OSDN Git Service

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