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