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;
26 using System.Threading;
27 using System.Threading.Tasks;
31 public class TimelineScheduler
33 private readonly Timer timer;
35 private bool enabled = false;
36 private bool systemResumeMode = false;
37 private bool preventTimerUpdate = false;
44 if (this.enabled == value)
49 var now = DateTimeUtc.Now;
50 this.LastUpdateHome = now;
51 this.LastUpdateMention = now;
52 this.LastUpdateDm = now;
53 this.LastUpdatePublicSearch = now;
54 this.LastUpdateUser = now;
55 this.LastUpdateList = now;
56 this.LastUpdateConfig = now;
59 this.RefreshSchedule();
63 public DateTimeUtc LastUpdateHome { get; private set; } = DateTimeUtc.MinValue;
64 public DateTimeUtc LastUpdateMention { get; private set; } = DateTimeUtc.MinValue;
65 public DateTimeUtc LastUpdateDm { get; private set; } = DateTimeUtc.MinValue;
66 public DateTimeUtc LastUpdatePublicSearch { get; private set; } = DateTimeUtc.MinValue;
67 public DateTimeUtc LastUpdateUser { get; private set; } = DateTimeUtc.MinValue;
68 public DateTimeUtc LastUpdateList { get; private set; } = DateTimeUtc.MinValue;
69 public DateTimeUtc LastUpdateConfig { get; private set; } = DateTimeUtc.MinValue;
70 public DateTimeUtc SystemResumedAt { get; private set; } = DateTimeUtc.MinValue;
72 public TimeSpan UpdateIntervalHome { get; set; } = Timeout.InfiniteTimeSpan;
73 public TimeSpan UpdateIntervalMention { get; set; } = Timeout.InfiniteTimeSpan;
74 public TimeSpan UpdateIntervalDm { get; set; } = Timeout.InfiniteTimeSpan;
75 public TimeSpan UpdateIntervalPublicSearch { get; set; } = Timeout.InfiniteTimeSpan;
76 public TimeSpan UpdateIntervalUser { get; set; } = Timeout.InfiniteTimeSpan;
77 public TimeSpan UpdateIntervalList { get; set; } = Timeout.InfiniteTimeSpan;
78 public TimeSpan UpdateIntervalConfig { get; set; } = Timeout.InfiniteTimeSpan;
79 public TimeSpan UpdateAfterSystemResume { get; set; } = Timeout.InfiniteTimeSpan;
81 public Func<Task>? UpdateHome;
82 public Func<Task>? UpdateMention;
83 public Func<Task>? UpdateDm;
84 public Func<Task>? UpdatePublicSearch;
85 public Func<Task>? UpdateUser;
86 public Func<Task>? UpdateList;
87 public Func<Task>? UpdateConfig;
90 private enum UpdateTask
96 PublicSearch = 1 << 4,
100 All = Home | Mention | Dm | PublicSearch | User | List | Config,
103 public TimelineScheduler()
104 => this.timer = new Timer(_ => this.TimerCallback());
106 public void RefreshSchedule()
108 if (this.preventTimerUpdate)
109 return; // TimerCallback 内で更新されるのでここは単に無視してよい
112 this.timer.Change(this.NextTimerDelay(), Timeout.InfiniteTimeSpan);
114 this.timer.Change(Timeout.InfiniteTimeSpan, Timeout.InfiniteTimeSpan);
117 public void SystemResumed()
119 this.SystemResumedAt = DateTimeUtc.Now;
120 this.systemResumeMode = true;
121 this.RefreshSchedule();
124 private async void TimerCallback()
128 this.preventTimerUpdate = true;
130 if (this.systemResumeMode)
131 await this.TimerCallback_AfterSystemResume().ConfigureAwait(false);
133 await this.TimerCallback_Normal().ConfigureAwait(false);
137 this.preventTimerUpdate = false;
138 this.RefreshSchedule();
142 private async Task TimerCallback_Normal()
144 var now = DateTimeUtc.Now;
145 var round = TimeSpan.FromSeconds(1); // 1秒未満の差異であればまとめて実行する
146 var tasks = UpdateTask.None;
148 var nextScheduledHome = this.LastUpdateHome + this.UpdateIntervalHome;
149 if (nextScheduledHome - now < round)
150 tasks |= UpdateTask.Home;
152 var nextScheduledMention = this.LastUpdateMention + this.UpdateIntervalMention;
153 if (nextScheduledMention - now < round)
154 tasks |= UpdateTask.Mention;
156 var nextScheduledDm = this.LastUpdateDm + this.UpdateIntervalDm;
157 if (nextScheduledDm - now < round)
158 tasks |= UpdateTask.Dm;
160 var nextScheduledPublicSearch = this.LastUpdatePublicSearch + this.UpdateIntervalPublicSearch;
161 if (nextScheduledPublicSearch - now < round)
162 tasks |= UpdateTask.PublicSearch;
164 var nextScheduledUser = this.LastUpdateUser + this.UpdateIntervalUser;
165 if (nextScheduledUser - now < round)
166 tasks |= UpdateTask.User;
168 var nextScheduledList = this.LastUpdateList + this.UpdateIntervalList;
169 if (nextScheduledList - now < round)
170 tasks |= UpdateTask.List;
172 var nextScheduledConfig = this.LastUpdateConfig + this.UpdateIntervalConfig;
173 if (nextScheduledConfig - now < round)
174 tasks |= UpdateTask.Config;
176 await this.RunUpdateTasks(tasks, now).ConfigureAwait(false);
179 private async Task TimerCallback_AfterSystemResume()
181 // systemResumeMode では一定期間経過後に全てのタイムラインを更新する
182 var now = DateTimeUtc.Now;
184 var nextScheduledUpdateAll = this.SystemResumedAt + this.UpdateAfterSystemResume;
185 if (nextScheduledUpdateAll - now < TimeSpan.Zero)
187 this.systemResumeMode = false;
188 await this.RunUpdateTasks(UpdateTask.All, now).ConfigureAwait(false);
192 private async Task RunUpdateTasks(UpdateTask tasks, DateTimeUtc now)
194 var updateTasks = new List<Task>(capacity: 7);
196 // LastUpdate* を次の時刻に更新してから Update* を実行すること
197 // (LastUpdate* が更新されずに Update* が例外を投げると無限ループに陥る)
199 if ((tasks & UpdateTask.Home) == UpdateTask.Home)
201 this.LastUpdateHome = now;
202 if (this.UpdateHome != null)
203 updateTasks.Add(this.UpdateHome());
206 if ((tasks & UpdateTask.Mention) == UpdateTask.Mention)
208 this.LastUpdateMention = now;
209 if (this.UpdateMention != null)
210 updateTasks.Add(this.UpdateMention());
213 if ((tasks & UpdateTask.Dm) == UpdateTask.Dm)
215 this.LastUpdateDm = now;
216 if (this.UpdateDm != null)
217 updateTasks.Add(this.UpdateDm());
220 if ((tasks & UpdateTask.PublicSearch) == UpdateTask.PublicSearch)
222 this.LastUpdatePublicSearch = now;
223 if (this.UpdatePublicSearch != null)
224 updateTasks.Add(this.UpdatePublicSearch());
227 if ((tasks & UpdateTask.User) == UpdateTask.User)
229 this.LastUpdateUser = now;
230 if (this.UpdateUser != null)
231 updateTasks.Add(this.UpdateUser());
234 if ((tasks & UpdateTask.List) == UpdateTask.List)
236 this.LastUpdateList = now;
237 if (this.UpdateList != null)
238 updateTasks.Add(this.UpdateList());
241 if ((tasks & UpdateTask.Config) == UpdateTask.Config)
243 this.LastUpdateConfig = now;
244 if (this.UpdateConfig != null)
245 updateTasks.Add(this.UpdateConfig());
248 await Task.WhenAll(updateTasks).ConfigureAwait(false);
251 private TimeSpan NextTimerDelay()
255 if (this.systemResumeMode)
257 // systemResumeMode が有効な間は UpdateAfterSystemResume 以外の設定値を見ない
258 var nextScheduledUpdateAll = this.SystemResumedAt + this.UpdateAfterSystemResume;
259 delay = nextScheduledUpdateAll - DateTimeUtc.Now;
261 return delay > TimeSpan.Zero ? delay : TimeSpan.Zero;
265 var min = DateTimeUtc.MaxValue;
267 var nextScheduledHome = this.LastUpdateHome + this.UpdateIntervalHome;
268 if (nextScheduledHome < min)
269 min = nextScheduledHome;
271 var nextScheduledMention = this.LastUpdateMention + this.UpdateIntervalMention;
272 if (nextScheduledMention < min)
273 min = nextScheduledMention;
275 var nextScheduledDm = this.LastUpdateDm + this.UpdateIntervalDm;
276 if (nextScheduledDm < min)
277 min = nextScheduledDm;
279 var nextScheduledPublicSearch = this.LastUpdatePublicSearch + this.UpdateIntervalPublicSearch;
280 if (nextScheduledPublicSearch < min)
281 min = nextScheduledPublicSearch;
283 var nextScheduledUser = this.LastUpdateUser + this.UpdateIntervalUser;
284 if (nextScheduledUser < min)
285 min = nextScheduledUser;
287 var nextScheduledList = this.LastUpdateList + this.UpdateIntervalList;
288 if (nextScheduledList < min)
289 min = nextScheduledList;
291 var nextScheduledConfig = this.LastUpdateConfig + this.UpdateIntervalConfig;
292 if (nextScheduledConfig < min)
293 min = nextScheduledConfig;
295 delay = min - DateTimeUtc.Now;
297 return delay > TimeSpan.Zero ? delay : TimeSpan.Zero;