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