OSDN Git Service

先制対潜の判定を最新の検証結果に合わせる
[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 KancolleSniffer.Util;\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 enum Mode\r
28         {\r
29             Normal,\r
30             Repeat,\r
31             Cont,\r
32             Preliminary\r
33         }\r
34 \r
35         private class Notification\r
36         {\r
37             public string Key { get; set; }\r
38             public int Fleet { get; set; }\r
39             public string Subject { get; set; }\r
40             public int Repeat { get; set; }\r
41             public Mode Mode { get; set; }\r
42             public DateTime Schedule { get; set; }\r
43         }\r
44 \r
45         public NotificationManager(Action<string, string, string> alarm, Func<DateTime> nowFunc = null)\r
46         {\r
47             _notificationQueue = new NotificationQueue(alarm, nowFunc);\r
48         }\r
49 \r
50         public void Enqueue(string key, int fleet, string subject, int repeat = 0, bool preliminary = false)\r
51         {\r
52             _notificationQueue.Enqueue(new Notification\r
53             {\r
54                 Key = key,\r
55                 Fleet = fleet,\r
56                 Subject = subject,\r
57                 Repeat = repeat,\r
58                 Mode = preliminary ? Mode.Preliminary : Mode.Normal\r
59             });\r
60         }\r
61 \r
62         public void Enqueue(string key, string subject, int repeat = 0)\r
63         {\r
64             Enqueue(key, 0, subject, repeat);\r
65         }\r
66 \r
67         public void Flash()\r
68         {\r
69             _notificationQueue.Flash();\r
70         }\r
71 \r
72         public void StopRepeat(string key, bool cont = false)\r
73         {\r
74             _notificationQueue.StopRepeat(key, cont);\r
75         }\r
76 \r
77         public void StopRepeat(string key, int fleet)\r
78         {\r
79             _notificationQueue.StopRepeat(key, fleet);\r
80         }\r
81 \r
82         public void StopRepeat(string key, string subject)\r
83         {\r
84             _notificationQueue.StopRepeat(key, subject);\r
85         }\r
86 \r
87         public void StopAllRepeat()\r
88         {\r
89             _notificationQueue.StopAllRepeat();\r
90         }\r
91 \r
92         public void SuspendRepeat(string exception = "")\r
93         {\r
94             _notificationQueue.SuspendRepeat(exception);\r
95         }\r
96 \r
97         public void ResumeRepeat()\r
98         {\r
99             _notificationQueue.ResumeRepeat();\r
100         }\r
101 \r
102         public string KeyToName(string key) => NotificationConfig.KeyToName(key);\r
103 \r
104         private class NotificationConfig\r
105         {\r
106             public class Message\r
107             {\r
108                 public string Title { get; set; }\r
109                 public string Body { get; set; }\r
110                 public string Name { get; set; }\r
111             }\r
112 \r
113             private readonly Dictionary<string, Message> _config = new Dictionary<string, Message>();\r
114 \r
115             private readonly Dictionary<string, Message> _default = new Dictionary<string, Message>\r
116             {\r
117                 {\r
118                     "遠征終了", new Message\r
119                     {\r
120                         Title = "遠征が終わりました",\r
121                         Body = "%f艦隊 %s"\r
122                     }\r
123                 },\r
124                 {\r
125                     "入渠終了", new Message\r
126                     {\r
127                         Title = "入渠が終わりました",\r
128                         Body = "%fドック %s"\r
129                     }\r
130                 },\r
131                 {\r
132                     "建造完了", new Message\r
133                     {\r
134                         Title = "建造が終わりました",\r
135                         Body = "%fドック"\r
136                     }\r
137                 },\r
138                 {\r
139                     "艦娘数超過", new Message\r
140                     {\r
141                         Title = "艦娘が多すぎます",\r
142                         Body = "%s"\r
143                     }\r
144                 },\r
145                 {\r
146                     "装備数超過", new Message\r
147                     {\r
148                         Title = "装備が多すぎます",\r
149                         Body = "%s"\r
150                     }\r
151                 },\r
152                 {\r
153                     "大破警告", new Message\r
154                     {\r
155                         Title = "大破した艦娘がいます",\r
156                         Body = "%s"\r
157                     }\r
158                 },\r
159                 {\r
160                     "泊地修理20分経過", new Message\r
161                     {\r
162                         Title = "泊地修理 %f艦隊",\r
163                         Body = "20分経過しました。"\r
164                     }\r
165                 },\r
166                 {\r
167                     "泊地修理進行", new Message\r
168                     {\r
169                         Title = "泊地修理 %f艦隊",\r
170                         Body = "修理進行:%s"\r
171                     }\r
172                 },\r
173                 {\r
174                     "泊地修理完了", new Message\r
175                     {\r
176                         Title = "泊地修理 %f艦隊",\r
177                         Body = "修理完了:%s"\r
178                     }\r
179                 },\r
180                 {\r
181                     "疲労回復40", new Message\r
182                     {\r
183                         Title = "疲労が回復しました",\r
184                         Body = "%f艦隊 残り9分"\r
185                     }\r
186                 },\r
187                 {\r
188                     "疲労回復49", new Message\r
189                     {\r
190                         Title = "疲労が回復しました",\r
191                         Body = "%f艦隊"\r
192                     }\r
193                 },\r
194                 {\r
195                     "任務達成", new Message\r
196                     {\r
197                         Title = "任務を達成しました",\r
198                         Body = "%s"\r
199                     }\r
200                 }\r
201             };\r
202 \r
203             public static string KeyToName(string key) => key.StartsWith("疲労回復") ? key.Substring(0, 4) : key;\r
204 \r
205             private void LoadConfig()\r
206             {\r
207                 const string fileName = "notification.json";\r
208                 _config.Clear();\r
209                 try\r
210                 {\r
211                     dynamic config = JsonParser.Parse(File.ReadAllText(fileName));\r
212                     foreach (var entry in config)\r
213                     {\r
214                         if (!_default.ContainsKey(entry.key))\r
215                             continue;\r
216                         _config[entry.key] = new Message\r
217                         {\r
218                             Title = entry.title,\r
219                             Body = entry.body\r
220                         };\r
221                     }\r
222                 }\r
223                 catch (FileNotFoundException)\r
224                 {\r
225                 }\r
226                 catch (Exception ex)\r
227                 {\r
228                     throw new Exception($"{fileName}に誤りがあります。: ${ex.Message}", ex);\r
229                 }\r
230             }\r
231 \r
232             public Message GenerateMessage(Notification notification)\r
233             {\r
234                 LoadConfig();\r
235                 var format = _config.TryGetValue(notification.Key, out Message value)\r
236                     ? value\r
237                     : _default[notification.Key];\r
238                 var prefix = new[] {"", "[リピート] ", "[継続] ", "[予告] "}[(int)notification.Mode];\r
239                 return new Message\r
240                 {\r
241                     Title = prefix + ProcessFormatString(format.Title, notification.Fleet, notification.Subject),\r
242                     Body = ProcessFormatString(format.Body, notification.Fleet, notification.Subject),\r
243                     Name = KeyToName(notification.Key)\r
244                 };\r
245             }\r
246 \r
247             private string ProcessFormatString(string format, int fleet, string subject)\r
248             {\r
249                 var fn = new[] {"第一", "第二", "第三", "第四"};\r
250                 var result = "";\r
251                 var percent = false;\r
252                 foreach (var ch in format)\r
253                 {\r
254                     if (ch == '%')\r
255                     {\r
256                         if (percent)\r
257                         {\r
258                             percent = false;\r
259                             result += '%';\r
260                         }\r
261                         else\r
262                         {\r
263                             percent = true;\r
264                         }\r
265                     }\r
266                     else if (percent)\r
267                     {\r
268                         percent = false;\r
269                         switch (ch)\r
270                         {\r
271                             case 'f':\r
272                                 result += fn[fleet];\r
273                                 break;\r
274                             case 's':\r
275                                 result += subject;\r
276                                 break;\r
277                             default:\r
278                                 result += '%'.ToString() + ch;\r
279                                 break;\r
280                         }\r
281                     }\r
282                     else\r
283                     {\r
284                         result += ch;\r
285                     }\r
286                 }\r
287                 return result;\r
288             }\r
289         }\r
290 \r
291         private class NotificationQueue\r
292         {\r
293             private readonly Action<string, string, string> _alarm;\r
294             private readonly List<Notification> _queue = new List<Notification>();\r
295             private readonly Func<DateTime> _nowFunc = () => DateTime.Now;\r
296             private readonly NotificationConfig _notificationConfig = new NotificationConfig();\r
297             private DateTime _lastAlarm;\r
298             private bool _suspend;\r
299             private string _suspendException;\r
300 \r
301             public NotificationQueue(Action<string, string, string> alarm, Func<DateTime> nowFunc = null)\r
302             {\r
303                 _alarm = alarm;\r
304                 if (nowFunc != null)\r
305                     _nowFunc = nowFunc;\r
306             }\r
307 \r
308             public void Enqueue(Notification notification)\r
309             {\r
310                 _queue.Add(notification);\r
311             }\r
312 \r
313             public void Flash()\r
314             {\r
315                 Alarm();\r
316             }\r
317 \r
318             public void StopRepeat(string key, bool cont = false)\r
319             {\r
320                 if (!cont)\r
321                 {\r
322                     _queue.RemoveAll(n => IsMatch(n, key));\r
323                 }\r
324                 else\r
325                 {\r
326                     foreach (var n in _queue.Where(n => IsMatch(n, key)))\r
327                     {\r
328                         n.Subject = "";\r
329                         n.Mode = Mode.Cont;\r
330                     }\r
331                 }\r
332             }\r
333 \r
334             public void StopRepeat(string key, int fleet)\r
335             {\r
336                 _queue.RemoveAll(n => IsMatch(n, key) && n.Fleet == fleet);\r
337             }\r
338 \r
339             public void StopRepeat(string key, string subject)\r
340             {\r
341                 _queue.RemoveAll(n => IsMatch(n, key) && n.Subject == subject);\r
342             }\r
343 \r
344             private bool IsMatch(Notification n, string key) =>\r
345                 n.Key.Substring(0, 4) == key.Substring(0, 4) && n.Schedule != default;\r
346 \r
347             public void StopAllRepeat()\r
348             {\r
349                 _queue.RemoveAll(n => n.Schedule != default);\r
350             }\r
351 \r
352             public void SuspendRepeat(string exception = null)\r
353             {\r
354                 _suspend = true;\r
355                 _suspendException = exception;\r
356             }\r
357 \r
358             public void ResumeRepeat()\r
359             {\r
360                 _suspend = false;\r
361             }\r
362 \r
363             private void Alarm()\r
364             {\r
365                 var now = _nowFunc();\r
366                 if (now - _lastAlarm < TimeSpan.FromSeconds(2))\r
367                     return;\r
368                 var first = _queue.FirstOrDefault(n => n.Schedule.CompareTo(now) <= 0 &&\r
369                                                        !(n.Schedule != default && _suspend && n.Key != _suspendException));\r
370                 if (first == null)\r
371                     return;\r
372                 var message = _notificationConfig.GenerateMessage(first);\r
373                 var similar = _queue.Where(n =>\r
374                         _notificationConfig.GenerateMessage(n).Name == message.Name && n.Schedule.CompareTo(now) <= 0)\r
375                     .ToArray();\r
376                 var body = string.Join("\r\n", similar.Select(n => _notificationConfig.GenerateMessage(n).Body));\r
377                 foreach (var n in similar)\r
378                 {\r
379                     if (n.Repeat == 0)\r
380                     {\r
381                         _queue.Remove(n);\r
382                     }\r
383                     else\r
384                     {\r
385                         n.Schedule = now + TimeSpan.FromSeconds(n.Repeat);\r
386                         if (n.Mode == Mode.Normal)\r
387                             n.Mode = Mode.Repeat;\r
388                     }\r
389                 }\r
390                 _alarm(message.Title, body, message.Name);\r
391                 _lastAlarm = now;\r
392             }\r
393         }\r
394     }\r
395 }