--- /dev/null
+// Copyright (C) 2017 Kazuhiro Fujieda <fujieda@users.osdn.me>\r
+//\r
+// Licensed under the Apache License, Version 2.0 (the "License");\r
+// you may not use this file except in compliance with the License.\r
+// You may obtain a copy of the License at\r
+//\r
+// http://www.apache.org/licenses/LICENSE-2.0\r
+//\r
+// Unless required by applicable law or agreed to in writing, software\r
+// distributed under the License is distributed on an "AS IS" BASIS,\r
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+// See the License for the specific language governing permissions and\r
+// limitations under the License.\r
+\r
+using System;\r
+using ExpressionToCodeLib;\r
+using Microsoft.VisualStudio.TestTools.UnitTesting;\r
+\r
+namespace KancolleSniffer.Test\r
+{\r
+ [TestClass]\r
+ public class NotificationManagerTest\r
+ {\r
+ private class MockTimer : NotificationManager.ITimer\r
+ {\r
+ private int _elapsed;\r
+ private bool _enabled;\r
+\r
+ public int Interval { get; set; }\r
+\r
+ public bool Enabled\r
+ {\r
+ get => _enabled;\r
+ set\r
+ {\r
+ _enabled = value;\r
+ _elapsed = 0;\r
+ }\r
+ }\r
+\r
+ public event EventHandler Tick;\r
+\r
+ public void Start()\r
+ {\r
+ Enabled = true;\r
+ }\r
+\r
+ public void Stop()\r
+ {\r
+ Enabled = false;\r
+ }\r
+\r
+ public void ElapseTime(int millis)\r
+ {\r
+ if (!Enabled)\r
+ return;\r
+ var after = _elapsed + millis;\r
+ for (var n = _elapsed / Interval; n < after / Interval; n++)\r
+ {\r
+ Tick?.Invoke(this, EventArgs.Empty);\r
+ }\r
+ _elapsed = after;\r
+ }\r
+ }\r
+\r
+ private class Message\r
+ {\r
+ public string Title { private get; set; }\r
+ public string Body { private get; set; }\r
+ public string Name { private get; set; }\r
+\r
+ public bool Equals(Message other) =>\r
+ other != null && Title == other.Title && Body == other.Body && Name == other.Name;\r
+ }\r
+\r
+ /// <summary>\r
+ /// 単発\r
+ /// </summary>\r
+ [TestMethod]\r
+ public void SingleNotification()\r
+ {\r
+ var timer = new MockTimer();\r
+ Message result = null;\r
+ var manager =\r
+ new NotificationManager((t, b, n) => { result = new Message {Title = t, Body = b, Name = n}; }, timer);\r
+ manager.Enqueue("遠征終了", "防空射撃演習");\r
+ PAssert.That(() => new Message {Title = "遠征が終わりました", Body = "防空射撃演習", Name = "遠征終了"}.Equals(result));\r
+ }\r
+\r
+ /// <summary>\r
+ /// 連続した通知の間隔を二秒空ける\r
+ /// </summary>\r
+ [TestMethod]\r
+ public void TwoNotificationAtSameTime()\r
+ {\r
+ var timer = new MockTimer();\r
+ Message result = null;\r
+ var manager =\r
+ new NotificationManager((t, b, n) => { result = new Message {Title = t, Body = b, Name = n}; }, timer);\r
+ manager.Enqueue("疲労回復40", 0, "cond40");\r
+ manager.Enqueue("疲労回復49", 1, "cond49");\r
+ PAssert.That(() => new Message {Title = "疲労が回復しました", Body = "第一艦隊 残り9分", Name = "疲労回復"}.Equals(result));\r
+ result = null;\r
+ timer.ElapseTime(1000);\r
+ PAssert.That(() => result == null);\r
+ timer.ElapseTime(1000);\r
+ PAssert.That(() => new Message {Title = "疲労が回復しました", Body = "第二艦隊", Name = "疲労回復"}.Equals(result));\r
+ timer.ElapseTime(2000);\r
+ PAssert.That(() => !timer.Enabled);\r
+ }\r
+\r
+ /// <summary>\r
+ /// 一つ目の通知の一秒後に投入された通知は一秒ずらす\r
+ /// </summary>\r
+ [TestMethod]\r
+ public void TwoNotification1SecDelay()\r
+ {\r
+ var timer = new MockTimer();\r
+ Message result = null;\r
+ var manager =\r
+ new NotificationManager((t, b, n) => { result = new Message {Title = t, Body = b, Name = n}; }, timer);\r
+ manager.Enqueue("建造完了", "第一ドック");\r
+ PAssert.That(() => new Message {Title = "建造が終わりました", Body = "第一ドック", Name = "建造完了"}.Equals(result));\r
+ timer.ElapseTime(1000);\r
+ manager.Enqueue("建造完了", "第二ドック");\r
+ timer.ElapseTime(1000);\r
+ PAssert.That(() => new Message {Title = "建造が終わりました", Body = "第二ドック", Name = "建造完了"}.Equals(result));\r
+ }\r
+ }\r
+}
\ No newline at end of file
public class NotificationManager\r
{\r
private readonly NotificationQueue _notificationQueue;\r
- private readonly NotificationConfig _notificationConfig = new NotificationConfig();\r
\r
- public class Notification\r
+ private class Notification\r
{\r
- public string Title;\r
- public string Body;\r
- public string Name;\r
+ public string Key { get; set; }\r
+ public int Fleet { get; set; }\r
+ public string Subject { get; set; }\r
}\r
\r
- public NotificationManager(Action<string, string, string> ring)\r
+ public NotificationManager(Action<string, string, string> ring, ITimer timer = null)\r
{\r
- _notificationQueue = new NotificationQueue(ring);\r
+\r
+ _notificationQueue = new NotificationQueue(ring, timer);\r
}\r
\r
public void Enqueue(string key, int fleet, string subject)\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
});\r
}\r
\r
Enqueue(key, 0, subject);\r
}\r
\r
- private string ProcessFormatString(string format, int fleet, string subject)\r
+ private class NotificationConfig\r
{\r
- var fn = new[] {"第一艦隊", "第二艦隊", "第三艦隊", "第四艦隊"};\r
- var result = "";\r
- var percent = false;\r
- foreach (var ch in format)\r
+ public class Message\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
+ public string Title { get; set; }\r
+ public string Body { get; set; }\r
+ public string Name { get; set; }\r
}\r
- return result;\r
- }\r
\r
- private class NotificationConfig\r
- {\r
- private readonly Dictionary<string, Notification> _config = new Dictionary<string, Notification>();\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 = "%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
- "艦娘数超過", 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
+ Body = "20分経過しました。"\r
}\r
},\r
{\r
- "泊地修理進行", new Notification\r
+ "泊地修理進行", new Message\r
{\r
Title = "泊地修理 %f",\r
- Body = "修理進行:%s",\r
- Name = "泊地修理進行"\r
+ Body = "修理進行:%s"\r
}\r
},\r
{\r
- "泊地修理完了", new Notification\r
+ "泊地修理完了", new Message\r
{\r
Title = "泊地修理 %f",\r
- Body = "修理完了:%s",\r
- Name = "泊地修理完了"\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) ? value : _default[notification.Key];\r
+ return new Message\r
+ {\r
+ Title = 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
+ public interface ITimer\r
+ {\r
+ int Interval { get; set; }\r
+ bool Enabled { get; set; }\r
+ event EventHandler Tick;\r
+ void Start();\r
+ void Stop();\r
+ }\r
+\r
+ public class TimerWrapper : ITimer\r
+ {\r
+ private readonly Timer _timer = new Timer();\r
+\r
+ public int Interval\r
+ {\r
+ get => _timer.Interval;\r
+ set => _timer.Interval = value;\r
+ }\r
+\r
+ public bool Enabled\r
+ {\r
+ get => _timer.Enabled;\r
+ set => _timer.Enabled = value;\r
+ }\r
+\r
+ public event EventHandler Tick\r
+ {\r
+ add => _timer.Tick += value;\r
+ remove => _timer.Tick -= value;\r
+ }\r
+\r
+ public void Start() => _timer.Start();\r
+\r
+ public void Stop() => _timer.Stop();\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 ITimer _timer;\r
+ private readonly NotificationConfig _notificationConfig = new NotificationConfig();\r
\r
- public NotificationQueue(Action<string, string, string> ring)\r
+ public NotificationQueue(Action<string, string, string> ring, ITimer timer)\r
{\r
_ring = ring;\r
+ _timer = timer ?? new TimerWrapper();\r
+ _timer.Interval = 2000;\r
_timer.Tick += TimerOnTick;\r
}\r
\r
return;\r
}\r
var notification = _queue.Dequeue();\r
- _ring(notification.Title, notification.Body, notification.Name);\r
+ Ring(notification);\r
}\r
\r
public void Enqueue(Notification notification)\r
}\r
else\r
{\r
- _ring(notification.Title, notification.Body, notification.Name);\r
+ Ring(notification);\r
_timer.Start();\r
}\r
}\r
+\r
+ private void Ring(Notification notification)\r
+ {\r
+ var message =\r
+ _notificationConfig.GenerateMessage(notification);\r
+ _ring(message.Title, message.Body, message.Name);\r
+ }\r
}\r
}\r
}
\ No newline at end of file