OSDN Git Service

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