OSDN Git Service

Fleetクラスで艦隊編成を行いShipStatusのリストを構築する
[kancollesniffer/KancolleSniffer.git] / KancolleSniffer / Logger.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.Text;\r
21 using KancolleSniffer.Model;\r
22 using KancolleSniffer.Util;\r
23 \r
24 namespace KancolleSniffer\r
25 {\r
26     [Flags]\r
27     public enum LogType\r
28     {\r
29         None = 0,\r
30         Mission = 1 << 0,\r
31         Battle = 1 << 1,\r
32         Material = 1 << 2,\r
33         CreateItem = 1 << 3,\r
34         CreateShip = 1 << 4,\r
35         RemodelSlot = 1 << 5,\r
36         Achivement = 1 << 6,\r
37         All = (1 << 7) - 1\r
38     }\r
39 \r
40     public class Logger\r
41     {\r
42         private LogType _logType;\r
43         private readonly ShipInfo _shipInfo;\r
44         private readonly ItemInfo _itemInfo;\r
45         private readonly BattleInfo _battleInfo;\r
46         private Action<string, string, string> _writer;\r
47         private Func<DateTime> _nowFunc;\r
48         public const string DateTimeFormat = @"yyyy\-MM\-dd HH\:mm\:ss";\r
49         private dynamic _battle;\r
50         private dynamic _map;\r
51         private dynamic _basic;\r
52         private int _kdockId;\r
53         private DateTime _prevTime;\r
54         private int[] _currentMaterial = new int[Enum.GetValues(typeof(Material)).Length];\r
55         private int _materialLogInterval = 10;\r
56         private bool _start;\r
57         private int _lastExp = -1;\r
58         private DateTime _lastDate;\r
59         private DateTime _endOfMonth;\r
60         private DateTime _nextDate;\r
61 \r
62         public int MaterialLogInterval\r
63         {\r
64             set => _materialLogInterval = value;\r
65         }\r
66 \r
67         public string OutputDir\r
68         {\r
69             set => _writer = new LogWriter(value).Write;\r
70         }\r
71 \r
72         public static string FormatDateTime(DateTime date)\r
73         {\r
74             return date.ToString(DateTimeFormat, CultureInfo.InvariantCulture);\r
75         }\r
76 \r
77         public Logger(ShipInfo ship, ItemInfo item, BattleInfo battle)\r
78         {\r
79             _shipInfo = ship;\r
80             _itemInfo = item;\r
81             _battleInfo = battle;\r
82             _writer = new LogWriter().Write;\r
83             _nowFunc = () => DateTime.Now;\r
84         }\r
85 \r
86         public void EnableLog(LogType type)\r
87         {\r
88             _logType = type;\r
89         }\r
90 \r
91         public void SetWriter(Action<string, string, string> writer, Func<DateTime> nowFunc)\r
92         {\r
93             _writer = writer;\r
94             _nowFunc = nowFunc;\r
95         }\r
96 \r
97         public void FlashLog()\r
98         {\r
99             FlashAchivementLog();\r
100         }\r
101 \r
102         public void InspectMissionResult(dynamic json)\r
103         {\r
104             var r = (int)json.api_clear_result;\r
105             var rstr = r == 2 ? "大成功" : r == 1 ? "成功" : "失敗";\r
106             var material = new int[7];\r
107             if (r != 0)\r
108                 ((int[])json.api_get_material).CopyTo(material, 0);\r
109             foreach (var i in new[] {1, 2})\r
110             {\r
111                 var attr = "api_get_item" + i;\r
112                 if (!json.IsDefined(attr) || json[attr].api_useitem_id != -1)\r
113                     continue;\r
114                 var count = (int)json[attr].api_useitem_count;\r
115                 var flag = ((int[])json.api_useitem_flag)[i - 1];\r
116                 if (flag == 1)\r
117                     material[(int)Material.Bucket] = count;\r
118                 else if (flag == 2)\r
119                     material[(int)Material.Burner + 2] = count; // 高速建造材と開発資材が反対なのでいつか直す\r
120                 else if (flag == 3)\r
121                     material[(int)Material.Development - 2] = count;\r
122             }\r
123             if ((_logType & LogType.Mission) != 0)\r
124             {\r
125                 _writer("遠征報告書",\r
126                     string.Join(",", FormatDateTime(_nowFunc()),\r
127                         rstr, json.api_quest_name, string.Join(",", material)),\r
128                     "日付,結果,遠征,燃料,弾薬,鋼材,ボーキ,開発資材,高速修復材,高速建造材");\r
129             }\r
130         }\r
131 \r
132         public void InspectMapStart(dynamic json)\r
133         {\r
134             _start = true;\r
135             _map = json;\r
136             _battle = null;\r
137             if ((_logType & LogType.Material) != 0)\r
138                 WriteMaterialLog(_nowFunc());\r
139         }\r
140 \r
141         public void InspectMapNext(dynamic json)\r
142         {\r
143             if ((_logType & LogType.Achivement) != 0 && json.api_get_eo_rate() && (int)json.api_get_eo_rate != 0)\r
144             {\r
145                 _writer("戦果",\r
146                     FormatDateTime(_nowFunc()) + "," + _lastExp + "," + (int)json.api_get_eo_rate,\r
147                     "日付,経験値,EO");\r
148             }\r
149             _map = json;\r
150         }\r
151 \r
152         public void InspectClearItemGet(dynamic json)\r
153         {\r
154             if ((_logType & LogType.Achivement) == 0)\r
155                 return;\r
156             if (!json.api_bounus())\r
157                 return;\r
158             foreach (var entry in json.api_bounus)\r
159             {\r
160                 if (entry.api_type != 18)\r
161                     continue;\r
162                 _writer("戦果",\r
163                     FormatDateTime(_nowFunc()) + "," + _lastExp + "," + (int)entry.api_count,\r
164                     "日付,経験値,EO");\r
165                 break;\r
166             }\r
167         }\r
168 \r
169         public void InspectBattle(dynamic json)\r
170         {\r
171             if (_battle != null) // 通常の夜戦は無視する\r
172                 return;\r
173             _battle = json;\r
174         }\r
175 \r
176         public void InspectBattleResult(dynamic result)\r
177         {\r
178             if ((_logType & LogType.Achivement) != 0 && result.api_get_exmap_rate())\r
179             {\r
180                 var rate = result.api_get_exmap_rate is string\r
181                     ? int.Parse(result.api_get_exmap_rate)\r
182                     : (int)result.api_get_exmap_rate;\r
183                 if (rate != 0)\r
184                 {\r
185                     _writer("戦果", FormatDateTime(_nowFunc()) + "," + _lastExp + "," + rate,\r
186                         "日付,経験値,EO");\r
187                 }\r
188             }\r
189             if ((_logType & LogType.Battle) == 0 || _map == null || _battle == null)\r
190             {\r
191                 _map = _battle = null;\r
192                 return;\r
193             }\r
194             var fships = GenerateFirendShipList();\r
195             var eships = GenerateEnemyShipList();\r
196             var cell = (int)_map.api_no;\r
197             var boss = "";\r
198             if (_start)\r
199                 boss = "出撃";\r
200             if (cell == (int)_map.api_bosscell_no || (int)_map.api_event_id == 5)\r
201                 boss = _start ? "出撃&ボス" : "ボス";\r
202             var dropType = result.api_get_ship() ? result.api_get_ship.api_ship_type : "";\r
203             if (result.api_get_useitem())\r
204             {\r
205                 if (dropType == "")\r
206                     dropType = "アイテム";\r
207                 else\r
208                     dropType += "+アイテム";\r
209             }\r
210             var dropName = result.api_get_ship() ? result.api_get_ship.api_ship_name : "";\r
211             if (result.api_get_useitem())\r
212             {\r
213                 var itemName = _itemInfo.GetUseItemName((int)result.api_get_useitem.api_useitem_id);\r
214                 if (dropName == "")\r
215                     dropName = itemName;\r
216                 else\r
217                     dropName += "+" + itemName;\r
218             }\r
219             var fp = _shipInfo.Fleets[BattleInfo.DeckId(_battle)].FighterPower;\r
220             var fpower = fp[0] == fp[1] ? fp[0].ToString() : fp[0] + "~" + fp[1];\r
221             _writer("海戦・ドロップ報告書", string.Join(",", FormatDateTime(_nowFunc()),\r
222                     result.api_quest_name,\r
223                     cell, boss,\r
224                     result.api_win_rank,\r
225                     BattleFormationName((int)_battle.api_formation[2]),\r
226                     FormationName(_battle.api_formation[0]),\r
227                     FormationName(_battle.api_formation[1]),\r
228                     result.api_enemy_info.api_deck_name,\r
229                     dropType, dropName,\r
230                     string.Join(",", fships),\r
231                     string.Join(",", eships),\r
232                     fpower, _battleInfo.EnemyFighterPower.AirCombat + _battleInfo.EnemyFighterPower.UnknownMark,\r
233                     AirControlLevelName(_battle)),\r
234                 "日付,海域,マス,ボス,ランク,艦隊行動,味方陣形,敵陣形,敵艦隊,ドロップ艦種,ドロップ艦娘," +\r
235                 "味方艦1,味方艦1HP,味方艦2,味方艦2HP,味方艦3,味方艦3HP,味方艦4,味方艦4HP,味方艦5,味方艦5HP,味方艦6,味方艦6HP," +\r
236                 "敵艦1,敵艦1HP,敵艦2,敵艦2HP,敵艦3,敵艦3HP,敵艦4,敵艦4HP,敵艦5,敵艦5HP,敵艦6,敵艦6HP," +\r
237                 "味方制空値,敵制空値,制空状態"\r
238             );\r
239             _map = _battle = null;\r
240             _start = false;\r
241         }\r
242 \r
243         private IEnumerable<string> GenerateFirendShipList()\r
244         {\r
245             int deckId = BattleInfo.DeckId(_battle);\r
246             if (_battle.api_f_nowhps_combined())\r
247             {\r
248                 var mainShips = _shipInfo.Fleets[0].Ships;\r
249                 var guardShips = _shipInfo.Fleets[1].Ships;\r
250                 return mainShips.Zip(guardShips, (main, guard) =>\r
251                 {\r
252                     if (main.Empty && guard.Empty)\r
253                         return ",";\r
254                     var name = "";\r
255                     var hp = "";\r
256                     if (!main.Empty)\r
257                     {\r
258                         name = $"{main.Name}(Lv{main.Level})";\r
259                         hp = $"{main.NowHp}/{main.MaxHp}";\r
260                     }\r
261                     name += "・";\r
262                     hp += "・";\r
263                     if (!guard.Empty)\r
264                     {\r
265                         name += $"{guard.Name}(Lv{guard.Level})";\r
266                         hp += $"{guard.NowHp}/{guard.MaxHp}";\r
267                     }\r
268                     return name + "," + hp;\r
269                 }).ToList();\r
270             }\r
271             var ships = _shipInfo.Fleets[deckId].Ships;\r
272             if (ships.Count > 6)\r
273             {\r
274                 var result = new List<string>();\r
275                 for (var i = 0; i < 12 - ships.Count; i++)\r
276                 {\r
277                     var ship = ships[i];\r
278                     result.Add($"{ship.Name}(Lv{ship.Level}),{ship.NowHp}/{ship.MaxHp}");\r
279                 }\r
280                 for (var i = 0; i < ships.Count - 6; i++)\r
281                 {\r
282                     var s1 = ships[12 - ships.Count + i];\r
283                     var s2 = ships[6 + i];\r
284                     result.Add(\r
285                         $"{s1.Name}(Lv{s1.Level})・{s2.Name}(Lv{s2.Level})," +\r
286                         $"{s1.NowHp}/{s1.MaxHp}・{s2.NowHp}/{s2.MaxHp}");\r
287                 }\r
288                 return result;\r
289             }\r
290             return ships.Select(ship =>\r
291             {\r
292                 if (ship.Empty)\r
293                     return ",";\r
294                 return $"{ship.Name}(Lv{ship.Level}),{ship.NowHp}/{ship.MaxHp}";\r
295             }).ToList();\r
296         }\r
297 \r
298         private IEnumerable<string> GenerateEnemyShipList()\r
299         {\r
300             var result = _battleInfo.Result.Enemy.Main.Concat(Enumerable.Repeat(new ShipStatus(), 6)).Take(6);\r
301             if (_battleInfo.Result.Enemy.Guard.Length == 0)\r
302             {\r
303                 return result.Select(s => s.Empty ? "," : $"{s.Name},{s.NowHp}/{s.MaxHp}").ToList();\r
304             }\r
305             var mainShips = result;\r
306             var guardShips = _battleInfo.Result.Enemy.Guard.Concat(Enumerable.Repeat(new ShipStatus(), 6)).Take(6);\r
307             return mainShips.Zip(guardShips, (main, guard) =>\r
308             {\r
309                 if (main.Empty && guard.Empty)\r
310                     return ",";\r
311                 var name = "";\r
312                 var hp = "";\r
313                 if (!main.Empty)\r
314                 {\r
315                     name = $"{main.Name}";\r
316                     hp = $"{main.NowHp}/{main.MaxHp}";\r
317                 }\r
318                 name += "・";\r
319                 hp += "・";\r
320                 if (!guard.Empty)\r
321                 {\r
322                     name += $"{guard.Name}";\r
323                     hp += $"{guard.NowHp}/{guard.MaxHp}";\r
324                 }\r
325                 return name + "," + hp;\r
326             }).ToList();\r
327         }\r
328 \r
329         private string FormationName(dynamic f)\r
330         {\r
331             if (f is string) // 連合艦隊のときは文字列\r
332                 f = int.Parse(f);\r
333             switch ((int)f)\r
334             {\r
335                 case 1:\r
336                     return "単縦陣";\r
337                 case 2:\r
338                     return "複縦陣";\r
339                 case 3:\r
340                     return "輪形陣";\r
341                 case 4:\r
342                     return "梯形陣";\r
343                 case 5:\r
344                     return "単横陣";\r
345                 case 6:\r
346                     return "警戒陣";\r
347                 case 11:\r
348                     return "第一警戒航行序列";\r
349                 case 12:\r
350                     return "第二警戒航行序列";\r
351                 case 13:\r
352                     return "第三警戒航行序列";\r
353                 case 14:\r
354                     return "第四警戒航行序列";\r
355                 default:\r
356                     return "単縦陣";\r
357             }\r
358         }\r
359 \r
360         private static string BattleFormationName(int f)\r
361         {\r
362             switch (f)\r
363             {\r
364                 case 1:\r
365                     return "同航戦";\r
366                 case 2:\r
367                     return "反航戦";\r
368                 case 3:\r
369                     return "T字戦(有利)";\r
370                 case 4:\r
371                     return "T字戦(不利)";\r
372                 default:\r
373                     return "同航戦";\r
374             }\r
375         }\r
376 \r
377         private string AirControlLevelName(dynamic json)\r
378         {\r
379             // BattleInfo.AirControlLevelは夜戦で消されているかもしれないので、こちらで改めて調べる。\r
380             if (!json.api_kouku())\r
381                 return "";\r
382             var stage1 = json.api_kouku.api_stage1;\r
383             if (stage1 == null)\r
384                 return "";\r
385             if (stage1.api_f_count == 0 && stage1.api_e_count == 0)\r
386                 return "";\r
387             switch ((int)stage1.api_disp_seiku)\r
388             {\r
389                 case 0:\r
390                     return "航空均衡";\r
391                 case 1:\r
392                     return "制空権確保";\r
393                 case 2:\r
394                     return "航空優勢";\r
395                 case 3:\r
396                     return "航空劣勢";\r
397                 case 4:\r
398                     return "制空権喪失";\r
399                 default:\r
400                     return "";\r
401             }\r
402         }\r
403 \r
404         public void InspectBasic(dynamic json)\r
405         {\r
406             _basic = json;\r
407             if ((_logType & LogType.Achivement) == 0)\r
408                 return;\r
409             var now = _nowFunc();\r
410             var exp = (int)json.api_experience;\r
411             var isNewMonth = _endOfMonth == DateTime.MinValue || now.CompareTo(_endOfMonth) >= 0;\r
412             var isNewDate = _nextDate == DateTime.MinValue || now.CompareTo(_nextDate) >= 0;\r
413             if (isNewDate || isNewMonth)\r
414             {\r
415                 if (_lastDate != DateTime.MinValue)\r
416                 {\r
417                     _writer("戦果", FormatDateTime(_lastDate) + "," + _lastExp + ",0", "日付,経験値,EO");\r
418                 }\r
419                 _writer("戦果", FormatDateTime(now) + "," + exp + ",0", "日付,経験値,EO");\r
420                 if (isNewMonth)\r
421                 {\r
422                     _endOfMonth = new DateTime(now.Year, now.Month, DateTime.DaysInMonth(now.Year, now.Month),\r
423                         22, 0, 0);\r
424                     if (_endOfMonth.CompareTo(now) <= 0)\r
425                     {\r
426                         var days = _endOfMonth.Month == 12\r
427                             ? DateTime.DaysInMonth(_endOfMonth.Year + 1, 1)\r
428                             : DateTime.DaysInMonth(_endOfMonth.Year, _endOfMonth.Month);\r
429                         _endOfMonth = _endOfMonth.AddDays(days);\r
430                     }\r
431                 }\r
432                 _nextDate = new DateTime(now.Year, now.Month, now.Day, 2, 0, 0);\r
433                 if (now.Hour >= 2)\r
434                     _nextDate = _nextDate.AddDays(1);\r
435                 if (_nextDate.Day == 1)\r
436                     _nextDate = _nextDate.AddDays(1);\r
437             }\r
438             _lastDate = now;\r
439             _lastExp = exp;\r
440         }\r
441 \r
442         private void FlashAchivementLog()\r
443         {\r
444             if ((_logType & LogType.Achivement) == 0)\r
445                 return;\r
446             if (_lastDate != DateTime.MinValue)\r
447             {\r
448                 _writer("戦果", FormatDateTime(_lastDate) + "," + _lastExp + ",0", "日付,経験値,EO");\r
449             }\r
450         }\r
451 \r
452         public void InspectCreateItem(string request, dynamic json)\r
453         {\r
454             if ((_logType & LogType.CreateItem) == 0)\r
455                 return;\r
456             var values = HttpUtility.ParseQueryString(request);\r
457             var name = "失敗";\r
458             var type = "";\r
459             if (json.api_slot_item())\r
460             {\r
461                 var spec = _itemInfo.GetSpecByItemId((int)json.api_slot_item.api_slotitem_id);\r
462                 name = spec.Name;\r
463                 type = spec.TypeName;\r
464             }\r
465             _writer("開発報告書",\r
466                 FormatDateTime(_nowFunc()) + "," +\r
467                 string.Join(",", name, type,\r
468                     values["api_item1"], values["api_item2"], values["api_item3"], values["api_item4"],\r
469                     Secretary(), _basic.api_level),\r
470                 "日付,開発装備,種別,燃料,弾薬,鋼材,ボーキ,秘書艦,司令部Lv");\r
471         }\r
472 \r
473         public void InspectCreateShip(string request)\r
474         {\r
475             var values = HttpUtility.ParseQueryString(request);\r
476             _kdockId = int.Parse(values["api_kdock_id"]);\r
477         }\r
478 \r
479         public void InspectKDock(dynamic json)\r
480         {\r
481             if ((_logType & LogType.CreateShip) == 0 || _basic == null || _kdockId == 0)\r
482                 return;\r
483             var kdock = ((dynamic[])json).First(e => e.api_id == _kdockId);\r
484             var material = Enumerable.Range(1, 5).Select(i => (int)kdock["api_item" + i]).ToArray();\r
485             var ship = _shipInfo.GetSpec((int)kdock.api_created_ship_id);\r
486             var avail = ((dynamic[])json).Count(e => (int)e.api_state == 0);\r
487             _writer("建造報告書",\r
488                 FormatDateTime(_nowFunc()) + "," +\r
489                 string.Join(",", material.First() >= 1500 ? "大型艦建造" : "通常艦建造",\r
490                     ship.Name, ship.ShipTypeName, string.Join(",", material), avail, Secretary(), _basic.api_level),\r
491                 "日付,種類,名前,艦種,燃料,弾薬,鋼材,ボーキ,開発資材,空きドック,秘書艦,司令部Lv");\r
492             _kdockId = 0;\r
493         }\r
494 \r
495         private string Secretary()\r
496         {\r
497             var ship = _shipInfo.Fleets[0].Ships[0];\r
498             return ship.Name + "(" + ship.Level + ")";\r
499         }\r
500 \r
501         public void InspectMaterial(dynamic json)\r
502         {\r
503             if ((_logType & LogType.Material) == 0)\r
504                 return;\r
505             foreach (var e in json)\r
506                 _currentMaterial[(int)e.api_id - 1] = (int)e.api_value;\r
507             var now = _nowFunc();\r
508             if (now - _prevTime < TimeSpan.FromMinutes(_materialLogInterval))\r
509                 return;\r
510             WriteMaterialLog(now);\r
511         }\r
512 \r
513         public void WriteMaterialLog(DateTime now)\r
514         {\r
515             _prevTime = now;\r
516             _writer("資材ログ",\r
517                 FormatDateTime(now) + "," +\r
518                 string.Join(",", _currentMaterial),\r
519                 "日付,燃料,弾薬,鋼材,ボーキ,高速建造材,高速修復材,開発資材,改修資材");\r
520         }\r
521 \r
522         public void SetCurrentMaterial(int[] material)\r
523         {\r
524             _currentMaterial = material;\r
525         }\r
526 \r
527         public void InspectRemodelSlot(string request, dynamic json)\r
528         {\r
529             if ((_logType & LogType.RemodelSlot) == 0)\r
530                 return;\r
531             var now = _nowFunc();\r
532             var values = HttpUtility.ParseQueryString(request);\r
533             var id = int.Parse(values["api_slot_id"]);\r
534             var name = _itemInfo.GetName(id);\r
535             var level = _itemInfo.GetStatus(id).Level;\r
536             var success = (int)json.api_remodel_flag == 1 ? "○" : "×";\r
537             var certain = int.Parse(values["api_certain_flag"]) == 1 ? "○" : "";\r
538             var useName = "";\r
539             var useNum = "";\r
540             if (json.api_use_slot_id())\r
541             {\r
542                 var use = (int[])json.api_use_slot_id;\r
543                 useName = _itemInfo.GetName(use[0]);\r
544                 useNum = use.Length.ToString();\r
545             }\r
546             var after = (int[])json.api_after_material;\r
547             var diff = new int[after.Length];\r
548             for (var i = 0; i < after.Length; i++)\r
549                 diff[i] = _currentMaterial[i] - after[i];\r
550             var ship1 = Secretary();\r
551             var ship2 = "";\r
552             var ships = _shipInfo.Fleets[0].Ships;\r
553             if (!ships[1].Empty)\r
554                 ship2 = ships[1].Name + "(" + ships[1].Level + ")";\r
555             _writer("改修報告書",\r
556                 FormatDateTime(now) + "," +\r
557                 string.Join(",", name, level, success, certain, useName, useNum,\r
558                     diff[(int)Material.Fuel], diff[(int)Material.Bullet], diff[(int)Material.Steal],\r
559                     diff[(int)Material.Bouxite],\r
560                     diff[(int)Material.Development], diff[(int)Material.Screw],\r
561                     ship1, ship2),\r
562                 "日付,改修装備,レベル,成功,確実化,消費装備,消費数,燃料,弾薬,鋼材,ボーキ,開発資材,改修資材,秘書艦,二番艦");\r
563         }\r
564     }\r
565 \r
566     public class LogWriter\r
567     {\r
568         private readonly IFile _file;\r
569         private readonly string _outputDir;\r
570 \r
571         public interface IFile\r
572         {\r
573             string ReadAllText(string path);\r
574             void AppendAllText(string path, string text);\r
575             void Delete(string path);\r
576             bool Exists(string path);\r
577         }\r
578 \r
579         private class FileWrapper : IFile\r
580         {\r
581             // Shift_JISでないとExcelで文字化けする\r
582             private readonly Encoding _encoding = Encoding.GetEncoding("Shift_JIS");\r
583 \r
584             public string ReadAllText(string path) => File.ReadAllText(path, _encoding);\r
585 \r
586             public void AppendAllText(string path, string text)\r
587             {\r
588                 File.AppendAllText(path, text, _encoding);\r
589             }\r
590 \r
591             public void Delete(string path)\r
592             {\r
593                 File.Delete(path);\r
594             }\r
595 \r
596             public bool Exists(string path) => File.Exists(path);\r
597         }\r
598 \r
599         public LogWriter(string outputDir = null, IFile file = null)\r
600         {\r
601             _outputDir = outputDir ?? AppDomain.CurrentDomain.BaseDirectory;\r
602             _file = file ?? new FileWrapper();\r
603         }\r
604 \r
605         public void Write(string file, string s, string header)\r
606         {\r
607             var path = Path.Combine(_outputDir, file);\r
608             var csv = path + ".csv";\r
609             var tmp = path + ".tmp";\r
610             if (_file.Exists(tmp))\r
611             {\r
612                 try\r
613                 {\r
614                     _file.AppendAllText(csv, _file.ReadAllText(tmp));\r
615                     _file.Delete(tmp);\r
616                 }\r
617                 catch (IOException)\r
618                 {\r
619                 }\r
620             }\r
621             if (!_file.Exists(csv))\r
622                 s = header + "\r\n" + s;\r
623             foreach (var f in new[] {csv, tmp})\r
624             {\r
625                 try\r
626                 {\r
627                     _file.AppendAllText(f, s + "\r\n");\r
628                     break;\r
629                 }\r
630                 catch (IOException e)\r
631                 {\r
632                     if (f == tmp)\r
633                         throw new LogIOException("報告書の出力中にエラーが発生しました。", e);\r
634                 }\r
635             }\r
636         }\r
637     }\r
638 \r
639     public class LogIOException : Exception\r
640     {\r
641         public LogIOException()\r
642         {\r
643         }\r
644 \r
645         public LogIOException(string message) : base(message)\r
646         {\r
647         }\r
648 \r
649         public LogIOException(string message, Exception inner) : base(message, inner)\r
650         {\r
651         }\r
652     }\r
653 }