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