OSDN Git Service

装備のTPをTP.csvから読み込む
[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()\r
82         {\r
83             _notificationQueue.SuspendRepeat();\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 \r
282             public NotificationQueue(Action<string, string, string> alarm, Func<DateTime> nowFunc = null)\r
283             {\r
284                 _alarm = alarm;\r
285                 if (nowFunc != null)\r
286                     _nowFunc = nowFunc;\r
287             }\r
288 \r
289             public void Enqueue(Notification notification)\r
290             {\r
291                 _queue.Add(notification);\r
292             }\r
293 \r
294             public void Flash()\r
295             {\r
296                 Alarm();\r
297             }\r
298 \r
299             public void StopRepeat(string key, bool cont = false)\r
300             {\r
301                 if (!cont)\r
302                 {\r
303                     _queue.RemoveAll(n => IsMatch(n, key));\r
304                 }\r
305                 else\r
306                 {\r
307                     foreach (var n in _queue.Where(n => IsMatch(n, key)))\r
308                     {\r
309                         n.Subject = "";\r
310                         n.Mode = Mode.Cont;\r
311                     }\r
312                 }\r
313             }\r
314 \r
315             public void StopRepeat(string key, int fleet)\r
316             {\r
317                 _queue.RemoveAll(n => IsMatch(n, key) && n.Fleet == fleet);\r
318             }\r
319 \r
320             private bool IsMatch(Notification n, string key) =>\r
321                 n.Key.Substring(0, 4) == key.Substring(0, 4) && n.Schedule != default;\r
322 \r
323             public void SuspendRepeat()\r
324             {\r
325                 _suspend = true;\r
326             }\r
327 \r
328             public void ResumeRepeat()\r
329             {\r
330                 _suspend = false;\r
331             }\r
332 \r
333             private void Alarm()\r
334             {\r
335                 var now = _nowFunc();\r
336                 if (now - _lastAlarm < TimeSpan.FromSeconds(2))\r
337                     return;\r
338                 var first = _queue.FirstOrDefault(n => n.Schedule.CompareTo(now) <= 0 &&\r
339                                                        !(_suspend && n.Schedule != default));\r
340                 if (first == null)\r
341                     return;\r
342                 var message = _notificationConfig.GenerateMessage(first);\r
343                 var similar = _queue.Where(n =>\r
344                         _notificationConfig.GenerateMessage(n).Name == message.Name && n.Schedule.CompareTo(now) <= 0)\r
345                     .ToArray();\r
346                 var body = string.Join("\r\n", similar.Select(n => _notificationConfig.GenerateMessage(n).Body));\r
347                 foreach (var n in similar)\r
348                 {\r
349                     if (n.Repeat == 0)\r
350                     {\r
351                         _queue.Remove(n);\r
352                     }\r
353                     else\r
354                     {\r
355                         n.Schedule = now + TimeSpan.FromSeconds(n.Repeat);\r
356                         if (n.Mode == Mode.Normal)\r
357                             n.Mode = Mode.Repeat;\r
358                     }\r
359                 }\r
360                 _alarm(message.Title, body, message.Name);\r
361                 _lastAlarm = now;\r
362             }\r
363         }\r
364     }\r
365 }