using System;\r
using System.Collections.Generic;\r
using System.IO;\r
-using System.Windows.Forms;\r
+using System.Linq;\r
\r
namespace KancolleSniffer\r
{\r
public class NotificationManager\r
{\r
private readonly NotificationQueue _notificationQueue;\r
- private readonly NotificationConfig _notificationConfig = new NotificationConfig();\r
\r
- public class Notification\r
+ private enum Mode\r
{\r
- public string Title;\r
- public string Body;\r
- public string Name;\r
+ Normal,\r
+ Repeat,\r
+ Cont,\r
+ Preliminary\r
}\r
\r
- public NotificationManager(Action<string, string, string> ring)\r
+ private class Notification\r
{\r
- _notificationQueue = new NotificationQueue(ring);\r
+ public string Key { get; set; }\r
+ public int Fleet { get; set; }\r
+ public string Subject { get; set; }\r
+ public int Repeat { get; set; }\r
+ public Mode Mode { get; set; }\r
+ public DateTime Schedule { get; set; }\r
}\r
\r
- public void Enqueue(string key, int fleet, string subject)\r
+ public NotificationManager(Action<string, string, string> alarm, Func<DateTime> nowFunc = null)\r
+ {\r
+ _notificationQueue = new NotificationQueue(alarm, nowFunc);\r
+ }\r
+\r
+ public void Enqueue(string key, int fleet, string subject, int repeat = 0, bool preliminary = false)\r
{\r
- _notificationConfig.LoadConfig();\r
- var notification = _notificationConfig[key];\r
_notificationQueue.Enqueue(new Notification\r
{\r
- Title = ProcessFormatString(notification.Title, fleet, subject),\r
- Body = ProcessFormatString(notification.Body, fleet, subject),\r
- Name = notification.Name\r
+ Key = key,\r
+ Fleet = fleet,\r
+ Subject = subject,\r
+ Repeat = repeat,\r
+ Mode = preliminary ? Mode.Preliminary : Mode.Normal\r
});\r
}\r
\r
- public void Enqueue(string key, string subject)\r
+ public void Enqueue(string key, string subject, int repeat = 0)\r
{\r
- Enqueue(key, 0, subject);\r
+ Enqueue(key, 0, subject, repeat);\r
}\r
\r
- private string ProcessFormatString(string format, int fleet, string subject)\r
+ public void Flash()\r
{\r
- var fn = new[] {"第一艦隊", "第二艦隊", "第三艦隊", "第四艦隊"};\r
- var result = "";\r
- var percent = false;\r
- foreach (var ch in format)\r
- {\r
- if (ch == '%')\r
- {\r
- if (percent)\r
- {\r
- percent = false;\r
- result += '%';\r
- }\r
- else\r
- {\r
- percent = true;\r
- }\r
- }\r
- else if (percent)\r
- {\r
- percent = false;\r
- switch (ch)\r
- {\r
- case 'f':\r
- result += fn[fleet];\r
- break;\r
- case 's':\r
- result += subject;\r
- break;\r
- default:\r
- result += '%'.ToString() + ch;\r
- break;\r
- }\r
- }\r
- else\r
- {\r
- result += ch;\r
- }\r
- }\r
- return result;\r
+ _notificationQueue.Flash();\r
+ }\r
+\r
+ public void StopRepeat(string key, bool cont = false)\r
+ {\r
+ _notificationQueue.StopRepeat(key, cont);\r
+ }\r
+\r
+ public void StopRepeat(string key, int fleet)\r
+ {\r
+ _notificationQueue.StopRepeat(key, fleet);\r
}\r
\r
+ public void SuspendRepeat()\r
+ {\r
+ _notificationQueue.SuspendRepeat();\r
+ }\r
+\r
+ public void ResumeRepeat()\r
+ {\r
+ _notificationQueue.ResumeRepeat();\r
+ }\r
+\r
+ public string KeyToName(string key) => NotificationConfig.KeyToName(key);\r
+\r
private class NotificationConfig\r
{\r
- private readonly Dictionary<string, Notification> _config = new Dictionary<string, Notification>();\r
+ public class Message\r
+ {\r
+ public string Title { get; set; }\r
+ public string Body { get; set; }\r
+ public string Name { get; set; }\r
+ }\r
+\r
+ private readonly Dictionary<string, Message> _config = new Dictionary<string, Message>();\r
\r
- private readonly Dictionary<string, Notification> _default = new Dictionary<string, Notification>\r
+ private readonly Dictionary<string, Message> _default = new Dictionary<string, Message>\r
{\r
{\r
- "遠征終了", new Notification\r
+ "遠征終了", new Message\r
{\r
Title = "遠征が終わりました",\r
- Body = "%s",\r
- Name = "遠征終了"\r
+ Body = "%f艦隊 %s"\r
}\r
},\r
{\r
- "入渠終了", new Notification\r
+ "入渠終了", new Message\r
{\r
Title = "入渠が終わりました",\r
- Body = "%s",\r
- Name = "入渠終了"\r
+ Body = "%fドック %s"\r
}\r
},\r
{\r
- "建造完了", new Notification\r
+ "建造完了", new Message\r
{\r
Title = "建造が終わりました",\r
- Body = "%s",\r
- Name = "建造完了"\r
+ Body = "%fドック"\r
}\r
},\r
{\r
- "艦娘数超過", new Notification\r
+ "艦娘数超過", new Message\r
{\r
Title = "艦娘が多すぎます",\r
- Body = "%s",\r
- Name = "艦娘数超過"\r
+ Body = "%s"\r
}\r
},\r
{\r
- "装備数超過", new Notification\r
+ "装備数超過", new Message\r
{\r
Title = "装備が多すぎます",\r
- Body = "%s",\r
- Name = "装備数超過"\r
+ Body = "%s"\r
}\r
},\r
{\r
- "大破警告", new Notification\r
+ "大破警告", new Message\r
{\r
Title = "大破した艦娘がいます",\r
- Body = "%s",\r
- Name = "大破警告"\r
+ Body = "%s"\r
}\r
},\r
{\r
- "泊地修理20分経過", new Notification\r
+ "泊地修理20分経過", new Message\r
{\r
- Title = "泊地修理 %f",\r
- Body = "20分経過しました。",\r
- Name = "泊地修理20分経過"\r
+ Title = "泊地修理 %f艦隊",\r
+ Body = "20分経過しました。"\r
}\r
},\r
{\r
- "泊地修理進行", new Notification\r
+ "泊地修理進行", new Message\r
{\r
- Title = "泊地修理 %f",\r
- Body = "修理進行:%s",\r
- Name = "泊地修理進行"\r
+ Title = "泊地修理 %f艦隊",\r
+ Body = "修理進行:%s"\r
}\r
},\r
{\r
- "泊地修理完了", new Notification\r
+ "泊地修理完了", new Message\r
{\r
- Title = "泊地修理 %f",\r
- Body = "修理完了:%s",\r
- Name = "泊地修理完了"\r
+ Title = "泊地修理 %f艦隊",\r
+ Body = "修理完了:%s"\r
}\r
},\r
{\r
- "疲労回復40", new Notification\r
+ "疲労回復40", new Message\r
{\r
Title = "疲労が回復しました",\r
- Body = "%f 残り9分",\r
- Name = "疲労回復"\r
+ Body = "%f艦隊 残り9分"\r
}\r
},\r
{\r
- "疲労回復49", new Notification\r
+ "疲労回復49", new Message\r
{\r
Title = "疲労が回復しました",\r
- Body = "%f",\r
- Name = "疲労回復"\r
+ Body = "%f艦隊"\r
}\r
}\r
};\r
\r
- public Notification this[string key] => _config.TryGetValue(key, out Notification value) ? value : _default[key];\r
+ public static string KeyToName(string key) => key.StartsWith("疲労回復") ? key.Substring(0, 4) : key;\r
\r
- public void LoadConfig()\r
+ private void LoadConfig()\r
{\r
const string fileName = "notification.json";\r
_config.Clear();\r
{\r
if (!_default.ContainsKey(entry.key))\r
continue;\r
- _config[entry.key] = new Notification\r
+ _config[entry.key] = new Message\r
{\r
Title = entry.title,\r
- Body = entry.body,\r
- Name = _default[entry.key].Name\r
+ Body = entry.body\r
};\r
}\r
}\r
throw new Exception($"{fileName}に誤りがあります。: ${ex.Message}", ex);\r
}\r
}\r
+\r
+ public Message GenerateMessage(Notification notification)\r
+ {\r
+ LoadConfig();\r
+ var format = _config.TryGetValue(notification.Key, out Message value)\r
+ ? value\r
+ : _default[notification.Key];\r
+ var prefix = new[] {"", "[リピート] ", "[継続] ", "[予告] "}[(int)notification.Mode];\r
+ return new Message\r
+ {\r
+ Title = prefix + ProcessFormatString(format.Title, notification.Fleet, notification.Subject),\r
+ Body = ProcessFormatString(format.Body, notification.Fleet, notification.Subject),\r
+ Name = KeyToName(notification.Key)\r
+ };\r
+ }\r
+\r
+ private string ProcessFormatString(string format, int fleet, string subject)\r
+ {\r
+ var fn = new[] {"第一", "第二", "第三", "第四"};\r
+ var result = "";\r
+ var percent = false;\r
+ foreach (var ch in format)\r
+ {\r
+ if (ch == '%')\r
+ {\r
+ if (percent)\r
+ {\r
+ percent = false;\r
+ result += '%';\r
+ }\r
+ else\r
+ {\r
+ percent = true;\r
+ }\r
+ }\r
+ else if (percent)\r
+ {\r
+ percent = false;\r
+ switch (ch)\r
+ {\r
+ case 'f':\r
+ result += fn[fleet];\r
+ break;\r
+ case 's':\r
+ result += subject;\r
+ break;\r
+ default:\r
+ result += '%'.ToString() + ch;\r
+ break;\r
+ }\r
+ }\r
+ else\r
+ {\r
+ result += ch;\r
+ }\r
+ }\r
+ return result;\r
+ }\r
}\r
\r
private class NotificationQueue\r
{\r
- private readonly Action<string, string, string> _ring;\r
- private readonly Queue<Notification> _queue = new Queue<Notification>();\r
- private readonly Timer _timer = new Timer {Interval = 2000};\r
+ private readonly Action<string, string, string> _alarm;\r
+ private readonly List<Notification> _queue = new List<Notification>();\r
+ private readonly Func<DateTime> _nowFunc = () => DateTime.Now;\r
+ private readonly NotificationConfig _notificationConfig = new NotificationConfig();\r
+ private DateTime _lastAlarm;\r
+ private bool _suspend;\r
\r
- public NotificationQueue(Action<string, string, string> ring)\r
+ public NotificationQueue(Action<string, string, string> alarm, Func<DateTime> nowFunc = null)\r
{\r
- _ring = ring;\r
- _timer.Tick += TimerOnTick;\r
+ _alarm = alarm;\r
+ if (nowFunc != null)\r
+ _nowFunc = nowFunc;\r
}\r
\r
- private void TimerOnTick(object obj, EventArgs e)\r
+ public void Enqueue(Notification notification)\r
{\r
- if (_queue.Count == 0)\r
- {\r
- _timer.Stop();\r
- return;\r
- }\r
- var notification = _queue.Dequeue();\r
- _ring(notification.Title, notification.Body, notification.Name);\r
+ _queue.Add(notification);\r
}\r
\r
- public void Enqueue(Notification notification)\r
+ public void Flash()\r
{\r
- if (_timer.Enabled)\r
+ Alarm();\r
+ }\r
+\r
+ public void StopRepeat(string key, bool cont = false)\r
+ {\r
+ if (!cont)\r
{\r
- _queue.Enqueue(notification);\r
+ _queue.RemoveAll(n => IsMatch(n, key));\r
}\r
else\r
{\r
- _ring(notification.Title, notification.Body, notification.Name);\r
- _timer.Start();\r
+ foreach (var n in _queue.Where(n => IsMatch(n, key)))\r
+ {\r
+ n.Subject = "";\r
+ n.Mode = Mode.Cont;\r
+ }\r
+ }\r
+ }\r
+\r
+ public void StopRepeat(string key, int fleet)\r
+ {\r
+ _queue.RemoveAll(n => IsMatch(n, key) && n.Fleet == fleet);\r
+ }\r
+\r
+ private bool IsMatch(Notification n, string key) =>\r
+ n.Key.Substring(0, 4) == key.Substring(0, 4) && n.Schedule != default;\r
+\r
+ public void SuspendRepeat()\r
+ {\r
+ _suspend = true;\r
+ }\r
+\r
+ public void ResumeRepeat()\r
+ {\r
+ _suspend = false;\r
+ }\r
+\r
+ private void Alarm()\r
+ {\r
+ var now = _nowFunc();\r
+ if (now - _lastAlarm < TimeSpan.FromSeconds(2))\r
+ return;\r
+ var first = _queue.FirstOrDefault(n => n.Schedule.CompareTo(now) <= 0 &&\r
+ !(_suspend && n.Schedule != default));\r
+ if (first == null)\r
+ return;\r
+ var message = _notificationConfig.GenerateMessage(first);\r
+ var similar = _queue.Where(n =>\r
+ _notificationConfig.GenerateMessage(n).Name == message.Name && n.Schedule.CompareTo(now) <= 0)\r
+ .ToArray();\r
+ var body = string.Join("\r\n", similar.Select(n => _notificationConfig.GenerateMessage(n).Body));\r
+ foreach (var n in similar)\r
+ {\r
+ if (n.Repeat == 0)\r
+ {\r
+ _queue.Remove(n);\r
+ }\r
+ else\r
+ {\r
+ n.Schedule = now + TimeSpan.FromSeconds(n.Repeat);\r
+ if (n.Mode == Mode.Normal)\r
+ n.Mode = Mode.Repeat;\r
+ }\r
}\r
+ _alarm(message.Title, body, message.Name);\r
+ _lastAlarm = now;\r
}\r
}\r
}\r