Skip to content

Commit a25c3da

Browse files
committed
MM patches history for part
1 parent 523ef9a commit a25c3da

8 files changed

Lines changed: 382 additions & 10 deletions

File tree

GameData/PartInfoInPAW/Localization/en-us.cfg

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,5 +33,20 @@ Localization
3333
#LOC_PartInfoInPAW_CopyToClipboard_PartOrigFile_FailureMsg = <color=#FF8000>Error: could not copy original CFG file (part <<1>>)!</color>
3434
#LOC_PartInfoInPAW_CantWriteToTempFile_PartCFG_FailureMsg = <color=#FF8000>Error: could not write part CFG to temporary file <<1>>!</color>
3535
#LOC_PartInfoInPAW_CantOpenFile_FailureMsg = <color=#FF8000>Error: could not open file <<1>> in default editor!</color>
36+
#LOC_PartInfoInPAW_ShowPartMMPatchesHistory_Action = MM patches history
37+
#LOC_PartInfoInPAW_PartMMPatchesHistory_Title = History of Module Manager patches applied to part<br><<1>>
38+
#LOC_PartInfoInPAW_CloseBtn = Close
39+
#LOC_PartInfoInPAW_PartMMPatchesHistory_StatusLogParsed = Part patches history is ready
40+
#LOC_PartInfoInPAW_PartMMPatchesHistory_PartPatchesMsg = <b>Part <<1>> : <<2>> MM patches applied:</b>
41+
#LOC_PartInfoInPAW_PartMMPatchesHistory_CopyPatchesHistoryFileBtn = Copy patches
42+
#LOC_PartInfoInPAW_PartMMPatchesHistory_OpenPatchesHistoryBtn = Open patches in editor
43+
#LOC_PartInfoInPAW_CantReadFile_FailureMsg = <color=#FF8000>Error: could not read file <<1>>!</color>
44+
#LOC_PartInfoInPAW_CantParsePartPatchesHistory_FailureMsg = <color=#FF8000>Error: could not get MM patches history for part <<1>>!</color>
45+
46+
#LOC_PartInfoInPAW_PartMMPatchesHistory_StatusNotInitialized = Module Manager log file not loaded
47+
#LOC_PartInfoInPAW_PartMMPatchesHistory_StatusLogsNotFound = <color=#FF8000>Module Manager log file not found</color>
48+
#LOC_PartInfoInPAW_PartMMPatchesHistory_StatusLoadingLogFile = Loading Module Manager log file...
49+
#LOC_PartInfoInPAW_PartMMPatchesHistory_StatusReadyToParse = Module Manager log file loaded
50+
#LOC_PartInfoInPAW_PartMMPatchesHistory_StatusParsingLog = Parsing Module Manager log file (<<1>>% done)
3651
}
3752
}

GameData/PartInfoInPAW/Localization/ru.cfg

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,5 +33,20 @@ Localization
3333
#LOC_PartInfoInPAW_CopyToClipboard_PartOrigFile_FailureMsg = <color=#FF8000>Ошибка: не удалось скопировать базовый CFG файл детали <<1>>!</color>
3434
#LOC_PartInfoInPAW_CantWriteToTempFile_PartCFG_FailureMsg = <color=#FF8000>Ошибка: не удалось записать конфиг детали во временный файл <<1>>!</color>
3535
#LOC_PartInfoInPAW_CantOpenFile_FailureMsg = <color=#FF8000>Ошибка: не удалось открыть файл <<1>> в редакторе по умолчанию!</color>
36+
#LOC_PartInfoInPAW_ShowPartMMPatchesHistory_Action = История MM патчей
37+
#LOC_PartInfoInPAW_PartMMPatchesHistory_Title = История применённых к детали<br><<1>><br>патчей Module Manager
38+
#LOC_PartInfoInPAW_CloseBtn = Закрыть
39+
#LOC_PartInfoInPAW_PartMMPatchesHistory_StatusLogParsed = История MM-патчей детали подготовлена
40+
#LOC_PartInfoInPAW_PartMMPatchesHistory_PartPatchesMsg = <b>Деталь <<1>> : применено <<2>> MM-патчей:</b>
41+
#LOC_PartInfoInPAW_PartMMPatchesHistory_CopyPatchesHistoryFileBtn = Скопировать патчи
42+
#LOC_PartInfoInPAW_PartMMPatchesHistory_OpenPatchesHistoryBtn = Открыть патчи в редакторе
43+
#LOC_PartInfoInPAW_CantReadFile_FailureMsg = <color=#FF8000>Ошибка: не удалось прочитать файл <<1>>!</color>
44+
#LOC_PartInfoInPAW_CantParsePartPatchesHistory_FailureMsg = <color=#FF8000>Ошибка: не удалось получить историю MM-патчей для детали <<1>>!</color>
45+
46+
#LOC_PartInfoInPAW_PartMMPatchesHistory_StatusNotInitialized = Лог-файл Module Manager не загружен
47+
#LOC_PartInfoInPAW_PartMMPatchesHistory_StatusLogsNotFound = <color=#FF8000>Не удалось найти лог-файл Module Manager</color>
48+
#LOC_PartInfoInPAW_PartMMPatchesHistory_StatusLoadingLogFile = Загрузка лог-файла Module Manager...
49+
#LOC_PartInfoInPAW_PartMMPatchesHistory_StatusReadyToParse = Лог-файл Module Manager загружен
50+
#LOC_PartInfoInPAW_PartMMPatchesHistory_StatusParsingLog = Анализируем лог-файл Module Manager (<<1>>% выполнено)
3651
}
3752
}

