1 // OpenTween - Client of Twitter
2 // Copyright (c) 2019 kim_upsilon (@kim_upsilon) <https://upsilo.net/~upsilon/>
3 // All rights reserved.
5 // This file is part of OpenTween.
7 // This program is free software; you can redistribute it and/or modify it
8 // under the terms of the GNU General Public License as published by the Free
9 // Software Foundation; either version 3 of the License, or (at your option)
12 // This program is distributed in the hope that it will be useful, but
13 // WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
14 // or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
17 // You should have received a copy of the GNU General Public License along
18 // with this program. If not, see <http://www.gnu.org/licenses/>, or write to
19 // the Free Software Foundation, Inc., 51 Franklin Street - Fifth Floor,
20 // Boston, MA 02110-1301, USA.
23 using System.Collections.Generic;
24 using System.Threading;
25 using System.Threading.Tasks;
29 public class TimelineScheduler
31 private readonly Timer timer;
33 private bool enabled = false;
34 private bool systemResumeMode = false;
35 private bool preventTimerUpdate = false;
42 if (this.enabled == value)
47 var now = DateTimeUtc.Now;
48 this.LastUpdateHome = now;
49 this.LastUpdateMention = now;
50 this.LastUpdateDm = now;
51 this.LastUpdatePublicSearch = now;
52 this.LastUpdateUser = now;
53 this.LastUpdateList = now;
54 this.LastUpdateConfig = now;
57 this.RefreshSchedule();
61 public DateTimeUtc LastUpdateHome { get; private set; } = DateTimeUtc.MinValue;
62 public DateTimeUtc LastUpdateMention { get; private set; } = DateTimeUtc.MinValue;
63 public DateTimeUtc LastUpdateDm { get; private set; } = DateTimeUtc.MinValue;
64 public DateTimeUtc LastUpdatePublicSearch { get; private set; } = DateTimeUtc.MinValue;
65 public DateTimeUtc LastUpdateUser { get; private set; } = DateTimeUtc.MinValue;
66 public DateTimeUtc LastUpdateList { get; private set; } = DateTimeUtc.MinValue;
67 public DateTimeUtc LastUpdateConfig { get; private set; } = DateTimeUtc.MinValue;
68 public DateTimeUtc SystemResumedAt { get; private set; } = DateTimeUtc.MinValue;
70 public TimeSpan UpdateIntervalHome { get; set; } = Timeout.InfiniteTimeSpan;
71 public TimeSpan UpdateIntervalMention { get; set; } = Timeout.InfiniteTimeSpan;
72 public TimeSpan UpdateIntervalDm { get; set; } = Timeout.InfiniteTimeSpan;
73 public TimeSpan UpdateIntervalPublicSearch { get; set; } = Timeout.InfiniteTimeSpan;
74 public TimeSpan UpdateIntervalUser { get; set; } = Timeout.InfiniteTimeSpan;
75 public TimeSpan UpdateIntervalList { get; set; } = Timeout.InfiniteTimeSpan;
76 public TimeSpan UpdateIntervalConfig { get; set; } = Timeout.InfiniteTimeSpan;
77 public TimeSpan UpdateAfterSystemResume { get; set; } = Timeout.InfiniteTimeSpan;
79 public Func<Task> UpdateHome;
80 public Func<Task> UpdateMention;
81 public Func<Task> UpdateDm;
82 public Func<Task> UpdatePublicSearch;
83 public Func<Task> UpdateUser;
84 public Func<Task> UpdateList;
85 public Func<Task> UpdateConfig;
88 private enum UpdateTask
94 PublicSearch = 1 << 4,
98 All = Home | Mention | Dm | PublicSearch | User | List | Config,
101 public TimelineScheduler()
102 => this.timer = new Timer(_ => this.TimerCallback());
104 public void RefreshSchedule()
106 if (this.preventTimerUpdate)
107 return; // TimerCallback 内で更新されるのでここは単に無視してよい
110 this.timer.Change(this.NextTimerDelay(), Timeout.InfiniteTimeSpan);
112 this.timer.Change(Timeout.InfiniteTimeSpan, Timeout.InfiniteTimeSpan);
115 public void SystemResumed()
117 this.SystemResumedAt = DateTimeUtc.Now;
118 this.systemResumeMode = true;
119 this.RefreshSchedule();
122 private async void TimerCallback()
126 this.preventTimerUpdate = true;
128 if (this.systemResumeMode)
129 await this.TimerCallback_AfterSystemResume().ConfigureAwait(false);
131 await this.TimerCallback_Normal().ConfigureAwait(false);
135 this.preventTimerUpdate = false;
136 this.RefreshSchedule();
140 private async Task TimerCallback_Normal()
142 var now = DateTimeUtc.Now;
143 var round = TimeSpan.FromSeconds(1); // 1秒未満の差異であればまとめて実行する
144 var tasks = UpdateTask.None;
146 var nextScheduledHome = this.LastUpdateHome + this.UpdateIntervalHome;
147 if (nextScheduledHome - now < round)
148 tasks |= UpdateTask.Home;
150 var nextScheduledMention = this.LastUpdateMention + this.UpdateIntervalMention;
151 if (nextScheduledMention - now < round)
152 tasks |= UpdateTask.Mention;
154 var nextScheduledDm = this.LastUpdateDm + this.UpdateIntervalDm;
155 if (nextScheduledDm - now < round)
156 tasks |= UpdateTask.Dm;
158 var nextScheduledPublicSearch = this.LastUpdatePublicSearch + this.UpdateIntervalPublicSearch;
159 if (nextScheduledPublicSearch - now < round)
160 tasks |= UpdateTask.PublicSearch;
162 var nextScheduledUser = this.LastUpdateUser + this.UpdateIntervalUser;
163 if (nextScheduledUser - now < round)
164 tasks |= UpdateTask.User;
166 var nextScheduledList = this.LastUpdateList + this.UpdateIntervalList;
167 if (nextScheduledList - now < round)
168 tasks |= UpdateTask.List;
170 var nextScheduledConfig = this.LastUpdateConfig + this.UpdateIntervalConfig;
171 if (nextScheduledConfig - now < round)
172 tasks |= UpdateTask.Config;
174 await this.RunUpdateTasks(tasks, now).ConfigureAwait(false);
177 private async Task TimerCallback_AfterSystemResume()
179 // systemResumeMode では一定期間経過後に全てのタイムラインを更新する
180 var now = DateTimeUtc.Now;
182 var nextScheduledUpdateAll = this.SystemResumedAt + this.UpdateAfterSystemResume;
183 if (nextScheduledUpdateAll - now < TimeSpan.Zero)
185 this.systemResumeMode = false;
186 await this.RunUpdateTasks(UpdateTask.All, now).ConfigureAwait(false);
190 private async Task RunUpdateTasks(UpdateTask tasks, DateTimeUtc now)
192 var updateTasks = new List<Task>(capacity: 7);
194 // LastUpdate* を次の時刻に更新してから Update* を実行すること
195 // (LastUpdate* が更新されずに Update* が例外を投げると無限ループに陥る)
197 if ((tasks & UpdateTask.Home) == UpdateTask.Home)
199 this.LastUpdateHome = now;
200 if (this.UpdateHome != null)
201 updateTasks.Add(this.UpdateHome());
204 if ((tasks & UpdateTask.Mention) == UpdateTask.Mention)
206 this.LastUpdateMention = now;
207 if (this.UpdateMention != null)
208 updateTasks.Add(this.UpdateMention());
211 if ((tasks & UpdateTask.Dm) == UpdateTask.Dm)
213 this.LastUpdateDm = now;
214 if (this.UpdateDm != null)
215 updateTasks.Add(this.UpdateDm());
218 if ((tasks & UpdateTask.PublicSearch) == UpdateTask.PublicSearch)
220 this.LastUpdatePublicSearch = now;
221 if (this.UpdatePublicSearch != null)
222 updateTasks.Add(this.UpdatePublicSearch());
225 if ((tasks & UpdateTask.User) == UpdateTask.User)
227 this.LastUpdateUser = now;
228 if (this.UpdateUser != null)
229 updateTasks.Add(this.UpdateUser());
232 if ((tasks & UpdateTask.List) == UpdateTask.List)
234 this.LastUpdateList = now;
235 if (this.UpdateList != null)
236 updateTasks.Add(this.UpdateList());
239 if ((tasks & UpdateTask.Config) == UpdateTask.Config)
241 this.LastUpdateConfig = now;
242 if (this.UpdateConfig != null)
243 updateTasks.Add(this.UpdateConfig());
246 await Task.WhenAll(updateTasks).ConfigureAwait(false);
249 private TimeSpan NextTimerDelay()
253 if (this.systemResumeMode)
255 // systemResumeMode が有効な間は UpdateAfterSystemResume 以外の設定値を見ない
256 var nextScheduledUpdateAll = this.SystemResumedAt + this.UpdateAfterSystemResume;
257 delay = nextScheduledUpdateAll - DateTimeUtc.Now;
259 return delay > TimeSpan.Zero ? delay : TimeSpan.Zero;
263 var min = DateTimeUtc.MaxValue;
265 var nextScheduledHome = this.LastUpdateHome + this.UpdateIntervalHome;
266 if (nextScheduledHome < min)
267 min = nextScheduledHome;
269 var nextScheduledMention = this.LastUpdateMention + this.UpdateIntervalMention;
270 if (nextScheduledMention < min)
271 min = nextScheduledMention;
273 var nextScheduledDm = this.LastUpdateDm + this.UpdateIntervalDm;
274 if (nextScheduledDm < min)
275 min = nextScheduledDm;
277 var nextScheduledPublicSearch = this.LastUpdatePublicSearch + this.UpdateIntervalPublicSearch;
278 if (nextScheduledPublicSearch < min)
279 min = nextScheduledPublicSearch;
281 var nextScheduledUser = this.LastUpdateUser + this.UpdateIntervalUser;
282 if (nextScheduledUser < min)
283 min = nextScheduledUser;
285 var nextScheduledList = this.LastUpdateList + this.UpdateIntervalList;
286 if (nextScheduledList < min)
287 min = nextScheduledList;
289 var nextScheduledConfig = this.LastUpdateConfig + this.UpdateIntervalConfig;
290 if (nextScheduledConfig < min)
291 min = nextScheduledConfig;
293 delay = min - DateTimeUtc.Now;
295 return delay > TimeSpan.Zero ? delay : TimeSpan.Zero;