OSDN Git Service

NotificationManagerのリファクタリング
authorKazuhiro Fujieda <fujieda@users.osdn.me>
Fri, 3 Nov 2017 10:00:09 +0000 (19:00 +0900)
committerKazuhiro Fujieda <fujieda@users.osdn.me>
Sat, 27 Jan 2018 08:33:48 +0000 (17:33 +0900)
KancolleSniffer.Test/KancolleSniffer.Test.csproj
KancolleSniffer.Test/NotificationManagerTest.cs [new file with mode: 0644]
KancolleSniffer/NotificationManager.cs

index 5e389dd..8414e36 100644 (file)
@@ -78,6 +78,7 @@
     <Compile Include="BattleTest.cs" />\r
     <Compile Include="ErrorLogTest.cs" />\r
     <Compile Include="JsonTest.cs" />\r
+    <Compile Include="NotificationManagerTest.cs" />\r
     <Compile Include="ShipLabelTest.cs" />\r
     <Compile Include="SnifferTest.cs" />\r
     <Compile Include="Properties\AssemblyInfo.cs" />\r
diff --git a/KancolleSniffer.Test/NotificationManagerTest.cs b/KancolleSniffer.Test/NotificationManagerTest.cs
new file mode 100644 (file)
index 0000000..b685f31
--- /dev/null
@@ -0,0 +1,130 @@
+// 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
index ddbcb85..708c130 100644 (file)
@@ -22,29 +22,27 @@ namespace KancolleSniffer
     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
@@ -53,148 +51,101 @@ namespace KancolleSniffer
             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
@@ -205,11 +156,10 @@ namespace KancolleSniffer
                     {\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
@@ -221,17 +171,111 @@ namespace KancolleSniffer
                     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
@@ -243,7 +287,7 @@ namespace KancolleSniffer
                     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
@@ -254,10 +298,17 @@ namespace KancolleSniffer
                 }\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