OSDN Git Service

fd4e1c41b7e494aa177cfd2f497d012841ebacc2
[opentween/open-tween.git] / OpenTween / Models / TabInformations.cs
1 // OpenTween - Client of Twitter
2 // Copyright (c) 2007-2011 kiri_feather (@kiri_feather) <kiri.feather@gmail.com>
3 //           (c) 2008-2011 Moz (@syo68k)
4 //           (c) 2008-2011 takeshik (@takeshik) <http://www.takeshik.org/>
5 //           (c) 2010-2011 anis774 (@anis774) <http://d.hatena.ne.jp/anis774/>
6 //           (c) 2010-2011 fantasticswallow (@f_swallow) <http://twitter.com/f_swallow>
7 //           (c) 2011      Egtra (@egtra) <http://dev.activebasic.com/egtra/>
8 //           (c) 2012      kim_upsilon (@kim_upsilon) <https://upsilo.net/~upsilon/>
9 // All rights reserved.
10 //
11 // This file is part of OpenTween.
12 //
13 // This program is free software; you can redistribute it and/or modify it
14 // under the terms of the GNU General Public License as published by the Free
15 // Software Foundation; either version 3 of the License, or (at your option)
16 // any later version.
17 //
18 // This program is distributed in the hope that it will be useful, but
19 // WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
20 // or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
21 // for more details.
22 //
23 // You should have received a copy of the GNU General Public License along
24 // with this program. If not, see <http://www.gnu.org/licenses/>, or write to
25 // the Free Software Foundation, Inc., 51 Franklin Street - Fifth Floor,
26 // Boston, MA 02110-1301, USA.
27
28 using System;
29 using System.Collections.Concurrent;
30 using System.Collections.Generic;
31 using System.Diagnostics;
32 using System.Linq;
33 using System.Text;
34 using System.Threading.Tasks;
35 using System.Windows.Forms;
36 using OpenTween.Setting;
37
38 namespace OpenTween.Models
39 {
40     public sealed class TabInformations
41     {
42         //個別タブの情報をDictionaryで保持
43         public Dictionary<string, TabModel> Tabs { get; } = new Dictionary<string, TabModel>();
44         public ConcurrentDictionary<long, PostClass> Posts { get; } = new ConcurrentDictionary<long, PostClass>();
45
46         private Dictionary<long, PostClass> _quotes = new Dictionary<long, PostClass>();
47         private ConcurrentDictionary<long, int> retweetsCount = new ConcurrentDictionary<long, int>();
48
49         public Stack<TabModel> RemovedTab { get; } = new Stack<TabModel>();
50
51         public ISet<long> BlockIds { get; set; } = new HashSet<long>();
52         public ISet<long> MuteUserIds { get; set; } = new HashSet<long>();
53
54         //発言の追加
55         //AddPost(複数回) -> DistributePosts          -> SubmitUpdate
56
57         private ConcurrentQueue<long> addQueue = new ConcurrentQueue<long>();
58
59         /// <summary>通知サウンドを再生する優先順位</summary>
60         private Dictionary<MyCommon.TabUsageType, int> notifyPriorityByTabType = new Dictionary<MyCommon.TabUsageType, int>
61         {
62             [MyCommon.TabUsageType.DirectMessage] = 100,
63             [MyCommon.TabUsageType.Mentions] = 90,
64             [MyCommon.TabUsageType.UserDefined] = 80,
65             [MyCommon.TabUsageType.Home] = 70,
66             [MyCommon.TabUsageType.Favorites] = 60,
67         };
68
69         //トランザクション用
70         private readonly object LockObj = new object();
71
72         private static TabInformations _instance = new TabInformations();
73
74         //List
75         private List<ListElement> _lists = new List<ListElement>();
76
77         private TabInformations()
78         {
79         }
80
81         public static TabInformations GetInstance()
82             => _instance; // singleton
83
84         public string SelectedTabName { get; private set; } = "";
85
86         public TabModel SelectedTab
87             => this.Tabs[this.SelectedTabName];
88
89         public List<ListElement> SubscribableLists
90         {
91             get => this._lists;
92             set
93             {
94                 if (value != null && value.Count > 0)
95                 {
96                     foreach (var tb in this.GetTabsByType<ListTimelineTabModel>())
97                     {
98                         foreach (var list in value)
99                         {
100                             if (tb.ListInfo.Id == list.Id)
101                             {
102                                 tb.ListInfo = list;
103                                 break;
104                             }
105                         }
106                     }
107                 }
108                 _lists = value;
109             }
110         }
111
112         public bool AddTab(TabModel tab)
113         {
114             lock (this.LockObj)
115             {
116                 if (this.Tabs.ContainsKey(tab.TabName))
117                     return false;
118
119                 this.Tabs.Add(tab.TabName, tab);
120                 tab.SetSortMode(this.SortMode, this.SortOrder);
121
122                 return true;
123             }
124         }
125
126         //public void AddTab(string TabName, TabClass Tab)
127         //{
128         //    _tabs.Add(TabName, Tab);
129         //}
130
131         public void RemoveTab(string TabName)
132         {
133             lock (LockObj)
134             {
135                 var tb = GetTabByName(TabName);
136                 if (tb.IsDefaultTabType) return; //念のため
137
138                 if (!tb.IsInnerStorageTabType)
139                 {
140                     var homeTab = GetTabByType(MyCommon.TabUsageType.Home);
141                     var dmTab = GetTabByType(MyCommon.TabUsageType.DirectMessage);
142
143                     for (int idx = 0; idx < tb.AllCount; ++idx)
144                     {
145                         var exist = false;
146                         var Id = tb.GetStatusIdAt(idx);
147                         if (Id < 0) continue;
148                         foreach (var tab in this.Tabs.Values)
149                         {
150                             if (tab != tb && tab != dmTab)
151                             {
152                                 if (tab.Contains(Id))
153                                 {
154                                     exist = true;
155                                     break;
156                                 }
157                             }
158                         }
159                         if (!exist) homeTab.AddPostImmediately(Id, this.Posts[Id].IsRead);
160                     }
161                 }
162                 this.RemovedTab.Push(tb);
163                 this.Tabs.Remove(TabName);
164             }
165         }
166
167         public bool ContainsTab(string TabText)
168             => this.Tabs.ContainsKey(TabText);
169
170         public bool ContainsTab(TabModel ts)
171             => this.Tabs.ContainsValue(ts);
172
173         public void SelectTab(string tabName)
174         {
175             if (!this.Tabs.ContainsKey(tabName))
176                 throw new ArgumentException($"{tabName} does not exist.", nameof(tabName));
177
178             this.SelectedTabName = tabName;
179         }
180
181         /// <summary>
182         /// 指定されたタブ名を元に、既存のタブ名との重複を避けた名前を生成します
183         /// </summary>
184         /// <param name="baseTabName">作成したいタブ名</param>
185         /// <returns>生成されたタブ名</returns>
186         /// <exception cref="TabException">タブ名の生成を 100 回試行して失敗した場合</exception>
187         public string MakeTabName(string baseTabName)
188             => this.MakeTabName(baseTabName, 100);
189
190         /// <summary>
191         /// 指定されたタブ名を元に、既存のタブ名との重複を避けた名前を生成します
192         /// </summary>
193         /// <param name="baseTabName">作成したいタブ名</param>
194         /// <param name="retryCount">重複を避けたタブ名を生成する試行回数</param>
195         /// <returns>生成されたタブ名</returns>
196         /// <exception cref="TabException">retryCount で指定された回数だけタブ名の生成を試行して失敗した場合</exception>
197         public string MakeTabName(string baseTabName, int retryCount)
198         {
199             if (!this.ContainsTab(baseTabName))
200                 return baseTabName;
201
202             foreach (var i in Enumerable.Range(2, retryCount - 1))
203             {
204                 var tabName = baseTabName + i;
205                 if (!this.ContainsTab(tabName))
206                 {
207                     return tabName;
208                 }
209             }
210
211             var message = string.Format(Properties.Resources.TabNameDuplicate_Text, baseTabName);
212             throw new TabException(message);
213         }
214
215         public Dictionary<string, TabModel>.KeyCollection KeysTab
216             => this.Tabs.Keys;
217
218         public SortOrder SortOrder { get; private set; }
219
220         public ComparerMode SortMode { get; private set; }
221
222         public void SetSortMode(ComparerMode mode, SortOrder sortOrder)
223         {
224             this.SortMode = mode;
225             this.SortOrder = sortOrder;
226
227             foreach (var tab in this.Tabs.Values)
228                 tab.SetSortMode(mode, sortOrder);
229         }
230
231         public SortOrder ToggleSortOrder(ComparerMode sortMode)
232         {
233             var sortOrder = this.SortOrder;
234
235             if (this.SortMode == sortMode)
236             {
237                 if (sortOrder == SortOrder.Ascending)
238                     sortOrder = SortOrder.Descending;
239                 else
240                     sortOrder = SortOrder.Ascending;
241             }
242             else
243             {
244                 sortOrder = SortOrder.Ascending;
245             }
246
247             this.SetSortMode(sortMode, sortOrder);
248
249             return this.SortOrder;
250         }
251
252         //    public PostClass RetweetSource(long Id)
253         //    {
254         //        get
255         //        {
256         //            if (_retweets.ContainsKey(Id))
257         //            {
258         //                return _retweets[Id];
259         //            }
260         //            else
261         //            {
262         //                return null;
263         //            }
264         //        }
265         //    }
266         public PostClass RetweetSource(long Id)
267             => this.Posts.TryGetValue(Id, out var status) ? status : null;
268
269         public void ScrubGeoReserve(long id, long upToStatusId)
270         {
271             lock (LockObj)
272             {
273                 //this._scrubGeo.Add(new ScrubGeoInfo With {.UserId = id, .UpToStatusId = upToStatusId});
274                 this.ScrubGeo(id, upToStatusId);
275             }
276         }
277
278         private void ScrubGeo(long userId, long upToStatusId)
279         {
280             lock (LockObj)
281             {
282                 var userPosts = from post in this.Posts.Values
283                                 where post.UserId == userId && post.UserId <= upToStatusId
284                                 select post;
285
286                 foreach (var p in userPosts)
287                 {
288                     p.PostGeo = null;
289                 }
290
291                 var userPosts2 = from tb in this.GetTabsInnerStorageType()
292                                  from post in tb.Posts.Values
293                                  where post.UserId == userId && post.UserId <= upToStatusId
294                                  select post;
295
296                 foreach (var p in userPosts2)
297                 {
298                     p.PostGeo = null;
299                 }
300             }
301         }
302
303         public void RemovePostFromAllTabs(long statusId, bool setIsDeleted)
304         {
305             foreach (var tab in this.Tabs.Values)
306             {
307                 tab.EnqueueRemovePost(statusId, setIsDeleted);
308             }
309
310             if (setIsDeleted)
311             {
312                 if (this.Posts.TryGetValue(statusId, out var post))
313                     post.IsDeleted = true;
314             }
315         }
316
317         public int SubmitUpdate()
318             => this.SubmitUpdate(out var soundFile, out var notifyPosts, out var newMentionOrDm, out var isDeletePost);
319
320         public int SubmitUpdate(out string soundFile, out PostClass[] notifyPosts,
321             out bool newMentionOrDm, out bool isDeletePost)
322         {
323             // 注:メインスレッドから呼ぶこと
324             lock (this.LockObj)
325             {
326                 soundFile = "";
327                 notifyPosts = Array.Empty<PostClass>();
328                 newMentionOrDm = false;
329                 isDeletePost = false;
330
331                 var addedCountTotal = 0;
332                 var removedIdsAll = new List<long>();
333                 var notifyPostsList = new List<PostClass>();
334
335                 var currentNotifyPriority = -1;
336
337                 foreach (var tab in this.Tabs.Values)
338                 {
339                     // 振分確定 (各タブに反映)
340                     var addedIds = tab.AddSubmit();
341
342                     if (tab.TabType == MyCommon.TabUsageType.Mentions ||
343                         tab.TabType == MyCommon.TabUsageType.DirectMessage)
344                     {
345                         if (addedIds.Count > 0)
346                             newMentionOrDm = true;
347                     }
348
349                     if (addedIds.Count != 0)
350                     {
351                         if (tab.Notify)
352                         {
353                             // 通知対象のリストに追加
354                             foreach (var statusId in addedIds)
355                             {
356                                 if (tab.Posts.TryGetValue(statusId, out var post))
357                                     notifyPostsList.Add(post);
358                             }
359                         }
360
361                         // 通知サウンドは TabClass.Notify の値に関わらず鳴らす
362                         // SettingCommon.PlaySound が false であれば TweenMain 側で無効化される
363                         if (!string.IsNullOrEmpty(tab.SoundFile))
364                         {
365                             if (!this.notifyPriorityByTabType.TryGetValue(tab.TabType, out var notifyPriority))
366                                 notifyPriority = 0;
367
368                             if (notifyPriority > currentNotifyPriority)
369                             {
370                                 // より優先度の高い通知を再生する
371                                 soundFile = tab.SoundFile;
372                                 currentNotifyPriority = notifyPriority;
373                             }
374                         }
375                     }
376
377                     addedCountTotal += addedIds.Count;
378
379                     var removedIds = tab.RemoveSubmit();
380                     removedIdsAll.AddRange(removedIds);
381                 }
382
383                 notifyPosts = notifyPostsList.Distinct().ToArray();
384
385                 if (removedIdsAll.Count > 0)
386                     isDeletePost = true;
387
388                 foreach (var removedId in removedIdsAll.Distinct())
389                 {
390                     var orphaned = true;
391                     foreach (var tab in this.Tabs.Values)
392                     {
393                         if (tab.Contains(removedId))
394                         {
395                             orphaned = false;
396                             break;
397                         }
398                     }
399
400                     // 全てのタブから表示されなくなった発言は this._statuses からも削除する
401                     if (orphaned)
402                         this.Posts.TryRemove(removedId, out var removedPost);
403                 }
404
405                 return addedCountTotal;
406             }
407         }
408
409         public int DistributePosts()
410         {
411             lock (this.LockObj)
412             {
413                 var homeTab = this.GetTabByType(MyCommon.TabUsageType.Home);
414                 var replyTab = this.GetTabByType(MyCommon.TabUsageType.Mentions);
415                 var favTab = this.GetTabByType(MyCommon.TabUsageType.Favorites);
416
417                 var distributableTabs = this.GetTabsByType<FilterTabModel>()
418                     .ToArray();
419
420                 var adddedCount = 0;
421
422                 while (this.addQueue.TryDequeue(out var statusId))
423                 {
424                     if (!this.Posts.TryGetValue(statusId, out var post))
425                         continue;
426
427                     var filterHit = false; // フィルタにヒットしたタブがあるか
428                     var mark = false; // フィルタによってマーク付けされたか
429                     var excludedReply = false; // リプライから除外されたか
430                     var moved = false; // Recentタブから移動するか (Recentタブに表示しない)
431
432                     foreach (var tab in distributableTabs)
433                     {
434                         // 各振り分けタブのフィルタを実行する
435                         switch (tab.AddFiltered(post))
436                         {
437                             case MyCommon.HITRESULT.Copy:
438                                 filterHit = true;
439                                 break;
440                             case MyCommon.HITRESULT.CopyAndMark:
441                                 filterHit = true;
442                                 mark = true;
443                                 break;
444                             case MyCommon.HITRESULT.Move:
445                                 filterHit = true;
446                                 moved = true;
447                                 break;
448                             case MyCommon.HITRESULT.None:
449                                 break;
450                             case MyCommon.HITRESULT.Exclude:
451                                 if (tab.TabType == MyCommon.TabUsageType.Mentions)
452                                     excludedReply = true;
453                                 break;
454                         }
455                     }
456
457                     post.FilterHit = filterHit;
458                     post.IsMark = mark;
459                     post.IsExcludeReply = excludedReply;
460
461                     // 移動されなかったらRecentに追加
462                     if (!moved)
463                         homeTab.AddPostQueue(post);
464
465                     // 除外ルール適用のないReplyならReplyタブに追加
466                     if (post.IsReply && !excludedReply)
467                         replyTab.AddPostQueue(post);
468
469                     // Fav済み発言だったらFavoritesタブに追加
470                     if (post.IsFav)
471                         favTab.AddPostQueue(post);
472
473                     adddedCount++;
474                 }
475
476                 return adddedCount;
477             }
478         }
479
480         public void AddPost(PostClass Item)
481         {
482             Debug.Assert(!Item.IsDm, "DM は TabClass.AddPostToInnerStorage を使用する");
483
484             lock (LockObj)
485             {
486                 if (this.IsMuted(Item, isHomeTimeline: true))
487                     return;
488
489                 if (Posts.TryGetValue(Item.StatusId, out var status))
490                 {
491                     if (Item.IsFav)
492                     {
493                         if (Item.RetweetedId == null)
494                         {
495                             status.IsFav = true;
496                         }
497                         else
498                         {
499                             Item.IsFav = false;
500                         }
501                     }
502                     else
503                     {
504                         return;        //追加済みなら何もしない
505                     }
506                 }
507                 else
508                 {
509                     if (Item.IsFav && Item.RetweetedId != null) Item.IsFav = false;
510
511                     //既に持っている公式RTは捨てる
512                     if (Item.RetweetedId != null && SettingManager.Common.HideDuplicatedRetweets)
513                     {
514                         var retweetCount = this.UpdateRetweetCount(Item);
515
516                         if (retweetCount > 1 && !Item.IsMe)
517                             return;
518                     }
519
520                     if (BlockIds.Contains(Item.UserId))
521                         return;
522
523                     Posts.TryAdd(Item.StatusId, Item);
524                 }
525                 if (Item.IsFav && this.retweetsCount.ContainsKey(Item.StatusId))
526                 {
527                     return;    //Fav済みのRetweet元発言は追加しない
528                 }
529                 this.addQueue.Enqueue(Item.StatusId);
530             }
531         }
532
533         public bool IsMuted(PostClass post, bool isHomeTimeline)
534         {
535             var muteTab = this.GetTabByType<MuteTabModel>();
536             if (muteTab != null && muteTab.AddFiltered(post) == MyCommon.HITRESULT.Move)
537                 return true;
538
539             // これ以降は Twitter 標準のミュート機能に準じた判定
540             // 参照: https://support.twitter.com/articles/20171399-muting-users-on-twitter
541
542             // ホームタイムライン以外 (検索・リストなど) は対象外
543             if (!isHomeTimeline)
544                 return false;
545
546             // リプライはミュート対象外
547             if (post.IsReply)
548                 return false;
549
550             if (this.MuteUserIds.Contains(post.UserId))
551                 return true;
552
553             if (post.RetweetedByUserId != null && this.MuteUserIds.Contains(post.RetweetedByUserId.Value))
554                 return true;
555
556             return false;
557         }
558
559         private int UpdateRetweetCount(PostClass retweetPost)
560         {
561             var retweetedId = retweetPost.RetweetedId.Value;
562
563             return this.retweetsCount.AddOrUpdate(retweetedId, 1, (k, v) => v >= 10 ? 1 : v + 1);
564         }
565
566         public bool AddQuoteTweet(PostClass item)
567         {
568             lock (LockObj)
569             {
570                 if (IsMuted(item, isHomeTimeline: false) || BlockIds.Contains(item.UserId))
571                     return false;
572
573                 _quotes[item.StatusId] = item;
574                 return true;
575             }
576         }
577
578         /// <summary>
579         /// 全てのタブを横断して既読状態を変更します
580         /// </summary>
581         /// <param name="statusId">変更するツイートのID</param>
582         /// <param name="read">既読状態</param>
583         /// <returns>既読状態に変化があれば true、変化がなければ false</returns>
584         public bool SetReadAllTab(long statusId, bool read)
585         {
586             lock (LockObj)
587             {
588                 foreach (var tab in this.Tabs.Values)
589                 {
590                     if (!tab.Contains(statusId))
591                         continue;
592
593                     tab.SetReadState(statusId, read);
594                 }
595
596                 // TabInformations自身が保持しているツイートであればここで IsRead を変化させる
597                 if (this.Posts.TryGetValue(statusId, out var post))
598                     post.IsRead = read;
599
600                 return true;
601             }
602         }
603
604         /// <summary>
605         /// Home タブのツイートを全て既読にします。
606         /// ただし IsReply または FilterHit が true なものを除きます。
607         /// </summary>
608         public void SetReadHomeTab()
609         {
610             var homeTab = this.GetTabByType(MyCommon.TabUsageType.Home);
611
612             lock (LockObj)
613             {
614                 foreach (var statusId in homeTab.GetUnreadIds())
615                 {
616                     if (!this.Posts.TryGetValue(statusId, out var post))
617                         continue;
618
619                     if (post.IsReply || post.FilterHit)
620                         continue;
621
622                     this.SetReadAllTab(post.StatusId, read: true);
623                 }
624             }
625         }
626
627         public PostClass this[long ID]
628         {
629             get
630             {
631                 if (this.Posts.TryGetValue(ID, out var status))
632                     return status;
633
634                 if (this._quotes.TryGetValue(ID, out status))
635                     return status;
636
637                 return this.GetTabsInnerStorageType()
638                     .Select(x => x.Posts.TryGetValue(ID, out status) ? status : null)
639                     .FirstOrDefault(x => x != null);
640             }
641         }
642
643         public bool ContainsKey(long Id)
644         {
645             //DM,公式検索は非対応
646             lock (LockObj)
647             {
648                 return Posts.ContainsKey(Id);
649             }
650         }
651
652         public void RenameTab(string Original, string NewName)
653         {
654             lock (this.LockObj)
655             {
656                 var tb = this.Tabs[Original];
657                 this.Tabs.Remove(Original);
658                 tb.TabName = NewName;
659                 this.Tabs.Add(NewName, tb);
660             }
661         }
662
663         public void FilterAll()
664         {
665             lock (LockObj)
666             {
667                 var homeTab = GetTabByType(MyCommon.TabUsageType.Home);
668                 var detachedIdsAll = Enumerable.Empty<long>();
669
670                 foreach (var tab in this.Tabs.Values.OfType<FilterTabModel>().ToArray())
671                 {
672                     if (tab.TabType == MyCommon.TabUsageType.Mute)
673                         continue;
674
675                     // フィルタに変更のあったタブのみを対象とする
676                     if (!tab.FilterModified)
677                         continue;
678
679                     tab.FilterModified = false;
680
681                     // フィルタ実行前の時点でタブに含まれていたstatusIdを記憶する
682                     var orgIds = tab.StatusIds;
683                     tab.ClearIDs();
684
685                     foreach (var post in Posts.Values)
686                     {
687                         var filterHit = false; // フィルタにヒットしたタブがあるか
688                         var mark = false; // フィルタによってマーク付けされたか
689                         var excluded = false; // 除外フィルタによって除外されたか
690                         var moved = false; // Recentタブから移動するか (Recentタブに表示しない)
691
692                         switch (tab.AddFiltered(post, immediately: true))
693                         {
694                             case MyCommon.HITRESULT.Copy:
695                                 filterHit = true;
696                                 break;
697                             case MyCommon.HITRESULT.CopyAndMark:
698                                 filterHit = true;
699                                 mark = true;
700                                 break;
701                             case MyCommon.HITRESULT.Move:
702                                 filterHit = true;
703                                 moved = true;
704                                 break;
705                             case MyCommon.HITRESULT.None:
706                                 break;
707                             case MyCommon.HITRESULT.Exclude:
708                                 excluded = true;
709                                 break;
710                         }
711
712                         post.FilterHit = filterHit;
713                         post.IsMark = mark;
714
715                         // 移動されたらRecentから除去
716                         if (moved)
717                             homeTab.RemovePostImmediately(post.StatusId);
718
719                         if (tab.TabType == MyCommon.TabUsageType.Mentions)
720                         {
721                             post.IsExcludeReply = excluded;
722
723                             // 除外ルール適用のないReplyならReplyタブに追加
724                             if (post.IsReply && !excluded)
725                                 tab.AddPostImmediately(post.StatusId, post.IsRead);
726                         }
727                     }
728
729                     // フィルタの更新によってタブから取り除かれたツイートのID
730                     var detachedIds = orgIds.Except(tab.StatusIds).ToArray();
731
732                     detachedIdsAll = detachedIdsAll.Concat(detachedIds);
733                 }
734
735                 // detachedIdsAll のうち、最終的にどのタブにも振り分けられていないツイートがあればRecentに追加
736                 foreach (var id in detachedIdsAll)
737                 {
738                     var hit = false;
739                     foreach (var tbTemp in this.Tabs.Values.ToArray())
740                     {
741                         if (!tbTemp.IsDistributableTabType)
742                             continue;
743
744                         if (tbTemp.Contains(id))
745                         {
746                             hit = true;
747                             break;
748                         }
749                     }
750
751                     if (!hit)
752                     {
753                         if (this.Posts.TryGetValue(id, out var post))
754                             homeTab.AddPostImmediately(post.StatusId, post.IsRead);
755                     }
756                 }
757             }
758         }
759
760         public void ClearTabIds(string TabName)
761         {
762             //不要なPostを削除
763             lock (LockObj)
764             {
765                 var tb = this.Tabs[TabName];
766                 if (!tb.IsInnerStorageTabType)
767                 {
768                     foreach (var Id in tb.StatusIds)
769                     {
770                         var Hit = false;
771                         foreach (var tab in this.Tabs.Values)
772                         {
773                             if (tab.Contains(Id))
774                             {
775                                 Hit = true;
776                                 break;
777                             }
778                         }
779                         if (!Hit)
780                             Posts.TryRemove(Id, out var removedPost);
781                     }
782                 }
783
784                 //指定タブをクリア
785                 tb.ClearIDs();
786             }
787         }
788
789         public void RefreshOwl(ISet<long> follower)
790         {
791             lock (LockObj)
792             {
793                 if (follower.Count > 0)
794                 {
795                     foreach (var post in Posts.Values)
796                     {
797                         //if (post.UserId = 0 || post.IsDm) Continue For
798                         if (post.IsMe)
799                         {
800                             post.IsOwl = false;
801                         }
802                         else
803                         {
804                             post.IsOwl = !follower.Contains(post.UserId);
805                         }
806                     }
807                 }
808                 else
809                 {
810                     foreach (var post in Posts.Values)
811                     {
812                         post.IsOwl = false;
813                     }
814                 }
815             }
816         }
817
818         public TabModel GetTabByType(MyCommon.TabUsageType tabType)
819         {
820             //Home,Mentions,DM,Favは1つに制限する
821             //その他のタイプを指定されたら、最初に合致したものを返す
822             //合致しなければnullを返す
823             lock (LockObj)
824             {
825                 return this.Tabs.Values
826                     .FirstOrDefault(x => x.TabType.HasFlag(tabType));
827             }
828         }
829
830         public T GetTabByType<T>() where T : TabModel
831         {
832             lock (this.LockObj)
833                 return this.Tabs.Values.OfType<T>().FirstOrDefault();
834         }
835
836         public TabModel[] GetTabsByType(MyCommon.TabUsageType tabType)
837         {
838             lock (LockObj)
839             {
840                 return this.Tabs.Values
841                     .Where(x => x.TabType.HasFlag(tabType))
842                     .ToArray();
843             }
844         }
845
846         public T[] GetTabsByType<T>() where T : TabModel
847         {
848             lock (this.LockObj)
849                 return this.Tabs.Values.OfType<T>().ToArray();
850         }
851
852         public TabModel[] GetTabsInnerStorageType()
853         {
854             lock (LockObj)
855             {
856                 return this.Tabs.Values
857                     .Where(x => x.IsInnerStorageTabType)
858                     .ToArray();
859             }
860         }
861
862         public TabModel GetTabByName(string tabName)
863         {
864             lock (LockObj)
865             {
866                 return this.Tabs.TryGetValue(tabName, out var tab)
867                     ? tab
868                     : null;
869             }
870         }
871     }
872 }