GameData/PartInfoInPAW/PartInfoInPAW.cfg

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,6 @@
66
showTWR = true
77
showGetInfo = true
88
showInfoInFlight = false
9+
startCollapsed = true
910
}
1011
}

src/GUI/MMPatchesHistoryDialog.cs

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using KSP.Localization;
4+
using UnityEngine;
5+
using UnityEngine.UI;
6+
7+
namespace PartInfoInPAW
8+
{
9+
public static class MMPatchesHistoryDialog
10+
{
11+
private static PopupDialog dialog;
12+
13+
public static async void ShowDialog(string partName, string partTitle)
14+
{
15+
List<MMPatchInfo> patchesList = new List<MMPatchInfo>();
16+
DialogGUIBase[] GUIPatchesList;
17+
UIStyle smallBtnStyle = new UIStyle(HighLogic.UISkin.button)
18+
{
19+
fontStyle = FontStyle.Normal,
20+
fontSize = 11
21+
};
22+
23+
try
24+
{
25+
patchesList = await MMLogParser.ParseLogFile(partName);
26+
}
27+
catch (Exception e)
28+
{
29+
Utils.LogError($"Part {partName} : failed to get MM patches history: " + e.Message);
30+
Utils.OnScreenMsg(Localizer.Format("#LOC_PartInfoInPAW_CantParsePartPatchesHistory_FailureMsg", partName));
31+
return;
32+
}
33+
if (MMLogParser.GetStatus() != MMLogParser.ParserStatus.LogParsed)
34+
{
35+
Utils.LogError($"Part {partName} : failed to get MM patches history");
36+
Utils.OnScreenMsg(Localizer.Format("#LOC_PartInfoInPAW_CantParsePartPatchesHistory_FailureMsg", partName));
37+
return;
38+
}
39+
40+
GUIPatchesList = new DialogGUIBase[patchesList.Count + 1];
41+
GUIPatchesList[0] = new DialogGUIContentSizer(ContentSizeFitter.FitMode.Unconstrained, ContentSizeFitter.FitMode.PreferredSize, true);
42+
for (int i = 1; i <= patchesList.Count; i++)
43+
{
44+
GUIPatchesList[i] = new DialogGUIVerticalLayout(
45+
true, false, 4, new RectOffset(5, 5, 5, 5),
46+
TextAnchor.UpperLeft,
47+
new DialogGUIHorizontalLayout(
48+
new DialogGUILabel(patchesList[i - 1].PatchFilePath + "<br> " + patchesList[i - 1].Patch, expandW: true),
49+
new DialogGUIFlexibleSpace(),
50+
new DialogGUIButton("C", () => { }, 30.0f, 20.0f, false, smallBtnStyle),
51+
new DialogGUIButton("E", () => { }, 30.0f, 20.0f, false, smallBtnStyle)
52+
)
53+
);
54+
}
55+
dialog = PopupDialog.SpawnPopupDialog(
56+
new Vector2(0.5f, 0.5f),
57+
new Vector2(0.5f, 0.5f),
58+
new MultiOptionDialog(
59+
"PartMMPatchesHistory",
60+
"",
61+
Localizer.Format("#LOC_PartInfoInPAW_PartMMPatchesHistory_Title", partTitle),
62+
HighLogic.UISkin,
63+
new Rect(0.5f, 0.5f, 1000f, 60f),
64+
new DialogGUIVerticalLayout(
65+
new DialogGUIHorizontalLayout(
66+
new DialogGUISpace(5f),
67+
new DialogGUILabel(MMLogParser.GetStatusMsg(partName),
68+
new UIStyle(HighLogic.UISkin.label),
69+
expandW: true
70+
),
71+
new DialogGUIFlexibleSpace(),
72+
new DialogGUIButton(
73+
Localizer.Format("#LOC_PartInfoInPAW_PartMMPatchesHistory_CopyPatchesHistoryFileBtn"),
74+
() => { },
75+
160.0f, 20.0f, false, smallBtnStyle
76+
),
77+
new DialogGUIButton(
78+
Localizer.Format("#LOC_PartInfoInPAW_PartMMPatchesHistory_OpenPatchesHistoryBtn"),
79+
() => { },
80+
160.0f, 20.0f, false, smallBtnStyle
81+
),
82+
new DialogGUISpace(5f)
83+
),
84+
new DialogGUIVerticalLayout(
85+
new DialogGUIScrollList(
86+
new Vector2(980f, 560f),
87+
false,
88+
true,
89+
new DialogGUIVerticalLayout(10, 100, 4, new RectOffset(5, 15, 0, 0), TextAnchor.UpperLeft, GUIPatchesList)
90+
)
91+
),
92+
new DialogGUIVerticalLayout(
93+
new DialogGUIHorizontalLayout(
94+
new DialogGUIFlexibleSpace(),
95+
new DialogGUIButton(
96+
Localizer.Format("#LOC_PartInfoInPAW_CloseBtn"),
97+
() => { },
98+
160.0f, 30.0f, true
99+
),
100+
new DialogGUIFlexibleSpace()
101+
)
102+
)
103+
)
104+
),
105+
false,
106+
HighLogic.UISkin
107+
);
108+
}
109+
}
110+
}

