OSDN Git Service

FavoriteTweet/UnfavoriteTweetを使用したFav追加・削除に対応
[opentween/open-tween.git] / OpenTween / ThrottleTimer.cs
1 // OpenTween - Client of Twitter
2 // Copyright (c) 2018 kim_upsilon (@kim_upsilon) <https://upsilo.net/~upsilon/>
3 // All rights reserved.
4 //
5 // This file is part of OpenTween.
6 //
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)
10 // any later version.
11 //
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
15 // for more details.
16 //
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.
21
22 #nullable enable
23
24 using System;
25 using System.Threading;
26 using System.Threading.Tasks;
27
28 namespace OpenTween
29 {
30     /// <summary>
31     /// コールバック先の関数を <see cref="Interval"/> 未満の頻度で呼ばないよう制御するタイマー
32     /// </summary>
33     /// <remarks>
34     /// lodash の <c>_.throttle()</c> に相当する機能となっている
35     /// </remarks>
36     public class ThrottleTimer : IDisposable
37     {
38         private readonly ITimer throttlingTimer;
39         private readonly Func<Task> timerCallback;
40         private readonly object lockObject = new();
41
42         private bool calledSinceLastInvoke;
43         private bool refreshTimerEnabled;
44
45         public TimeSpan Interval { get; }
46
47         public ThrottleTimer(Func<Task> timerCallback, TimeSpan interval)
48         {
49             this.timerCallback = timerCallback;
50             this.Interval = interval;
51             this.throttlingTimer = this.CreateTimer(this.Execute);
52             this.calledSinceLastInvoke = false;
53             this.refreshTimerEnabled = false;
54         }
55
56         protected virtual ITimer CreateTimer(Func<Task> callback)
57             => new AsyncTimer(callback);
58
59         public async Task Call()
60         {
61             bool startTimer;
62             lock (this.lockObject)
63             {
64                 this.calledSinceLastInvoke = true;
65                 if (this.refreshTimerEnabled)
66                 {
67                     startTimer = false;
68                 }
69                 else
70                 {
71                     startTimer = true;
72                     this.refreshTimerEnabled = true;
73                 }
74             }
75
76             if (startTimer)
77             {
78                 await this.Invoke().ConfigureAwait(false);
79                 this.throttlingTimer.Change(dueTime: this.Interval, period: Timeout.InfiniteTimeSpan);
80             }
81         }
82
83         private async Task Execute()
84         {
85             bool invoke;
86             lock (this.lockObject)
87             {
88                 if (this.calledSinceLastInvoke)
89                 {
90                     invoke = true;
91                 }
92                 else
93                 {
94                     invoke = false;
95                     this.refreshTimerEnabled = false;
96                 }
97             }
98
99             if (invoke)
100             {
101                 await this.Invoke().ConfigureAwait(false);
102                 this.throttlingTimer.Change(dueTime: this.Interval, period: Timeout.InfiniteTimeSpan);
103             }
104         }
105
106         private async Task Invoke()
107         {
108             await Task.Run(async () =>
109             {
110                 lock (this.lockObject)
111                     this.calledSinceLastInvoke = false;
112
113                 await this.timerCallback().ConfigureAwait(false);
114             });
115         }
116
117         public void Dispose()
118             => this.throttlingTimer.Dispose();
119
120         public static ThrottleTimer Create(Func<Task> callback, TimeSpan wait)
121             => new(callback, wait);
122     }
123 }