1 // Copyright (C) 2014, 2015 Kazuhiro Fujieda <fujieda@users.osdn.me>
\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
7 // http://www.apache.org/licenses/LICENSE-2.0
\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
16 using System.Collections.Generic;
\r
17 using System.Globalization;
\r
20 using System.Net.Sockets;
\r
22 using System.Text.RegularExpressions;
\r
24 namespace KancolleSniffer
\r
26 public class LogServer
\r
28 private static readonly string IndexDir = AppDomain.CurrentDomain.BaseDirectory;
\r
29 private static string _outputDir = AppDomain.CurrentDomain.BaseDirectory;
\r
31 public static string OutputDir
\r
33 set => _outputDir = value;
\r
36 public static MaterialCount[] MaterialHistory { private get; set; }
\r
38 public static void Process(Socket client, string requestLine)
\r
40 var from = DateTime.MinValue;
\r
41 var to = DateTime.MaxValue;
\r
42 var timestamp = false;
\r
44 var request = requestLine.Split(' ');
\r
45 if (request.Length != 3)
\r
47 SendError(client, "400 Bad Request");
\r
50 if (!request[0].StartsWith("GET", StringComparison.OrdinalIgnoreCase))
\r
52 SendError(client, "501 Not Implemented");
\r
55 var tmp = request[1].Split('?');
\r
56 var path = HttpUtility.UrlDecode(tmp[0]);
\r
57 if (path == null || !path.StartsWith("/"))
\r
59 SendError(client, "400 Bad Request");
\r
62 if (tmp.Length == 2)
\r
64 var query = HttpUtility.ParseQueryString(tmp[1]);
\r
65 if (query["from"] != null)
\r
67 double.TryParse(query["from"], out var tick);
\r
68 from = new DateTime(1970, 1, 1).ToLocalTime().AddSeconds(tick / 1000);
\r
70 if (query["to"] != null)
\r
72 double.TryParse(query["to"], out var tick);
\r
73 to = new DateTime(1970, 1, 1).ToLocalTime().AddSeconds(tick / 1000);
\r
75 if (query["number"] != null)
\r
76 timestamp = query["number"] == "true";
\r
79 path = path == "/" ? "index.html" : path.Substring(1);
\r
80 var full = Path.Combine(IndexDir, path);
\r
81 var csv = Path.Combine(_outputDir, path);
\r
82 if (path.EndsWith(".html", StringComparison.OrdinalIgnoreCase) && File.Exists(full))
\r
84 SendFile(client, full, "text/html");
\r
87 if (path.EndsWith(".csv", StringComparison.OrdinalIgnoreCase) && File.Exists(csv))
\r
89 SendFile(client, csv, "text/csv; charset=Shift_JIS");
\r
92 if (path.EndsWith(".json", StringComparison.OrdinalIgnoreCase))
\r
94 SendJsonData(client, csv, from, to, timestamp);
\r
97 if (path.EndsWith(".js", StringComparison.OrdinalIgnoreCase) && File.Exists(full))
\r
99 SendFile(client, full, "application/javascript");
\r
102 if (path.EndsWith(".pac"))
\r
104 SendProxyPac(client, HttpProxy.LocalPort);
\r
107 if (File.Exists(full))
\r
109 SendFile(client, full, "text/plain");
\r
112 SendError(client, "404 Not Found");
\r
115 private static void SendError(Socket client, string error)
\r
117 using (var writer = new StreamWriter(new MemoryStream(), Encoding.ASCII))
\r
119 writer.Write("HTTP/1.1 {0}\r\n", error);
\r
120 writer.Write("Server: KancolleSniffer\r\n");
\r
121 writer.Write("Date: {0:R}\r\n", DateTime.Now);
\r
122 writer.Write("Connection: close\r\n\r\n");
\r
123 writer.Write("<html><head><title>{0}</title></head>\r\n", error);
\r
124 writer.Write("<body><h4>{0}</h4></body></html>\r\n\r\n", error);
\r
126 client.Send(((MemoryStream)writer.BaseStream).ToArray());
\r
130 private static void SendJsonData(Socket client, string path, DateTime from, DateTime to, bool number)
\r
132 using (var header = new StreamWriter(new MemoryStream(), Encoding.ASCII))
\r
134 header.Write("HTTP/1.1 200 OK\r\n");
\r
135 header.Write("Server: KancolleSniffer\r\n");
\r
136 header.Write("Date: {0:R}\r\n", DateTime.Now);
\r
137 header.Write("Content-Type: {0}\r\n", "application/json; charset=Shift_JIS");
\r
138 header.Write("Connection: close\r\n\r\n");
\r
140 client.Send(((MemoryStream)header.BaseStream).ToArray());
\r
142 var csv = path.Replace(".json", ".csv");
\r
143 var encoding = Encoding.GetEncoding("Shift_JIS");
\r
144 client.Send(encoding.GetBytes("{ \"data\": [\n"));
\r
145 var battle = false;
\r
146 var material = false;
\r
149 if (!File.Exists(csv))
\r
152 if (path.EndsWith("遠征報告書.json"))
\r
156 else if (path.EndsWith("改修報告書.json"))
\r
160 else if (path.EndsWith("海戦・ドロップ報告書.json"))
\r
165 else if (path.EndsWith("開発報告書.json"))
\r
169 else if (path.EndsWith("建造報告書.json"))
\r
173 else if (path.EndsWith("資材ログ.json"))
\r
178 else if (path.EndsWith("戦果.json"))
\r
182 var delimiter = "";
\r
183 foreach (var line in File.ReadLines(csv, encoding).Skip(1))
\r
185 var data = line.Split(',');
\r
186 if (!DateTime.TryParseExact(data[0], Logger.DateTimeFormat, CultureInfo.InvariantCulture,
\r
187 DateTimeStyles.AssumeLocal, out var date))
\r
189 // システムが和暦に設定されていて和暦が出力されてしまったケースを救う
\r
190 if (data[0][2] == '-')
\r
192 if (!int.TryParse(data[0].Substring(0, 2), out var year))
\r
194 data[0] = 1988 + year + data[0].Substring(2);
\r
195 if (!DateTime.TryParseExact(data[0], Logger.DateTimeFormat, CultureInfo.InvariantCulture,
\r
196 DateTimeStyles.AssumeLocal, out date))
\r
199 else if (DateTime.TryParse(data[0], CultureInfo.CurrentCulture,
\r
200 DateTimeStyles.AssumeLocal, out date))
\r
202 data[0] = Logger.FormatDateTime(date);
\r
209 if (date < from || to < date)
\r
211 IEnumerable<string> entries = data;
\r
213 entries = data.Take(9);
\r
215 entries = BattleLogProcessor.Process(data);
\r
216 if (entries.Count() != records)
\r
220 var stamp = ((date.ToUniversalTime().Ticks -
\r
221 new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).Ticks) /
\r
222 TimeSpan.TicksPerMillisecond).ToString();
\r
223 client.Send(encoding.GetBytes(delimiter + "[" + stamp + "," +
\r
224 string.Join(",", entries.Skip(1)) + "]"));
\r
228 client.Send(encoding.GetBytes(delimiter + "[\"" +
\r
229 string.Join("\",\"", entries) + "\"]"));
\r
233 if (material && !number)
\r
235 client.Send(encoding.GetBytes(delimiter + "[\"" +
\r
236 string.Join("\",\"", GetCurrentMaterialRecord()) + "\"]"));
\r
241 client.Send(encoding.GetBytes("]}\n"));
\r
245 private static IEnumerable<string> GetCurrentMaterialRecord()
\r
247 return new[] {Logger.FormatDateTime(DateTime.Now)}.Concat(MaterialHistory.Select(c => c.Now.ToString()));
\r
250 private static void SendFile(Socket client, string path, string mime)
\r
252 using (var header = new StreamWriter(new MemoryStream(), Encoding.ASCII))
\r
254 header.Write("HTTP/1.1 200 OK\r\n");
\r
255 header.Write("Server: KancolleSniffer\r\n");
\r
256 header.Write("Date: {0:R}\r\n", DateTime.Now);
\r
257 header.Write("Content-Length: {0}\r\n", new FileInfo(path).Length);
\r
258 header.Write("Content-Type: {0}\r\n", mime);
\r
259 header.Write("Connection: close\r\n\r\n");
\r
261 client.SendFile(path, ((MemoryStream)header.BaseStream).ToArray(), null,
\r
262 TransmitFileOptions.UseDefaultWorkerThread);
\r
266 private static void SendProxyPac(Socket client, int port)
\r
268 using (var header = new StreamWriter(new MemoryStream(), Encoding.ASCII))
\r
270 header.Write("HTTP/1.1 200 OK\r\n");
\r
271 header.Write("Server: KancolleSniffer\r\n");
\r
272 header.Write("Date: {0:R}\r\n", DateTime.Now);
\r
273 header.Write("Content-Type: application/x-ns-proxy-autoconfig\r\n");
\r
274 header.Write("Connection: close\r\n\r\n");
\r
276 client.Send(((MemoryStream)header.BaseStream).ToArray());
\r
281 pacFile = File.ReadAllText("proxy.pac").Replace("8080", port.ToString());
\r
287 client.Send(Encoding.ASCII.GetBytes(pacFile));
\r
291 public static class BattleLogProcessor
\r
293 public static IEnumerable<string> Process(string[] data)
\r
295 if (data.Length == 35)
\r
296 data = data.Concat(Enumerable.Repeat("", 3)).ToArray();
\r
297 if (data.Length == 40)
\r
299 data = data.Take(21).Concat(new[] {data[21] + "・" + data[23], data[22] + "・" + data[24]})
\r
300 .Concat(data.Skip(25)).ToArray();
\r
302 else if (data.Length != 38)
\r
306 if (data[5] == "T字戦(有利)")
\r
308 if (data[5] == "T字戦(不利)")
\r
310 if (data[6].EndsWith("航行序列"))
\r
311 data[6] = data[6].Substring(0, 4);
\r
312 if (data[7].EndsWith("航行序列"))
\r
313 data[7] = data[7].Substring(0, 4);
\r
314 data[37] = ShortenAirBattleResult(data[37]);
\r
315 return AddDamagedShip(data);
\r
318 private static string ShortenAirBattleResult(string result)
\r
337 private static IEnumerable<string> AddDamagedShip(string[] data)
\r
339 var damaged = new List<string>();
\r
340 for (var i = 11; i < 11 + 12; i += 2)
\r
344 var ship = data[i] = StripKana(data[i]);
\r
345 var hp = data[i + 1];
\r
348 damaged.AddRange(from entry in ship.Split('・').Zip(hp.Split('・'), (s, h) => new {s, h})
\r
349 where entry.h.Contains("/")
\r
350 let nm = entry.h.Split('/').Select(int.Parse).ToArray()
\r
351 where ShipStatus.CalcDamage(nm[0], nm[1]) == ShipStatus.Damage.Badly
\r
354 catch (FormatException)
\r
359 return data.Take(23).Concat(new[] {string.Join("・", damaged)}).Concat(data.Skip(23));
\r
362 private static readonly Regex Kana = new Regex(@"\([^)]+\)\(", RegexOptions.Compiled);
\r
364 private static string StripKana(string name)
\r
366 return Kana.Replace(name, "(");
\r