OSDN Git Service

NotificationManagerでタイマーをリピートをサポートする
[kancollesniffer/KancolleSniffer.git] / KancolleSniffer / NotificationManager.cs
1 // Copyright (C) 2017 Kazuhiro Fujieda <fujieda@users.osdn.me>\r
2 //\r
3 // Licensed under the Apache License, Version 2.0 (the "License");\r
4 // you may not use this file except in compliance with the License.\r
5 // You may obtain a copy of the License at\r
6 //\r
7 //    http://www.apache.org/licenses/LICENSE-2.0\r
8 //\r
9 // Unless required by applicable law or agreed to in writing, software\r
10 // distributed under the License is distributed on an "AS IS" BASIS,\r
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
12 // See the License for the specific language governing permissions and\r
13 // limitations under the License.\r
14 \r
15 using System;\r
16 using System.Collections.Generic;\r
17 using System.IO;\r
18 using System.Linq;\r
19 using System.Windows.Forms;\r
20 \r
21 namespace KancolleSniffer\r
22 {\r
23     public class NotificationManager\r
24     {\r
25         private readonly NotificationQueue _notificationQueue;\r
26 \r
27         private class Notification\r
28         {\r
29             public string Key { get; set; }\r
30             public int Fleet { get; set; }\r
31             public string Subject { get; set; }\r
32             public int Repeat { get; set; }\r
33             public DateTime Schedule { get; set; }\r
34         }\r
35 \r
36         public NotificationManager(Action<string, string, string> ring, ITimer timer = null)\r
37         {\r
38             _notificationQueue = new NotificationQueue(ring, timer);\r
39         }\r
40 \r
41         public void Enqueue(string key, int fleet, string subject, int repeat = 0)\r
42         {\r
43             _notificationQueue.Enqueue(new Notification\r
44             {\r
45                 Key = key,\r
46                 Fleet = fleet,\r
47                 Subject = subject,\r
48                 Repeat = repeat\r
49             });\r
50         }\r
51 \r
52         public void Enqueue(string key, string subject, int repeat = 0)\r
53         {\r
54             Enqueue(key, 0, subject, repeat);\r
55         }\r
56 \r
57         public void StopRepeat(string key)\r
58         {\r
59             _notificationQueue.StopRepeat(key);\r
60         }\r
61 \r
62         private class NotificationConfig\r
63         {\r
64             public class Message\r
65             {\r
66                 public string Title { get; set; }\r
67                 public string Body { get; set; }\r
68                 public string Name { get; set; }\r
69             }\r
70 \r
71             private readonly Dictionary<string, Message> _config = new Dictionary<string, Message>();\r
72 \r
73             private readonly Dictionary<string, Message> _default = new Dictionary<string, Message>\r
74             {\r
75                 {\r
76                     "遠征終了", new Message\r
77                     {\r
78                         Title = "遠征が終わりました",\r
79                         Body = "%s"\r
80                     }\r
81                 },\r
82                 {\r
83                     "入渠終了", new Message\r
84                     {\r
85                         Title = "入渠が終わりました",\r
86                         Body = "%s"\r
87                     }\r
88                 },\r
89                 {\r
90                     "建造完了", new Message\r
91                     {\r
92                         Title = "建造が終わりました",\r
93                         Body = "%s"\r
94                     }\r
95                 },\r
96                 {\r
97                     "艦娘数超過", new Message\r
98                     {\r
99                         Title = "艦娘が多すぎます",\r
100                         Body = "%s"\r
101                     }\r
102                 },\r
103                 {\r
104                     "装備数超過", new Message\r
105                     {\r
106                         Title = "装備が多すぎます",\r
107                         Body = "%s"\r
108                     }\r
109                 },\r
110                 {\r
111                     "大破警告", new Message\r
112                     {\r
113                         Title = "大破した艦娘がいます",\r
114                         Body = "%s"\r
115                     }\r
116                 },\r
117                 {\r
118                     "泊地修理20分経過", new Message\r
119                     {\r
120                         Title = "泊地修理 %f",\r
121                         Body = "20分経過しました。"\r
122                     }\r
123                 },\r
124                 {\r
125                     "泊地修理進行", new Message\r
126                     {\r
127                         Title = "泊地修理 %f",\r
128                         Body = "修理進行:%s"\r
129                     }\r
130                 },\r
131                 {\r
132                     "泊地修理完了", new Message\r
133                     {\r
134                         Title = "泊地修理 %f",\r
135                         Body = "修理完了:%s"\r
136                     }\r
137                 },\r
138                 {\r
139                     "疲労回復40", new Message\r
140                     {\r
141                         Title = "疲労が回復しました",\r
142                         Body = "%f 残り9分"\r
143                     }\r
144                 },\r
145                 {\r
146                     "疲労回復49", new Message\r
147                     {\r
148                         Title = "疲労が回復しました",\r
149                         Body = "%f"\r
150                     }\r
151                 }\r
152             };\r
153 \r
154             public static string KeyToName(string key) => key.StartsWith("疲労回復") ? key.Substring(0, 4) : key;\r
155 \r
156             private void LoadConfig()\r
157             {\r
158                 const string fileName = "notification.json";\r
159                 _config.Clear();\r
160                 try\r
161                 {\r
162                     dynamic config = JsonParser.Parse(File.ReadAllText(fileName));\r
163                     foreach (var entry in config)\r
164                     {\r
165                         if (!_default.ContainsKey(entry.key))\r
166                             continue;\r
167                         _config[entry.key] = new Message\r
168                         {\r
169                             Title = entry.title,\r
170                             Body = entry.body\r
171                         };\r
172                     }\r
173                 }\r
174                 catch (FileNotFoundException)\r
175                 {\r
176                 }\r
177                 catch (Exception ex)\r
178                 {\r
179                     throw new Exception($"{fileName}に誤りがあります。: ${ex.Message}", ex);\r
180                 }\r
181             }\r
182 \r
183             public Message GenerateMessage(Notification notification)\r
184             {\r
185                 LoadConfig();\r
186                 var format = _config.TryGetValue(notification.Key, out Message value) ? value : _default[notification.Key];\r
187                 return new Message\r
188                 {\r
189                     Title = ProcessFormatString(format.Title, notification.Fleet, notification.Subject),\r
190                     Body = ProcessFormatString(format.Body, notification.Fleet, notification.Subject),\r
191                     Name = KeyToName(notification.Key)\r
192                 };\r
193             }\r
194 \r
195             private string ProcessFormatString(string format, int fleet, string subject)\r
196             {\r
197                 var fn = new[] {"第一艦隊", "第二艦隊", "第三艦隊", "第四艦隊"};\r
198                 var result = "";\r
199                 var percent = false;\r
200                 foreach (var ch in format)\r
201                 {\r
202                     if (ch == '%')\r
203                     {\r
204                         if (percent)\r
205                         {\r
206                             percent = false;\r
207                             result += '%';\r
208                         }\r
209                         else\r
210                         {\r
211                             percent = true;\r
212                         }\r
213                     }\r
214                     else if (percent)\r
215                     {\r
216                         percent = false;\r
217                         switch (ch)\r
218                         {\r
219                             case 'f':\r
220                                 result += fn[fleet];\r
221                                 break;\r
222                             case 's':\r
223                                 result += subject;\r
224                                 break;\r
225                             default:\r
226                                 result += '%'.ToString() + ch;\r
227                                 break;\r
228                         }\r
229                     }\r
230                     else\r
231                     {\r
232                         result += ch;\r
233                     }\r
234                 }\r
235                 return result;\r
236             }\r
237         }\r
238 \r
239         public interface ITimer\r
240         {\r
241             int Interval { get; set; }\r
242             bool Enabled { get; set; }\r
243             event EventHandler Tick;\r
244             void Start();\r
245             void Stop();\r
246             DateTime Now { get; }\r
247         }\r
248 \r
249         public class TimerWrapper : ITimer\r
250         {\r
251             private readonly Timer _timer = new Timer();\r
252 \r
253             public int Interval\r
254             {\r
255                 get => _timer.Interval;\r
256                 set => _timer.Interval = value;\r
257             }\r
258 \r
259             public bool Enabled\r
260             {\r
261                 get => _timer.Enabled;\r
262                 set => _timer.Enabled = value;\r
263             }\r
264 \r
265             public event EventHandler Tick\r
266             {\r
267                 add => _timer.Tick += value;\r
268                 remove => _timer.Tick -= value;\r
269             }\r
270 \r
271             public void Start() => _timer.Start();\r
272 \r
273             public void Stop() => _timer.Stop();\r
274 \r
275             public DateTime Now => DateTime.Now;\r
276         }\r
277 \r
278         private class NotificationQueue\r
279         {\r
280             private readonly Action<string, string, string> _ring;\r
281             private readonly List<Notification> _queue = new List<Notification>();\r
282             private readonly ITimer _timer;\r
283             private readonly NotificationConfig _notificationConfig = new NotificationConfig();\r
284             private DateTime _lastRing;\r
285 \r
286             public NotificationQueue(Action<string, string, string> ring, ITimer timer = null)\r
287             {\r
288                 _ring = ring;\r
289                 _timer = timer ?? new TimerWrapper();\r
290                 _timer.Interval = 1000;\r
291                 _timer.Tick += TimerOnTick;\r
292             }\r
293 \r
294             private void TimerOnTick(object obj, EventArgs e)\r
295             {\r
296                 if (_queue.Count == 0)\r
297                 {\r
298                     _timer.Stop();\r
299                     return;\r
300                 }\r
301                 Ring();\r
302             }\r
303 \r
304             public void Enqueue(Notification notification)\r
305             {\r
306                 _queue.Add(notification);\r
307                 Ring();\r
308                 if (_queue.Count > 0)\r
309                     _timer.Start();\r
310             }\r
311 \r
312             public void StopRepeat(string key)\r
313             {\r
314                 _queue.RemoveAll(n => n.Key.Substring(0, 4) == key.Substring(0, 4) && n.Schedule != default);\r
315             }\r
316 \r
317             private void Ring()\r
318             {\r
319                 var now = _timer.Now;\r
320                 if (now - _lastRing < TimeSpan.FromSeconds(2))\r
321                     return;\r
322                 var notification = _queue.FirstOrDefault(n => n.Schedule.CompareTo(now) <= 0);\r
323                 if (notification == null)\r
324                     return;\r
325                 if (notification.Repeat == 0)\r
326                 {\r
327                     _queue.Remove(notification);\r
328                 }\r
329                 else\r
330                 {\r
331                     notification.Schedule = _timer.Now + TimeSpan.FromSeconds(notification.Repeat);\r
332                 }\r
333                 var message =\r
334                     _notificationConfig.GenerateMessage(notification);\r
335                 _ring(message.Title, message.Body, message.Name);\r
336                 _lastRing = now;\r
337             }\r
338         }\r
339     }\r
340 }