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.
25 using System.Collections.Generic;
27 using System.Threading;
28 using System.Threading.Tasks;
32 public class TimelineScheduler
34 private readonly AsyncTimer timer;
36 private bool enabled = false;
37 private bool systemResumeMode = false;
38 private bool preventTimerUpdate = false;
45 if (this.enabled == value)
50 var now = DateTimeUtc.Now;
51 this.LastUpdateHome = now;
52 this.LastUpdateMention = now;
53 this.LastUpdateDm = now;
54 this.LastUpdatePublicSearch = now;
55 this.LastUpdateUser = now;
56 this.LastUpdateList = now;
57 this.LastUpdateConfig = now;
60 this.RefreshSchedule();
64 public DateTimeUtc LastUpdateHome { get; private set; } = DateTimeUtc.MinValue;
65 public DateTimeUtc LastUpdateMention { get; private set; } = DateTimeUtc.MinValue;
66 public DateTimeUtc LastUpdateDm { get; private set; } = DateTimeUtc.MinValue;
67 public DateTimeUtc LastUpdatePublicSearch { get; private set; } = DateTimeUtc.MinValue;
68 public DateTimeUtc LastUpdateUser { get; private set; } = DateTimeUtc.MinValue;
69 public DateTimeUtc LastUpdateList { get; private set; } = DateTimeUtc.MinValue;
70 public DateTimeUtc LastUpdateConfig { get; private set; } = DateTimeUtc.MinValue;
71 public DateTimeUtc SystemResumedAt { get; private set; } = DateTimeUtc.MinValue;
73 public TimeSpan UpdateIntervalHome { get; set; } = Timeout.InfiniteTimeSpan;
74 public TimeSpan UpdateIntervalMention { get; set; } = Timeout.InfiniteTimeSpan;
75 public TimeSpan UpdateIntervalDm { get; set; } = Timeout.InfiniteTimeSpan;
76 public TimeSpan UpdateIntervalPublicSearch { get; set; } = Timeout.InfiniteTimeSpan;
77 public TimeSpan UpdateIntervalUser { get; set; } = Timeout.InfiniteTimeSpan;
78 public TimeSpan UpdateIntervalList { get; set; } = Timeout.InfiniteTimeSpan;
79 public TimeSpan UpdateIntervalConfig { get; set; } = Timeout.InfiniteTimeSpan;
80 public TimeSpan UpdateAfterSystemResume { get; set; } = Timeout.InfiniteTimeSpan;
82 public bool EnableUpdateHome
83 => this.UpdateIntervalHome != Timeout.InfiniteTimeSpan;
85 public bool EnableUpdateMention
86 => this.UpdateIntervalMention != Timeout.InfiniteTimeSpan;
88 public bool EnableUpdateDm
89 => this.UpdateIntervalDm != Timeout.InfiniteTimeSpan;
91 public bool EnableUpdatePublicSearch
92 => this.UpdateIntervalPublicSearch != Timeout.InfiniteTimeSpan;
94 public bool EnableUpdateUser
95 => this.UpdateIntervalUser != Timeout.InfiniteTimeSpan;
97 public bool EnableUpdateList
98 => this.UpdateIntervalList != Timeout.InfiniteTimeSpan;
100 public bool EnableUpdateConfig
101 => this.UpdateIntervalConfig != Timeout.InfiniteTimeSpan;
103 public bool EnableUpdateSystemResume
104 => this.UpdateAfterSystemResume != Timeout.InfiniteTimeSpan;
106 public Func<Task>? UpdateHome;
107 public Func<Task>? UpdateMention;
108 public Func<Task>? UpdateDm;
109 public Func<Task>? UpdatePublicSearch;
110 public Func<Task>? UpdateUser;
111 public Func<Task>? UpdateList;
112 public Func<Task>? UpdateConfig;
115 private enum UpdateTask
121 PublicSearch = 1 << 4,
125 All = Home | Mention | Dm | PublicSearch | User | List | Config,
128 public TimelineScheduler()
129 => this.timer = new AsyncTimer(this.TimerCallback);
131 public void RefreshSchedule()
133 if (this.preventTimerUpdate)
134 return; // TimerCallback 内で更新されるのでここは単に無視してよい
137 this.timer.Change(this.NextTimerDelay(), Timeout.InfiniteTimeSpan);
139 this.timer.Change(Timeout.InfiniteTimeSpan, Timeout.InfiniteTimeSpan);
142 public void SystemResumed()
144 if (!this.EnableUpdateSystemResume)
147 this.SystemResumedAt = DateTimeUtc.Now;
148 this.systemResumeMode = true;
149 this.RefreshSchedule();
152 private async Task TimerCallback()
156 this.preventTimerUpdate = true;
158 if (this.systemResumeMode)
159 await this.TimerCallback_AfterSystemResume().ConfigureAwait(false);
161 await this.TimerCallback_Normal().ConfigureAwait(false);
165 this.preventTimerUpdate = false;
166 this.RefreshSchedule();
170 private async Task TimerCallback_Normal()
172 var now = DateTimeUtc.Now;
173 var round = TimeSpan.FromSeconds(1); // 1秒未満の差異であればまとめて実行する
174 var tasks = UpdateTask.None;
176 if (this.EnableUpdateHome)
178 var nextScheduledHome = this.LastUpdateHome + this.UpdateIntervalHome;
179 if (nextScheduledHome - now < round)
180 tasks |= UpdateTask.Home;
183 if (this.EnableUpdateMention)
185 var nextScheduledMention = this.LastUpdateMention + this.UpdateIntervalMention;
186 if (nextScheduledMention - now < round)
187 tasks |= UpdateTask.Mention;
190 if (this.EnableUpdateDm)
192 var nextScheduledDm = this.LastUpdateDm + this.UpdateIntervalDm;
193 if (nextScheduledDm - now < round)
194 tasks |= UpdateTask.Dm;
197 if (this.EnableUpdatePublicSearch)
199 var nextScheduledPublicSearch = this.LastUpdatePublicSearch + this.UpdateIntervalPublicSearch;
200 if (nextScheduledPublicSearch - now < round)
201 tasks |= UpdateTask.PublicSearch;
204 if (this.EnableUpdateUser)
206 var nextScheduledUser = this.LastUpdateUser + this.UpdateIntervalUser;
207 if (nextScheduledUser - now < round)
208 tasks |= UpdateTask.User;
211 if (this.EnableUpdateList)
213 var nextScheduledList = this.LastUpdateList + this.UpdateIntervalList;
214 if (nextScheduledList - now < round)
215 tasks |= UpdateTask.List;
218 if (this.EnableUpdateConfig)
220 var nextScheduledConfig = this.LastUpdateConfig + this.UpdateIntervalConfig;
221 if (nextScheduledConfig - now < round)
222 tasks |= UpdateTask.Config;
225 await this.RunUpdateTasks(tasks, now).ConfigureAwait(false);
228 private async Task TimerCallback_AfterSystemResume()
230 // systemResumeMode では一定期間経過後に全てのタイムラインを更新する
231 var now = DateTimeUtc.Now;
233 var nextScheduledUpdateAll = this.SystemResumedAt + this.UpdateAfterSystemResume;
234 if (nextScheduledUpdateAll - now < TimeSpan.Zero)
236 this.systemResumeMode = false;
237 await this.RunUpdateTasks(UpdateTask.All, now).ConfigureAwait(false);
241 private async Task RunUpdateTasks(UpdateTask tasks, DateTimeUtc now)
243 var updateTasks = new List<Func<Task>>(capacity: 7);
245 if ((tasks & UpdateTask.Home) == UpdateTask.Home)
247 this.LastUpdateHome = now;
248 if (this.UpdateHome != null)
249 updateTasks.Add(this.UpdateHome);
252 if ((tasks & UpdateTask.Mention) == UpdateTask.Mention)
254 this.LastUpdateMention = now;
255 if (this.UpdateMention != null)
256 updateTasks.Add(this.UpdateMention);
259 if ((tasks & UpdateTask.Dm) == UpdateTask.Dm)
261 this.LastUpdateDm = now;
262 if (this.UpdateDm != null)
263 updateTasks.Add(this.UpdateDm);
266 if ((tasks & UpdateTask.PublicSearch) == UpdateTask.PublicSearch)
268 this.LastUpdatePublicSearch = now;
269 if (this.UpdatePublicSearch != null)
270 updateTasks.Add(this.UpdatePublicSearch);
273 if ((tasks & UpdateTask.User) == UpdateTask.User)
275 this.LastUpdateUser = now;
276 if (this.UpdateUser != null)
277 updateTasks.Add(this.UpdateUser);
280 if ((tasks & UpdateTask.List) == UpdateTask.List)
282 this.LastUpdateList = now;
283 if (this.UpdateList != null)
284 updateTasks.Add(this.UpdateList);
287 if ((tasks & UpdateTask.Config) == UpdateTask.Config)
289 this.LastUpdateConfig = now;
290 if (this.UpdateConfig != null)
291 updateTasks.Add(this.UpdateConfig);
294 await Task.WhenAll(updateTasks.Select(x => Task.Run(x)))
295 .ConfigureAwait(false);
298 private TimeSpan NextTimerDelay()
302 if (this.systemResumeMode)
304 // systemResumeMode が有効な間は UpdateAfterSystemResume 以外の設定値を見ない
305 var nextScheduledUpdateAll = this.SystemResumedAt + this.UpdateAfterSystemResume;
306 delay = nextScheduledUpdateAll - DateTimeUtc.Now;
308 return delay > TimeSpan.Zero ? delay : TimeSpan.Zero;
312 var min = DateTimeUtc.MaxValue;
314 if (this.EnableUpdateHome)
316 var nextScheduledHome = this.LastUpdateHome + this.UpdateIntervalHome;
317 if (nextScheduledHome < min)
318 min = nextScheduledHome;
321 if (this.EnableUpdateMention)
323 var nextScheduledMention = this.LastUpdateMention + this.UpdateIntervalMention;
324 if (nextScheduledMention < min)
325 min = nextScheduledMention;
328 if (this.EnableUpdateDm)
330 var nextScheduledDm = this.LastUpdateDm + this.UpdateIntervalDm;
331 if (nextScheduledDm < min)
332 min = nextScheduledDm;
335 if (this.EnableUpdatePublicSearch)
337 var nextScheduledPublicSearch = this.LastUpdatePublicSearch + this.UpdateIntervalPublicSearch;
338 if (nextScheduledPublicSearch < min)
339 min = nextScheduledPublicSearch;
342 if (this.EnableUpdateUser)
344 var nextScheduledUser = this.LastUpdateUser + this.UpdateIntervalUser;
345 if (nextScheduledUser < min)
346 min = nextScheduledUser;
349 if (this.EnableUpdateList)
351 var nextScheduledList = this.LastUpdateList + this.UpdateIntervalList;
352 if (nextScheduledList < min)
353 min = nextScheduledList;
356 if (this.EnableUpdateConfig)
358 var nextScheduledConfig = this.LastUpdateConfig + this.UpdateIntervalConfig;
359 if (nextScheduledConfig < min)
360 min = nextScheduledConfig;
363 delay = min - DateTimeUtc.Now;
365 return delay > TimeSpan.Zero ? delay : TimeSpan.Zero;