OSDN Git Service

トラックとリンガのIPアドレスの変更に対応する
[kancollesniffer/KancolleSniffer.git] / KancolleSniffer / LogServer.cs
index 509ce00..20df042 100644 (file)
 // Copyright (C) 2014, 2015 Kazuhiro Fujieda <fujieda@users.osdn.me>\r
 //\r
-// This program is part of KancolleSniffer.\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
-// KancolleSniffer is free software: you can redistribute it and/or modify\r
-// it under the terms of the GNU General Public License as published by\r
-// the Free Software Foundation, either version 3 of the License, or\r
-// (at your option) any later version.\r
+//    http://www.apache.org/licenses/LICENSE-2.0\r
 //\r
-// This program is distributed in the hope that it will be useful,\r
-// but WITHOUT ANY WARRANTY; without even the implied warranty of\r
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r
-// GNU General Public License for more details.\r
-//\r
-// You should have received a copy of the GNU General Public License\r
-// along with this program; if not, see <http://www.gnu.org/licenses/>.\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 System.Collections.Generic;\r
 using System.Globalization;\r
 using System.IO;\r
 using System.Linq;\r
-using System.Net;\r
 using System.Net.Sockets;\r
 using System.Text;\r
-using System.Threading;\r
-using System.Web;\r
-using System.Windows.Forms;\r
+using System.Text.RegularExpressions;\r
 \r
 namespace KancolleSniffer\r
 {\r
     public class LogServer\r
     {\r
-        private readonly TcpListener _listener;\r
-        private readonly string _indexDir = Path.GetDirectoryName(Application.ExecutablePath);\r
-        private string _outputDir = Path.GetDirectoryName(Application.ExecutablePath);\r
-\r
-        public int Port { get; private set; }\r
+        private static readonly string IndexDir = AppDomain.CurrentDomain.BaseDirectory;\r
+        private static string _outputDir = AppDomain.CurrentDomain.BaseDirectory;\r
 \r
-        public bool IsListening { get; private set; }\r
-\r
-        public string OutputDir\r
+        public static string OutputDir\r
         {\r
-            set { _outputDir = value; }\r
+            set => _outputDir = value;\r
         }\r
 \r
-        public LogServer(int port)\r
-        {\r
-            _listener = new TcpListener(IPAddress.Loopback, port);\r
-        }\r
+        public static MaterialCount[] MaterialHistory { private get; set; }\r
 \r
-        public void Start()\r
+        public static void Process(Socket client, string requestLine)\r
         {\r
-            _listener.Start();\r
-            Port = ((IPEndPoint)_listener.LocalEndpoint).Port;\r
-            IsListening = true;\r
-            new Thread(Listen).Start();\r
-        }\r
+            var from = DateTime.MinValue;\r
+            var to = DateTime.MaxValue;\r
+            var timestamp = false;\r
 \r
-        private void Listen()\r
-        {\r
-            try\r
+            var request = requestLine.Split(' ');\r
+            if (request.Length != 3)\r
             {\r
-                while (true)\r
-                {\r
-                    var socket = _listener.AcceptSocket();\r
-                    new Thread(Process).Start(socket);\r
-                }\r
+                SendError(client, "400 Bad Request");\r
+                return;\r
             }\r
-            catch (SocketException)\r
+            if (!request[0].StartsWith("GET", StringComparison.OrdinalIgnoreCase))\r
             {\r
+                SendError(client, "501 Not Implemented");\r
+                return;\r
             }\r
-            finally\r
+            var tmp = request[1].Split('?');\r
+            var path = HttpUtility.UrlDecode(tmp[0]);\r
+            if (path == null || !path.StartsWith("/"))\r
             {\r
-                _listener.Stop();\r
+                SendError(client, "400 Bad Request");\r
+                return;\r
             }\r
-        }\r
-\r
-        private void Process(Object obj)\r
-        {\r
-            var client = (Socket)obj;\r
-            var data = new byte[4096];\r
-            var from = DateTime.MinValue;\r
-            var to = DateTime.MaxValue;\r
-            try\r
+            if (tmp.Length == 2)\r
             {\r
-                if (client.Receive(data) == 0)\r
-                    return;\r
-                var request = Encoding.UTF8.GetString(data).Split('\r')[0].Split(' ');\r
-                if (request.Length != 3)\r
-                {\r
-                    SendError(client, "400 Bad Request");\r
-                    return;\r
-                }\r
-                if (!request[0].StartsWith("GET", StringComparison.OrdinalIgnoreCase))\r
+                var query = HttpUtility.ParseQueryString(tmp[1]);\r
+                if (query["from"] != null)\r
                 {\r
-                    SendError(client, "501 Not Implemented");\r
-                    return;\r
+                    double.TryParse(query["from"], out var tick);\r
+                    from = new DateTime(1970, 1, 1).ToLocalTime().AddSeconds(tick / 1000);\r
                 }\r
-                var tmp = request[1].Split('?');\r
-                var path = HttpUtility.UrlDecode(tmp[0]);\r
-                if (path == null || !path.StartsWith("/"))\r
+                if (query["to"] != null)\r
                 {\r
-                    SendError(client, "400 Bad Request");\r
-                    return;\r
-                }\r
-                if (tmp.Length == 2)\r
-                {\r
-                    var query = HttpUtility.ParseQueryString(tmp[1]);\r
-                    if (query["from"] != null)\r
-                    {\r
-                        double tick;\r
-                        double.TryParse(query["from"], out tick);\r
-                        from = new DateTime(1970, 1, 1).ToLocalTime().AddSeconds(tick / 1000);\r
-                    }\r
-                    if (query["to"] != null)\r
-                    {\r
-                        double tick;\r
-                        double.TryParse(query["to"], out tick);\r
-                        to = new DateTime(1970, 1, 1).ToLocalTime().AddSeconds(tick / 1000);\r
-                    }\r
+                    double.TryParse(query["to"], out var tick);\r
+                    to = new DateTime(1970, 1, 1).ToLocalTime().AddSeconds(tick / 1000);\r
                 }\r
+                if (query["number"] != null)\r
+                    timestamp = query["number"] == "true";\r
+            }\r
 \r
-                path = path == "/" ? "index.html" : path.Substring(1);\r
-                var full = Path.Combine(_indexDir, path);\r
-                var csv = Path.Combine(_outputDir, path);\r
-                if (path.EndsWith(".html", StringComparison.OrdinalIgnoreCase) && File.Exists(full))\r
-                {\r
-                    SendFile(client, full, "text/html");\r
-                    return;\r
-                }\r
-                if (path.EndsWith(".csv", StringComparison.OrdinalIgnoreCase) && File.Exists(csv))\r
-                {\r
-                    SendFile(client, csv, "text/csv; charset=Shift_JIS");\r
-                    return;\r
-                }\r
-                if (path.EndsWith(".json", StringComparison.OrdinalIgnoreCase))\r
-                {\r
-                    SendJsonData(client, csv, from, to);\r
-                    return;\r
-                }\r
-                if (path.EndsWith(".js", StringComparison.OrdinalIgnoreCase) && File.Exists(full))\r
-                {\r
-                    SendFile(client, full, "application/javascript");\r
-                    return;\r
-                }\r
-                SendError(client, "404 Not Found");\r
+            path = path == "/" ? "index.html" : path.Substring(1);\r
+            var full = Path.Combine(IndexDir, path);\r
+            var csv = Path.Combine(_outputDir, path);\r
+            if (path.EndsWith(".html", StringComparison.OrdinalIgnoreCase) && File.Exists(full))\r
+            {\r
+                SendFile(client, full, "text/html");\r
+                return;\r
             }\r
-            catch (IOException)\r
+            if (path.EndsWith(".csv", StringComparison.OrdinalIgnoreCase) && File.Exists(csv))\r
             {\r
+                SendFile(client, csv, "text/csv; charset=Shift_JIS");\r
+                return;\r
             }\r
-            catch (SocketException)\r
+            if (path.EndsWith(".json", StringComparison.OrdinalIgnoreCase))\r
             {\r
+                SendJsonData(client, csv, from, to, timestamp);\r
+                return;\r
             }\r
-            finally\r
+            if (path.EndsWith(".js", StringComparison.OrdinalIgnoreCase) && File.Exists(full))\r
             {\r
-                client.Close();\r
+                SendFile(client, full, "application/javascript");\r
+                return;\r
             }\r
+            if (path.EndsWith("proxy.pac"))\r
+            {\r
+                SendProxyPac(client, HttpProxy.LocalPort);\r
+                return;\r
+            }\r
+            SendError(client, "404 Not Found");\r
         }\r
 \r
-        private void SendError(Socket client, string error)\r
+        private static void SendError(Socket client, string error)\r
         {\r
             using (var writer = new StreamWriter(new MemoryStream(), Encoding.ASCII))\r
             {\r
@@ -172,42 +122,103 @@ namespace KancolleSniffer
             }\r
         }\r
 \r
-        private void SendJsonData(Socket client, string path, DateTime from, DateTime to)\r
+        private static void SendJsonData(Socket client, string path, DateTime from, DateTime to, bool number)\r
         {\r
-            var header = new StreamWriter(new MemoryStream(), Encoding.ASCII);\r
-            header.Write("HTTP/1.1 200 OK\r\n");\r
-            header.Write("Server: KancolleSniffer\r\n");\r
-            header.Write("Date: {0:R}\r\n", DateTime.Now);\r
-            header.Write("Content-Type: {0}\r\n", "application/json; charset=Shift_JIS");\r
-            header.Write("Connection: close\r\n\r\n");\r
-            header.Flush();\r
-            client.Send(((MemoryStream)header.BaseStream).ToArray());\r
-\r
+            using (var header = new StreamWriter(new MemoryStream(), Encoding.ASCII))\r
+            {\r
+                header.Write("HTTP/1.1 200 OK\r\n");\r
+                header.Write("Server: KancolleSniffer\r\n");\r
+                header.Write("Date: {0:R}\r\n", DateTime.Now);\r
+                header.Write("Content-Type: {0}\r\n", "application/json; charset=Shift_JIS");\r
+                header.Write("Connection: close\r\n\r\n");\r
+                header.Flush();\r
+                client.Send(((MemoryStream)header.BaseStream).ToArray());\r
+            }\r
             var csv = path.Replace(".json", ".csv");\r
             var encoding = Encoding.GetEncoding("Shift_JIS");\r
             client.Send(encoding.GetBytes("{ \"data\": [\n"));\r
+            var battle = false;\r
+            var material = false;\r
             try\r
             {\r
-                if (File.Exists(csv))\r
+                if (!File.Exists(csv))\r
+                    return;\r
+                var records = 0;\r
+                if (path.EndsWith("遠征報告書.json"))\r
+                {\r
+                    records = 10;\r
+                }\r
+                else if (path.EndsWith("改修報告書.json"))\r
+                {\r
+                    records = 15;\r
+                }\r
+                else if (path.EndsWith("海戦・ドロップ報告書.json"))\r
                 {\r
-                    var delimiter = "";\r
-                    var material = path.EndsWith("資材ログ.json"); // 末尾の空データを削除する必要がある\r
-                    foreach (var line in File.ReadLines(csv, encoding).Skip(1))\r
+                    records = 39;\r
+                    battle = true;\r
+                }\r
+                else if (path.EndsWith("開発報告書.json"))\r
+                {\r
+                    records = 9;\r
+                }\r
+                else if (path.EndsWith("建造報告書.json"))\r
+                {\r
+                    records = 12;\r
+                }\r
+                else if (path.EndsWith("資材ログ.json"))\r
+                {\r
+                    records = 9;\r
+                    material = true;\r
+                }\r
+                else if (path.EndsWith("戦果.json"))\r
+                {\r
+                    records = 3;\r
+                }\r
+                var delimiter = "";\r
+                foreach (var line in File.ReadLines(csv, encoding).Skip(1))\r
+                {\r
+                    var data = line.Split(',');\r
+                    if (!DateTime.TryParseExact(data[0], Logger.DateTimeFormat, CultureInfo.InvariantCulture,\r
+                        DateTimeStyles.AssumeLocal, out DateTime date))\r
                     {\r
-                        var data = line.Split(',');\r
-                        DateTime date;\r
-                        if (!DateTime.TryParseExact(data[0], Logger.DateTimeFormat, CultureInfo.InvariantCulture,\r
-                            DateTimeStyles.AssumeLocal, out date) &&\r
-                            DateTime.TryParse(data[0], CultureInfo.CurrentCulture, DateTimeStyles.AssumeLocal, out date))\r
+                        if (DateTime.TryParse(data[0], CultureInfo.CurrentCulture,\r
+                            DateTimeStyles.AssumeLocal, out date))\r
                         {\r
                             data[0] = date.ToString(Logger.DateTimeFormat);\r
                         }\r
-                        if (date < from || to < date)\r
+                        else\r
+                        {\r
                             continue;\r
+                        }\r
+                    }\r
+                    if (date < from || to < date)\r
+                        continue;\r
+                    IEnumerable<string> entries = data;\r
+                    if (material)\r
+                        entries = data.Take(9);\r
+                    if (battle)\r
+                        entries = BattleLogProcessor.Process(data);\r
+                    if (entries.Count() != records)\r
+                        continue;\r
+                    if (number)\r
+                    {\r
+                        var stamp = ((date.ToUniversalTime().Ticks -\r
+                                      new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).Ticks) /\r
+                                     TimeSpan.TicksPerMillisecond).ToString();\r
+                        client.Send(encoding.GetBytes(delimiter + "[" + stamp + "," +\r
+                                                      string.Join(",", entries.Skip(1)) + "]"));\r
+                    }\r
+                    else\r
+                    {\r
                         client.Send(encoding.GetBytes(delimiter + "[\"" +\r
-                                                      string.Join("\",\"", (material ? data.Take(9) : data)) + "\"]"));\r
-                        delimiter = ",\n";\r
+                                                      string.Join("\",\"", entries) + "\"]"));\r
                     }\r
+                    delimiter = ",\n";\r
+                }\r
+                if (material && !number)\r
+                {\r
+                    client.Send(encoding.GetBytes(delimiter + "[\"" +\r
+                                                  string.Join("\",\"", GetCurrentMaterialRecord()) + "\"]"));\r
                 }\r
             }\r
             finally\r
