OSDN Git Service

資材報告書に現在の資材を表示する
[kancollesniffer/KancolleSniffer.git] / KancolleSniffer / LogServer.cs
1 // Copyright (C) 2014, 2015 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.Globalization;\r
18 using System.IO;\r
19 using System.Linq;\r
20 using System.Net.Sockets;\r
21 using System.Text;\r
22 \r
23 namespace KancolleSniffer\r
24 {\r
25     public class LogServer\r
26     {\r
27         private static readonly string IndexDir = AppDomain.CurrentDomain.BaseDirectory;\r
28         private static string _outputDir = AppDomain.CurrentDomain.BaseDirectory;\r
29 \r
30         public static string OutputDir\r
31         {\r
32             set => _outputDir = value;\r
33         }\r
34 \r
35         public static MaterialCount[] MaterialHistory { private get; set; }\r
36 \r
37         public static void Process(Socket client, string requestLine)\r
38         {\r
39             var from = DateTime.MinValue;\r
40             var to = DateTime.MaxValue;\r
41             var timestamp = false;\r
42 \r
43             var request = requestLine.Split(' ');\r
44             if (request.Length != 3)\r
45             {\r
46                 SendError(client, "400 Bad Request");\r
47                 return;\r
48             }\r
49             if (!request[0].StartsWith("GET", StringComparison.OrdinalIgnoreCase))\r
50             {\r
51                 SendError(client, "501 Not Implemented");\r
52                 return;\r
53             }\r
54             var tmp = request[1].Split('?');\r
55             var path = HttpUtility.UrlDecode(tmp[0]);\r
56             if (path == null || !path.StartsWith("/"))\r
57             {\r
58                 SendError(client, "400 Bad Request");\r
59                 return;\r
60             }\r
61             if (tmp.Length == 2)\r
62             {\r
63                 var query = HttpUtility.ParseQueryString(tmp[1]);\r
64                 if (query["from"] != null)\r
65                 {\r
66                     double.TryParse(query["from"], out var tick);\r
67                     from = new DateTime(1970, 1, 1).ToLocalTime().AddSeconds(tick / 1000);\r
68                 }\r
69                 if (query["to"] != null)\r
70                 {\r
71                     double.TryParse(query["to"], out var tick);\r
72                     to = new DateTime(1970, 1, 1).ToLocalTime().AddSeconds(tick / 1000);\r
73                 }\r
74                 if (query["number"] != null)\r
75                     timestamp = query["number"] == "true";\r
76             }\r
77 \r
78             path = path == "/" ? "index.html" : path.Substring(1);\r
79             var full = Path.Combine(IndexDir, path);\r
80             var csv = Path.Combine(_outputDir, path);\r
81             if (path.EndsWith(".html", StringComparison.OrdinalIgnoreCase) && File.Exists(full))\r
82             {\r
83                 SendFile(client, full, "text/html");\r
84                 return;\r
85             }\r
86             if (path.EndsWith(".csv", StringComparison.OrdinalIgnoreCase) && File.Exists(csv))\r
87             {\r
88                 SendFile(client, csv, "text/csv; charset=Shift_JIS");\r
89                 return;\r
90             }\r
91             if (path.EndsWith(".json", StringComparison.OrdinalIgnoreCase))\r
92             {\r
93                 SendJsonData(client, csv, from, to, timestamp);\r
94                 return;\r
95             }\r
96             if (path.EndsWith(".js", StringComparison.OrdinalIgnoreCase) && File.Exists(full))\r
97             {\r
98                 SendFile(client, full, "application/javascript");\r
99                 return;\r
100             }\r
101             if (path.EndsWith("proxy.pac"))\r
102             {\r
103                 SendProxyPac(client, HttpProxy.LocalPort);\r
104                 return;\r
105             }\r
106             SendError(client, "404 Not Found");\r
107         }\r
108 \r
109         private static void SendError(Socket client, string error)\r
110         {\r
111             using (var writer = new StreamWriter(new MemoryStream(), Encoding.ASCII))\r
112             {\r
113                 writer.Write("HTTP/1.1 {0}\r\n", error);\r
114                 writer.Write("Server: KancolleSniffer\r\n");\r
115                 writer.Write("Date: {0:R}\r\n", DateTime.Now);\r
116                 writer.Write("Connection: close\r\n\r\n");\r
117                 writer.Write("<html><head><title>{0}</title></head>\r\n", error);\r
118                 writer.Write("<body><h4>{0}</h4></body></html>\r\n\r\n", error);\r
119                 writer.Flush();\r
120                 client.Send(((MemoryStream)writer.BaseStream).ToArray());\r
121             }\r
122         }\r
123 \r
124         private static void SendJsonData(Socket client, string path, DateTime from, DateTime to, bool number)\r
125         {\r
126             using (var header = new StreamWriter(new MemoryStream(), Encoding.ASCII))\r
127             {\r
128                 header.Write("HTTP/1.1 200 OK\r\n");\r
129                 header.Write("Server: KancolleSniffer\r\n");\r
130                 header.Write("Date: {0:R}\r\n", DateTime.Now);\r
131                 header.Write("Content-Type: {0}\r\n", "application/json; charset=Shift_JIS");\r
132                 header.Write("Connection: close\r\n\r\n");\r
133                 header.Flush();\r
134                 client.Send(((MemoryStream)header.BaseStream).ToArray());\r
135             }\r
136             var csv = path.Replace(".json", ".csv");\r
137             var encoding = Encoding.GetEncoding("Shift_JIS");\r
138             client.Send(encoding.GetBytes("{ \"data\": [\n"));\r
139             var battle = false;\r
140             var material = false;\r
141             try\r
142             {\r
143                 if (!File.Exists(csv))\r
144                     return;\r
145                 var records = 0;\r
146                 if (path.EndsWith("遠征報告書.json"))\r
147                 {\r
148                     records = 10;\r
149                 }\r
150                 else if (path.EndsWith("改修報告書.json"))\r
151                 {\r
152                     records = 15;\r
153                 }\r
154                 else if (path.EndsWith("海戦・ドロップ報告書.json"))\r
155                 {\r
156                     records = 39;\r
157                     battle = true;\r
158                 }\r
159                 else if (path.EndsWith("開発報告書.json"))\r
160                 {\r
161                     records = 9;\r
162                 }\r
163                 else if (path.EndsWith("建造報告書.json"))\r
164                 {\r
165                     records = 12;\r
166                 }\r
167                 else if (path.EndsWith("資材ログ.json"))\r
168                 {\r
169                     records = 9;\r
170                     material = true;\r
171                 }\r
172                 else if (path.EndsWith("戦果.json"))\r
173                 {\r
174                     records = 3;\r
175                 }\r
176                 var delimiter = "";\r
177                 foreach (var line in File.ReadLines(csv, encoding).Skip(1))\r
178                 {\r
179                     var data = line.Split(',');\r
180                     if (!DateTime.TryParseExact(data[0], Logger.DateTimeFormat, CultureInfo.InvariantCulture,\r
181                         DateTimeStyles.AssumeLocal, out DateTime date))\r
182                     {\r
183                         if (DateTime.TryParse(data[0], CultureInfo.CurrentCulture,\r
184                             DateTimeStyles.AssumeLocal, out date))\r
185                         {\r
186                             data[0] = date.ToString(Logger.DateTimeFormat);\r
187                         }\r
188                         else\r
189                         {\r
190                             continue;\r
191                         }\r
192                     }\r
193                     if (date < from || to < date)\r
194                         continue;\r
195                     IEnumerable<string> entries = data;\r
196                     if (material)\r
197                         entries = data.Take(9);\r
198                     if (battle)\r
199                         entries = ProcessBattleLog(data);\r
200                     if (entries.Count() != records)\r
201                         continue;\r
202                     if (number)\r
203                     {\r
204                         var stamp = ((date.ToUniversalTime().Ticks -\r
205                                       new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).Ticks) /\r
206                                      TimeSpan.TicksPerMillisecond).ToString();\r
207                         client.Send(encoding.GetBytes(delimiter + "[" + stamp + "," +\r
208                                                       string.Join(",", entries.Skip(1)) + "]"));\r
209                     }\r
210                     else\r
211                     {\r
212                         client.Send(encoding.GetBytes(delimiter + "[\"" +\r
213                                                       string.Join("\",\"", entries) + "\"]"));\r
214                     }\r
215                     delimiter = ",\n";\r
216                 }\r
217                 if (material && !number)\r
218                 {\r
219                     client.Send(encoding.GetBytes(delimiter + "[\"" +\r
220                                                   string.Join("\",\"", GetCurrentMaterialRecord()) + "\"]"));\r
221                 }\r
222             }\r
223             finally\r
224             {\r
225                 client.Send(encoding.GetBytes("]}\n"));\r
226             }\r
227         }\r
228 \r
229         private static IEnumerable<string> GetCurrentMaterialRecord()\r
230         {\r
231             return new[] {DateTime.Now.ToString(Logger.DateTimeFormat)}.\r
232                 Concat(MaterialHistory.Select(c => c.Now.ToString()));\r
233         }\r
234 \r
235         private static IEnumerable<string> ProcessBattleLog(string[] data)\r
236         {\r
237             if (data.Length == 35)\r
238                 data = data.Concat(Enumerable.Repeat("", 3)).ToArray();\r
239             if (data.Length != 38)\r
240                 return data;\r
241             if (data[5] == "T字戦(有利)")\r
242                 data[5] = "T字有利";\r
243             if (data[5] == "T字戦(不利)")\r
244                 data[5] = "T字不利";\r
245             if (data[6].EndsWith("航行序列"))\r
246                 data[6] = data[6].Substring(0, 4);\r
247             if (data[7].EndsWith("航行序列"))\r
248                 data[7] = data[7].Substring(0, 4);\r
249             data[37] = ShortenAirBattleResult(data[37]);\r
250             return AddDamagedShip(data);\r
251         }\r
252 \r
253         private static string ShortenAirBattleResult(string result)\r
254         {\r
255             switch (result)\r
256             {\r
257                 case "制空均衡":\r
258                     return "均衡";\r
259                 case "制空権確保":\r
260                     return "確保";\r
261                 case "航空優勢":\r
262                     return "優勢";\r
263                 case "航空劣勢":\r
264                     return "劣勢";\r
265                 case "制空権喪失":\r
266                     return "喪失";\r
267                 default:\r
268                     return "";\r
269             }\r
270         }\r
271 \r
272         private static IEnumerable<string> AddDamagedShip(string[] data)\r
273         {\r
274             var damaged = new List<string>();\r
275             for (var i = 11; i < 11 + 12; i += 2)\r
276             {\r
277                 var ship = data[i];\r
278                 if (ship == "")\r
279                     continue;\r
280                 var hp = data[i + 1];\r
281                 try\r
282                 {\r
283                     if (ship.Contains("・"))\r
284                     {\r
285                         var ships = ship.Split('・');\r
286                         var hps = hp.Split('・');\r
287                         var nowMax = hps[0].Split('/').Select(int.Parse).ToArray();\r
288                         if (ShipStatus.CalcDamage(nowMax[0], nowMax[1]) == ShipStatus.Damage.Badly)\r
289                             damaged.Add(ships[0]);\r
290                         nowMax = hps[1].Split('/').Select(int.Parse).ToArray();\r
291                         if (ShipStatus.CalcDamage(nowMax[0], nowMax[1]) == ShipStatus.Damage.Badly)\r
292                             damaged.Add(ships[1]);\r
293                     }\r
294                     else\r
295                     {\r
296                         var nowMax = hp.Split('/').Select(int.Parse).ToArray();\r
297                         if (ShipStatus.CalcDamage(nowMax[0], nowMax[1]) == ShipStatus.Damage.Badly)\r
298                             damaged.Add(ship);\r
299                     }\r
300                 }\r
301                 catch (FormatException)\r
302                 {\r
303                     return data;\r
304                 }\r
305             }\r
306             return data.Take(23).Concat(new[] {string.Join("・", damaged)}).Concat(data.Skip(23));\r
307         }\r
308 \r
309         private static void SendFile(Socket client, string path, string mime)\r
310         {\r
311             using (var header = new StreamWriter(new MemoryStream(), Encoding.ASCII))\r
312             {\r
313                 header.Write("HTTP/1.1 200 OK\r\n");\r
314                 header.Write("Server: KancolleSniffer\r\n");\r
315                 header.Write("Date: {0:R}\r\n", DateTime.Now);\r
316                 header.Write("Content-Length: {0}\r\n", new FileInfo(path).Length);\r
317                 header.Write("Content-Type: {0}\r\n", mime);\r
318                 header.Write("Connection: close\r\n\r\n");\r
319                 header.Flush();\r
320                 client.SendFile(path, ((MemoryStream)header.BaseStream).ToArray(), null,\r
321                     TransmitFileOptions.UseDefaultWorkerThread);\r
322             }\r
323         }\r
324 \r
325         private static void SendProxyPac(Socket client, int port)\r
326         {\r
327             using (var header = new StreamWriter(new MemoryStream(), Encoding.ASCII))\r
328             {\r
329                 header.Write("HTTP/1.1 200 OK\r\n");\r
330                 header.Write("Server: KancolleSniffer\r\n");\r
331                 header.Write("Date: {0:R}\r\n", DateTime.Now);\r
332                 header.Write("Content-Type: application/x-ns-proxy-autoconfig\r\n");\r
333                 header.Write("Connection: close\r\n\r\n");\r
334                 header.Flush();\r
335                 client.Send(((MemoryStream)header.BaseStream).ToArray());\r
336             }\r
337             var pacFile = @"\r
338 function FindProxyForURL(url, host) {\r
339   if(isInNet(host, ""203.104.209.71"", ""255.255.255.255"") ||\r
340      isInNet(host, ""125.6.184.15"", ""255.255.255.255"") ||\r
341      isInNet(host, ""125.6.184.16"", ""255.255.255.255"") ||\r
342      isInNet(host, ""125.6.187.205"", ""255.255.255.255"") ||\r
343      isInNet(host, ""125.6.187.229"", ""255.255.255.255"") ||\r
344      isInNet(host, ""125.6.187.253"", ""255.255.255.255"") ||\r
345      isInNet(host, ""125.6.188.25"", ""255.255.255.255"") ||\r
346      isInNet(host, ""203.104.248.135"", ""255.255.255.255"") ||\r
347      isInNet(host, ""125.6.189.7"", ""255.255.255.255"") ||\r
348      isInNet(host, ""125.6.189.39"", ""255.255.255.255"") ||\r
349      isInNet(host, ""125.6.189.71"", ""255.255.255.255"") ||\r
350      isInNet(host, ""125.6.189.103"", ""255.255.255.255"") ||\r
351      isInNet(host, ""125.6.189.135"", ""255.255.255.255"") ||\r
352      isInNet(host, ""125.6.189.167"", ""255.255.255.255"") ||\r
353      isInNet(host, ""125.6.189.215"", ""255.255.255.255"") ||\r
354      isInNet(host, ""125.6.189.247"", ""255.255.255.255"") ||\r
355      isInNet(host, ""203.104.209.23"", ""255.255.255.255"") ||\r
356      isInNet(host, ""203.104.209.39"", ""255.255.255.255"") ||\r
357      isInNet(host, ""203.104.209.55"", ""255.255.255.255"") ||\r
358      isInNet(host, ""203.104.209.102"", ""255.255.255.255"") ||\r
359      isInNet(host, ""203.104.209.87"", ""255.255.255.255"")) {\r
360        return ""PROXY 127.0.0.1:8080"";\r
361     }\r
362   else {\r
363     return ""DIRECT"";\r
364   }\r
365 }".Replace("8080", port.ToString());\r
366             client.Send(Encoding.ASCII.GetBytes(pacFile));\r
367         }\r
368     }\r
369 }