src/MMLogParser.cs

Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
using KSP.Localization;
2+
using System;
3+
using System.Collections.Generic;
4+
using System.IO;
5+
using System.Text;
6+
using System.Text.RegularExpressions;
7+
using System.Threading.Tasks;
8+
9+
namespace PartInfoInPAW
10+
{
11+
public delegate void PartPatchesHistoryCallback(List<MMPatchInfo> patchesList);
12+
13+
public static class MMLogParser
14+
{
15+
public enum ParserStatus
16+
{
17+
NotInitialized,
18+
LogsNotFound,
19+
LoadingLogFile,
20+
ReadyToParse,
21+
ParsingLog,
22+
LogParsed
23+
}
24+
25+
private static ParserStatus status = ParserStatus.NotInitialized;
26+
private static string[] LogFileLines;
27+
28+
private static Dictionary<string, List<MMPatchInfo>> PatchesHistoryDict = new Dictionary<string, List<MMPatchInfo>>();
29+
30+
public static ParserStatus GetStatus()
31+
{
32+
return status;
33+
}
34+
35+
public static string GetStatusMsg(string partName)
36+
{
37+
switch (status)
38+
{
39+
case ParserStatus.LogParsed:
40+
if (PatchesHistoryDict.ContainsKey(partName))
41+
{
42+
return Localizer.Format("#LOC_PartInfoInPAW_PartMMPatchesHistory_PartPatchesMsg", partName, PatchesHistoryDict[partName].Count.ToString());
43+
}
44+
else
45+
{
46+
return Localizer.Format("#LOC_PartInfoInPAW_PartMMPatchesHistory_StatusLogParsed");
47+
}
48+
default:
49+
return "";
50+
}
51+
}
52+
53+
public static async Task Initialize()
54+
{
55+
if (status != ParserStatus.NotInitialized) return;
56+
string LogFilePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Logs", "ModuleManager", "MMPatch.log");
57+
if (!File.Exists(LogFilePath))
58+
{
59+
LogFilePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Logs", "ModuleManager", "ModuleManager.log");
60+
if (!File.Exists(LogFilePath))
61+
{
62+
status = ParserStatus.LogsNotFound;
63+
Utils.LogWarning("No Module Manager log file found");
64+
return;
65+
}
66+
}
67+
status = ParserStatus.LoadingLogFile;
68+
Utils.Log($"Reading Module Manager log file from {LogFilePath.Replace('\\', '/')}");
69+
await ReadLogFileLinesAsync(LogFilePath);
70+
}
71+
72+
private static async Task ReadLogFileLinesAsync(string filePath)
73+
{
74+
await Task.Run(() => {
75+
try
76+
{
77+
// memory intensive, but fast
78+
LogFileLines = File.ReadAllLines(filePath, Encoding.UTF8);
79+
Utils.Log($"Finished reading Module Manager log file from {filePath.Replace('\\', '/')}");
80+
status = ParserStatus.ReadyToParse;
81+
}
82+
catch (Exception e)
83+
{
84+
Utils.LogError($"Could not read file {filePath.Replace('\\', '/')} : " + e.Message);
85+
Utils.OnScreenMsg(Localizer.Format("#LOC_PartInfoInPAW_CantReadFile_FailureMsg", filePath));
86+
}
87+
});
88+
}
89+
90+
public static async Task<List<MMPatchInfo>> ParseLogFile(string partName)
91+
{
92+
await Initialize();
93+
List<MMPatchInfo> patches = new List<MMPatchInfo>();
94+
if ((status != ParserStatus.LogParsed) && (status != ParserStatus.ReadyToParse))
95+
{
96+
return patches;
97+
}
98+
if (PatchesHistoryDict.ContainsKey(partName))
99+
{
100+
Utils.LogDebugMsg($"Patches history for part {partName} found in cache");
101+
patches = PatchesHistoryDict[partName];
102+
status = ParserStatus.LogParsed;
103+
}
104+
else
105+
{
106+
status = ParserStatus.ParsingLog;
107+
Utils.LogDebugMsg($"Parsing MM log file for part {partName}");
108+
int lineNum = 0;
109+
int totalLinesCount = LogFileLines.Length;
110+
if (totalLinesCount > 0)
111+
{
112+
string searchStr = "/PART[" + @partName + "]";
113+
foreach (string line in LogFileLines)
114+
{
115+
if (line.IndexOf(searchStr) != -1)
116+
{
117+
Match m = Regex.Match(line.Trim(), RegexPattern(partName));
118+
if (m.Success)
119+
{
120+
patches.Add(new MMPatchInfo(MMPatchInfo.AddExtension(m.Groups[1].ToString()), m.Groups[2].ToString()));
121+
Utils.LogDebugMsg($"Found patch for part {partName} in MM log file");
122+
}
123+
}
124+
lineNum++;
125+
}
126+
}
127+
PatchesHistoryDict.Add(partName, patches);
128+
Utils.LogDebugMsg($"Finished parsing MM log file for part {partName}");
129+
status = ParserStatus.LogParsed;
130+
}
131+
return patches;
132+
}
133+
134+
public static string GetPatchesHistoryAsStr(string partName)
135+
{
136+
if (PatchesHistoryDict.ContainsKey(partName))
137+
{
138+
List<MMPatchInfo> patches = PatchesHistoryDict[partName];
139+
string result = $"Patches for part {partName}: {patches.Count}" + Environment.NewLine + Environment.NewLine;
140+
foreach (MMPatchInfo m in patches)
141+
{
142+
result += m;
143+
}
144+
return result;
145+
}
146+
else
147+
{
148+
return $"Patches count for part {partName}: 0" + Environment.NewLine;
149+
}
150+
}
151+
152+
private static string RegexPattern(string partName)
153+
{
154+
return @"^\[LOG \d{2}:\d{2}:\d{2}\.\d{3}\] Applying update (.+)/(@PART\[.+) to .+/PART\[" + Regex.Escape(partName) + @"\]$";
155+
}
156+
}
157+
158+
public class MMPatchInfo
159+
{
160+
public string PatchFilePath { get; protected set; }
161+
public string Patch { get; protected set; }
162+
163+
public MMPatchInfo(string patchFilePath, string patch)
164+
{
165+
PatchFilePath = patchFilePath;
166+
Patch = patch;
167+
}
168+
169+
public static string AddExtension(string patch)
170+
{
171+
string result = patch;
172+
if (result[0] == '/')
173+
{
174+
result = result.Substring(1);
175+
}
176+
result += "." + UrlDir.configExtension;
177+
return result;
178+
}
179+
180+
public override string ToString()
181+
{
182+
return PatchFilePath + Environment.NewLine + "\t" + Patch + Environment.NewLine;
183+
}
184+
}
185+
}

0 commit comments

Comments
 (0)