@@ -216,26 +227,153 @@ namespace KancolleSniffer
             }\r
         }\r
 \r
-        private void SendFile(Socket client, string path, string mime)\r
+        private static IEnumerable<string> GetCurrentMaterialRecord()\r
         {\r
-            using (var writer = new StreamWriter(new MemoryStream(), Encoding.ASCII))\r
+            return new[] {DateTime.Now.ToString(Logger.DateTimeFormat)}.\r
+                Concat(MaterialHistory.Select(c => c.Now.ToString()));\r
+        }\r
+\r
+        private static void SendFile(Socket client, string path, string mime)\r
+        {\r
+            using (var header = new StreamWriter(new MemoryStream(), Encoding.ASCII))\r
             {\r
-                writer.Write("HTTP/1.1 200 OK\r\n");\r
-                writer.Write("Server: KancolleSniffer\r\n");\r
-                writer.Write("Date: {0:R}\r\n", DateTime.Now);\r
-                writer.Write("Content-Length: {0}\r\n", new FileInfo(path).Length);\r
-                writer.Write("Content-Type: {0}\r\n", mime);\r
-                writer.Write("Connection: close\r\n\r\n");\r
-                writer.Flush();\r
-                client.SendFile(path, ((MemoryStream)writer.BaseStream).ToArray(), null,\r
+                header.Write("HTTP/1.1 200 OK\r\n");\r
+                header.Write("Server: KancolleSniffer\r\n");\r
+                header.Write("Date: {0:R}\r\n", DateTime.Now);\r
+                header.Write("Content-Length: {0}\r\n", new FileInfo(path).Length);\r
+                header.Write("Content-Type: {0}\r\n", mime);\r
+                header.Write("Connection: close\r\n\r\n");\r
+                header.Flush();\r
+                client.SendFile(path, ((MemoryStream)header.BaseStream).ToArray(), null,\r
                     TransmitFileOptions.UseDefaultWorkerThread);\r
             }\r
         }\r
 \r
-        public void Stop()\r
+        private static void SendProxyPac(Socket client, int port)\r
+        {\r
+            using (var header = new StreamWriter(new MemoryStream(), Encoding.ASCII))\r
+            {\r
+                header.Write("HTTP/1.1 200 OK\r\n");\r
+                header.Write("Server: KancolleSniffer\r\n");\r
+                header.Write("Date: {0:R}\r\n", DateTime.Now);\r
+                header.Write("Content-Type: application/x-ns-proxy-autoconfig\r\n");\r
+                header.Write("Connection: close\r\n\r\n");\r
+                header.Flush();\r
+                client.Send(((MemoryStream)header.BaseStream).ToArray());\r
+            }\r
+            var pacFile = @"\r
+function FindProxyForURL(url, host) {\r
+  if(isInNet(host, ""203.104.209.71"", ""255.255.255.255"") ||\r
+     isInNet(host, ""203.104.209.87"", ""255.255.255.255"") ||\r
+     isInNet(host, ""125.6.184.16"", ""255.255.255.255"") ||\r
+     isInNet(host, ""125.6.187.205"", ""255.255.255.255"") ||\r
+     isInNet(host, ""125.6.187.229"", ""255.255.255.255"") ||\r
+     isInNet(host, ""203.104.209.134"", ""255.255.255.255"") ||\r
+     isInNet(host, ""203.104.209.167"", ""255.255.255.255"") ||\r
+     isInNet(host, ""203.104.248.135"", ""255.255.255.255"") ||\r
+     isInNet(host, ""125.6.189.7"", ""255.255.255.255"") ||\r
+     isInNet(host, ""125.6.189.39"", ""255.255.255.255"") ||\r
+     isInNet(host, ""125.6.189.71"", ""255.255.255.255"") ||\r
+     isInNet(host, ""125.6.189.103"", ""255.255.255.255"") ||\r
+     isInNet(host, ""125.6.189.135"", ""255.255.255.255"") ||\r
+     isInNet(host, ""125.6.189.167"", ""255.255.255.255"") ||\r
+     isInNet(host, ""125.6.189.215"", ""255.255.255.255"") ||\r
+     isInNet(host, ""125.6.189.247"", ""255.255.255.255"") ||\r
+     isInNet(host, ""203.104.209.23"", ""255.255.255.255"") ||\r
+     isInNet(host, ""203.104.209.39"", ""255.255.255.255"") ||\r
+     isInNet(host, ""203.104.209.55"", ""255.255.255.255"") ||\r
+     isInNet(host, ""203.104.209.102"", ""255.255.255.255"")) {\r
+       return ""PROXY 127.0.0.1:8080"";\r
+    }\r
+  else {\r
+    return ""DIRECT"";\r
+  }\r
+}".Replace("8080", port.ToString());\r
+            client.Send(Encoding.ASCII.GetBytes(pacFile));\r
+        }\r
+    }\r
+\r
+    public static class BattleLogProcessor\r
+    {\r
+        public static IEnumerable<string> Process(string[] data)\r
+        {\r
+            if (data.Length == 35)\r
+                data = data.Concat(Enumerable.Repeat("", 3)).ToArray();\r
+            if (data.Length != 38)\r
+                return data;\r
+            if (data[5] == "T字戦(有利)")\r
+                data[5] = "T字有利";\r
+            if (data[5] == "T字戦(不利)")\r
+                data[5] = "T字不利";\r
+            if (data[6].EndsWith("航行序列"))\r
+                data[6] = data[6].Substring(0, 4);\r
+            if (data[7].EndsWith("航行序列"))\r
+                data[7] = data[7].Substring(0, 4);\r
+            data[37] = ShortenAirBattleResult(data[37]);\r
+            return AddDamagedShip(data);\r
+        }\r
+\r
+        private static string ShortenAirBattleResult(string result)\r
+        {\r
+            switch (result)\r
+            {\r
+                case "制空均衡":\r
+                    return "均衡";\r
+                case "制空権確保":\r
+                    return "確保";\r
+                case "航空優勢":\r
+                    return "優勢";\r
+                case "航空劣勢":\r
+                    return "劣勢";\r
+                case "制空権喪失":\r
+                    return "喪失";\r
+                default:\r
+                    return "";\r
+            }\r
+        }\r
+\r
+        private static IEnumerable<string> AddDamagedShip(string[] data)\r
+        {\r
+            var damaged = new List<string>();\r
+            for (var i = 11; i < 11 + 12; i += 2)\r
+            {\r
+                if (data[i] == "")\r
+                    continue;\r
+                var ship = data[i] = StripKana(data[i]);\r
+                var hp = data[i + 1];\r
+                try\r
+                {\r
+                    if (ship.Contains("・"))\r
+                    {\r
+                        var ships = ship.Split('・');\r
+                        var hps = hp.Split('・');\r
+                        var nowMax = hps[0].Split('/').Select(int.Parse).ToArray();\r
+                        if (ShipStatus.CalcDamage(nowMax[0], nowMax[1]) == ShipStatus.Damage.Badly)\r
+                            damaged.Add(ships[0]);\r
+                        nowMax = hps[1].Split('/').Select(int.Parse).ToArray();\r
+                        if (ShipStatus.CalcDamage(nowMax[0], nowMax[1]) == ShipStatus.Damage.Badly)\r
+                            damaged.Add(ships[1]);\r
+                    }\r
+                    else\r
+                    {\r
+                        var nowMax = hp.Split('/').Select(int.Parse).ToArray();\r
+                        if (ShipStatus.CalcDamage(nowMax[0], nowMax[1]) == ShipStatus.Damage.Badly)\r
+                            damaged.Add(ship);\r
+                    }\r
+                }\r
+                catch (FormatException)\r
+                {\r
+                    return data;\r
+                }\r
+            }\r
+            return data.Take(23).Concat(new[] { string.Join("・", damaged) }).Concat(data.Skip(23));\r
+        }\r
+\r
+        private static readonly Regex Kana = new Regex(@"\([^)]+\)\(", RegexOptions.Compiled);\r
+\r
+        private static string StripKana(string name)\r
         {\r
-            IsListening = false;\r
-            _listener.Server.Close();\r
+            return Kana.Replace(name, "(");\r
         }\r
     }\r
 }
\ No newline at end of file