• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

ParadoxGameConverters / Fronter.NET / 21102980608

18 Jan 2026 12:09AM UTC coverage: 22.911% (+0.02%) from 22.891%
21102980608

Pull #931

github

web-flow
Merge 2182743db into 16c5617d5
Pull Request #931: Use AppContext.BaseDirectory for reading configuration files

143 of 770 branches covered (18.57%)

Branch coverage included in aggregate %.

3 of 7 new or added lines in 2 files covered. (42.86%)

49 existing lines in 1 file now uncovered.

718 of 2988 relevant lines covered (24.03%)

8.87 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

28.87
/Fronter.NET/Models/Configuration/Config.cs
1
using Avalonia.Controls.ApplicationLifetimes;
2
using commonItems;
3
using Fronter.Models.Configuration.Options;
4
using Fronter.ViewModels;
5
using log4net;
6
using System;
7
using System.Collections.Generic;
8
using System.Collections.ObjectModel;
9
using System.IO;
10
using System.Linq;
11

12
namespace Fronter.Models.Configuration;
13

14
internal sealed class Config {
15
        public string Name { get; private set; } = string.Empty;
13✔
16
        public string ConverterFolder { get; private set; } = string.Empty;
29✔
17
        public string BackendExePath { get; private set; } = string.Empty; // relative to ConverterFolder
13✔
18
        public string DisplayName { get; private set; } = string.Empty;
13✔
19
        public string SourceGame { get; private set; } = string.Empty;
13✔
20
        public string TargetGame { get; private set; } = string.Empty;
13✔
21
        public string? SentryDsn { get; private set; }
×
22
        public string? ModAutoGenerationSource { get; private set; } = null;
14✔
23
        public ObservableCollection<Mod> AutoLocatedMods { get; } = [];
16✔
24
        public bool CopyToTargetGameModDirectory { get; set; } = true;
6✔
25
        public ushort ProgressOnCopyingComplete { get; set; } = 109;
12✔
26
        public bool UpdateCheckerEnabled { get; private set; } = false;
13✔
27
        public bool CheckForUpdatesOnStartup { get; private set; } = false;
13✔
28
        public string ConverterReleaseForumThread { get; private set; } = string.Empty;
13✔
29
        public string LatestGitHubConverterReleaseUrl { get; private set; } = string.Empty;
13✔
30
        public string PagesCommitIdUrl { get; private set; } = string.Empty;
13✔
31
        public List<RequiredFile> RequiredFiles { get; } = [];
135✔
32
        public List<RequiredFolder> RequiredFolders { get; } = [];
153✔
33
        public List<Option> Options { get; } = [];
192✔
34
        private int optionCounter;
35

36
        private static readonly ILog logger = LogManager.GetLogger("Configuration");
1✔
37

38
        public Config() {
12✔
39
                var parser = new Parser();
6✔
40
                RegisterKeys(parser);
6✔
41
                var fronterConfigurationPath = Path.Combine(AppContext.BaseDirectory, "Configuration", "fronter-configuration.txt");
6✔
42
                if (File.Exists(fronterConfigurationPath)) {
12!
43
                        parser.ParseFile(fronterConfigurationPath);
6✔
44
                        logger.Info("Frontend configuration loaded.");
6✔
45
                } else {
6✔
46
                        logger.Warn($"{fronterConfigurationPath} not found!");
×
47
                }
×
48

49
                var fronterOptionsPath = Path.Combine(AppContext.BaseDirectory, "Configuration", "fronter-options.txt");
6✔
50
                if (File.Exists(fronterOptionsPath)) {
12!
51
                        parser.ParseFile(fronterOptionsPath);
6✔
52
                        logger.Info("Frontend options loaded.");
6✔
53
                } else {
6✔
54
                        logger.Warn($"{fronterOptionsPath} not found!");
×
55
                }
×
56

57
                InitializePaths();
6✔
58

59
                LoadExistingConfiguration();
6✔
60
        }
6✔
61

62
        private void RegisterKeys(Parser parser) {
6✔
63
                parser.RegisterKeyword("name", reader => Name = reader.GetString());
12✔
64
                parser.RegisterKeyword("sentryDsn", reader => SentryDsn = reader.GetString());
6✔
65
                parser.RegisterKeyword("converterFolder", reader => ConverterFolder = reader.GetString());
12✔
66
                parser.RegisterKeyword("backendExePath", reader => BackendExePath = reader.GetString());
12✔
67
                parser.RegisterKeyword("requiredFolder", reader => {
30✔
68
                        var newFolder = new RequiredFolder(reader, this);
24✔
69
                        if (!string.IsNullOrEmpty(newFolder.Name)) {
48✔
70
                                RequiredFolders.Add(newFolder);
24✔
71
                        } else {
24✔
72
                                logger.Error("Required Folder has no mandatory field: name!");
×
73
                        }
×
74
                });
30✔
75
                parser.RegisterKeyword("requiredFile", reader => {
12✔
76
                        var newFile = new RequiredFile(reader);
6✔
77
                        if (!string.IsNullOrEmpty(newFile.Name)) {
12✔
78
                                RequiredFiles.Add(newFile);
6✔
79
                        } else {
6✔
80
                                logger.Error("Required File has no mandatory field: name!");
×
81
                        }
×
82
                });
12✔
83
                parser.RegisterKeyword("option", reader => {
60✔
84
                        var newOption = new Option(reader, ++optionCounter);
54✔
85
                        Options.Add(newOption);
54✔
86
                });
60✔
87
                parser.RegisterKeyword("displayName", reader => DisplayName = reader.GetString());
12✔
88
                parser.RegisterKeyword("sourceGame", reader => SourceGame = reader.GetString());
12✔
89
                parser.RegisterKeyword("targetGame", reader => TargetGame = reader.GetString());
12✔
90
                parser.RegisterKeyword("autoGenerateModsFrom", reader => ModAutoGenerationSource = reader.GetString());
12✔
91
                parser.RegisterKeyword("copyToTargetGameModDirectory", reader => {
6✔
92
                        CopyToTargetGameModDirectory = reader.GetString().Equals("true");
×
93
                });
6✔
94
                parser.RegisterKeyword("progressOnCopyingComplete", reader => {
12✔
95
                        ProgressOnCopyingComplete = (ushort)reader.GetInt();
6✔
96
                });
12✔
97
                parser.RegisterKeyword("enableUpdateChecker", reader => {
12✔
98
                        UpdateCheckerEnabled = reader.GetString().Equals("true");
6✔
99
                });
12✔
100
                parser.RegisterKeyword("checkForUpdatesOnStartup", reader => {
12✔
101
                        CheckForUpdatesOnStartup = reader.GetString().Equals("true");
6✔
102
                });
12✔
103
                parser.RegisterKeyword("converterReleaseForumThread", reader => {
12✔
104
                        ConverterReleaseForumThread = reader.GetString();
6✔
105
                });
12✔
106
                parser.RegisterKeyword("latestGitHubConverterReleaseUrl", reader => {
12✔
107
                        LatestGitHubConverterReleaseUrl = reader.GetString();
6✔
108
                });
12✔
109
                parser.RegisterKeyword("pagesCommitIdUrl", reader => PagesCommitIdUrl = reader.GetString());
12✔
110
                parser.IgnoreAndLogUnregisteredItems();
6✔
111
        }
6✔
112

113
        private void RegisterPreloadKeys(Parser parser) {
8✔
114
                parser.RegisterRegex(CommonRegexes.String, (reader, incomingKey) => {
128✔
115
                        var valueStringOfItem = reader.GetStringOfItem();
120✔
116
                        var valueStr = valueStringOfItem.ToString().RemQuotes();
120✔
117
                        var valueReader = new BufferedReader(valueStr);
120✔
118

8✔
119
                        foreach (var folder in RequiredFolders) {
1,800✔
120
                                if (folder.Name.Equals(incomingKey) && Directory.Exists(valueStr)) {
480✔
121
                                        folder.Value = valueStr;
×
122
                                }
×
123
                        }
480✔
124

8✔
125
                        foreach (var file in RequiredFiles) {
720✔
126
                                if (file.Name.Equals(incomingKey) && File.Exists(valueStr)) {
120✔
127
                                        file.Value = valueStr;
×
128
                                }
×
129
                        }
120✔
130
                        foreach (var option in Options) {
3,600✔
131
                                if (option.Name.Equals(incomingKey) && option.CheckBoxSelector is null) {
1,152✔
132
                                        option.SetValue(valueStr);
72✔
133
                                } else if (option.Name.Equals(incomingKey) && option.CheckBoxSelector is not null) {
1,080✔
134
                                        var selections = valueReader.GetStrings();
×
135
                                        var values = selections.ToHashSet(StringComparer.Ordinal);
×
136
                                        option.SetValue(values);
×
137
                                        option.SetCheckBoxSelectorPreloaded();
×
138
                                }
×
139
                        }
1,080✔
140
                        if (incomingKey.Equals("selectedMods")) {
128✔
141
                                var theList = valueReader.GetStrings();
8✔
142
                                var matchingMods = AutoLocatedMods.Where(m => theList.Contains(m.FileName, StringComparer.Ordinal));
8✔
143
                                foreach (var mod in matchingMods) {
24✔
144
                                        mod.Enabled = true;
×
145
                                }
×
146
                        }
8✔
147
                });
128✔
148
                parser.RegisterRegex(CommonRegexes.Catchall, ParserHelpers.IgnoreAndLogItem);
8✔
149
        }
8✔
150

151
        private void InitializePaths() {
6✔
152
                if (!OperatingSystem.IsWindows()) {
12!
153
                        return;
6✔
154
                }
155

156
                string documentsDir = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
×
157
                InitializeFolders(documentsDir);
×
158
                InitializeFiles(documentsDir);
×
159
        }
6✔
160

161
        private void InitializeFolders(string documentsDir) {
×
162
                foreach (var folder in RequiredFolders) {
×
163
                        string? initialValue = null;
×
164

165
                        if (!string.IsNullOrEmpty(folder.Value)) {
×
166
                                continue;
×
167
                        }
168

169
                        if (folder.SearchPathType.Equals("windowsUsersFolder")) {
×
170
                                initialValue = Path.Combine(documentsDir, folder.SearchPath);
×
171
                        } else if (folder.SearchPathType.Equals("storeFolder")) {
×
172
                                string? possiblePath = null;
×
173
                                if (uint.TryParse(folder.SteamGameId, out uint steamId)) {
×
174
                                        possiblePath = CommonFunctions.GetSteamInstallPath(steamId);
×
UNCOV
175
                                }
×
176
                                if (possiblePath is null && long.TryParse(folder.GOGGameId, out long gogId)) {
×
177
                                        possiblePath = CommonFunctions.GetGOGInstallPath(gogId);
×
UNCOV
178
                                }
×
179

180
                                if (possiblePath is null) {
×
181
                                        continue;
×
182
                                }
183

184
                                initialValue = possiblePath;
×
185
                                if (!string.IsNullOrEmpty(folder.SearchPath)) {
×
186
                                        initialValue = Path.Combine(initialValue, folder.SearchPath);
×
UNCOV
187
                                }
×
188
                        } else if (folder.SearchPathType.Equals("direct")) {
×
189
                                initialValue = folder.SearchPath;
×
190
                        }
×
191

192
                        if (Directory.Exists(initialValue)) {
×
193
                                folder.Value = initialValue;
×
194
                        }
×
195

196
                        if (folder.Name.Equals(ModAutoGenerationSource)) {
×
UNCOV
197
                                AutoLocateMods();
×
198
                        }
×
199
                }
×
200
        }
×
201

UNCOV
202
        private void InitializeFiles(string documentsDir) {
×
203
                foreach (var file in RequiredFiles) {
×
204
                        string? initialDirectory = null;
×
205
                        string? initialValue = null;
×
206

207
                        if (!string.IsNullOrEmpty(file.Value)) {
×
208
                                initialDirectory = CommonFunctions.GetPath(file.Value);
×
209
                        } else if (file.SearchPathType.Equals("windowsUsersFolder")) {
×
210
                                initialDirectory = Path.Combine(documentsDir, file.SearchPath);
×
211
                                if (!string.IsNullOrEmpty(file.FileName)) {
×
212
                                        initialValue = Path.Combine(initialDirectory, file.FileName);
×
213
                                }
×
214
                        } else if (file.SearchPathType.Equals("converterFolder")) {
×
215
                                var currentDir = Directory.GetCurrentDirectory();
×
216
                                initialDirectory = Path.Combine(currentDir, file.SearchPath);
×
UNCOV
217
                                if (!string.IsNullOrEmpty(file.FileName)) {
×
218
                                        initialValue = Path.Combine(initialDirectory, file.FileName);
×
219
                                }
×
220
                        }
×
221

222
                        if (string.IsNullOrEmpty(file.Value) && File.Exists(initialValue)) {
×
223
                                file.Value = initialValue;
×
224
                        }
×
225

226
                        if (Directory.Exists(initialDirectory)) {
×
UNCOV
227
                                file.InitialDirectory = initialDirectory;
×
UNCOV
228
                        }
×
UNCOV
229
                }
×
UNCOV
230
        }
×
231

232
        public void LoadExistingConfiguration() {
8✔
233
                var parser = new Parser();
8✔
234
                RegisterPreloadKeys(parser);
8✔
235
                var converterConfigurationPath = Path.Combine(ConverterFolder, "configuration.txt");
8✔
236
                if (string.IsNullOrEmpty(ConverterFolder) || !File.Exists(converterConfigurationPath)) {
8!
UNCOV
237
                        return;
×
238
                }
239

240
                logger.Info("Previous configuration located, preloading selections...");
8✔
241
                parser.ParseFile(converterConfigurationPath);
8✔
242
        }
8✔
243

244
        public bool ExportConfiguration() {
×
245
                SetSavingStatus("CONVERTSTATUSIN");
×
246

UNCOV
247
                if (string.IsNullOrEmpty(ConverterFolder)) {
×
248
                        logger.Error("Converter folder is not set!");
×
249
                        SetSavingStatus("CONVERTSTATUSPOSTFAIL");
×
250
                        return false;
×
251
                }
UNCOV
252
                if (!Directory.Exists(ConverterFolder)) {
×
UNCOV
253
                        logger.Error("Could not find converter folder!");
×
254
                        SetSavingStatus("CONVERTSTATUSPOSTFAIL");
×
255
                        return false;
×
256
                }
257

258
                var outConfPath = Path.Combine(ConverterFolder, "configuration.txt");
×
UNCOV
259
                try {
×
260
                        using var writer = new StreamWriter(outConfPath);
×
261
                        WriteRequiredFolders(writer);
×
262
                        WriteRequiredFiles(writer);
×
263

264
                        if (ModAutoGenerationSource is not null) {
×
UNCOV
265
                                WriteSelectedMods(writer);
×
266
                        }
×
267

268
                        WriteOptions(writer);
×
269

270
                        SetSavingStatus("CONVERTSTATUSPOSTSUCCESS");
×
271
                        return true;
×
UNCOV
272
                } catch (Exception ex) {
×
273
                        logger.Error($"Could not open configuration.txt! Error: {ex}");
×
UNCOV
274
                        SetSavingStatus("CONVERTSTATUSPOSTFAIL");
×
275
                        return false;
×
276
                }
277
        }
×
278

279
        private void WriteSelectedMods(StreamWriter writer) {
×
280
                writer.WriteLine("selectedMods = {");
×
281
                foreach (var mod in AutoLocatedMods) {
×
UNCOV
282
                        if (mod.Enabled) {
×
283
                                writer.WriteLine($"\t\"{mod.FileName}\"");
×
284
                        }
×
UNCOV
285
                }
×
286

287
                writer.WriteLine("}");
×
288
        }
×
289

290
        private void WriteOptions(StreamWriter writer) {
×
291
                foreach (var option in Options) {
×
292
                        if (option.CheckBoxSelector is not null) {
×
UNCOV
293
                                writer.Write($"{option.Name} = {{ ");
×
294
                                foreach (var value in option.GetValues()) {
×
295
                                        writer.Write($"\"{value}\" ");
×
296
                                }
×
297

298
                                writer.WriteLine("}");
×
299
                        } else {
×
UNCOV
300
                                writer.WriteLine($"{option.Name} = \"{option.GetValue()}\"");
×
301
                        }
×
302
                }
×
303
        }
×
304

UNCOV
305
        private void WriteRequiredFiles(StreamWriter writer) {
×
UNCOV
306
                foreach (var file in RequiredFiles) {
×
UNCOV
307
                        if (!file.Outputtable) {
×
308
                                continue;
×
309
                        }
310

311
                        // In the file path, replace backslashes with forward slashes.
UNCOV
312
                        string pathToWrite = file.Value.Replace('\\', '/');
×
313
                        writer.WriteLine($"{file.Name} = \"{pathToWrite}\"");
×
314
                }
×
UNCOV
315
        }
×
316

317
        private void WriteRequiredFolders(StreamWriter writer) {
×
318
                foreach (var folder in RequiredFolders) {
×
319
                        // In the folder path, replace backslashes with forward slashes.
UNCOV
320
                        string pathToWrite = folder.Value.Replace('\\', '/');
×
321
                        writer.WriteLine($"{folder.Name} = \"{pathToWrite}\"");
×
322
                }
×
323
        }
×
324

UNCOV
325
        private static void SetSavingStatus(string locKey) {
×
326
                if (Avalonia.Application.Current?.ApplicationLifetime is not IClassicDesktopStyleApplicationLifetime desktop) {
×
327
                        return;
×
328
                }
329

UNCOV
330
                if (desktop.MainWindow?.DataContext is MainWindowViewModel mainWindowDataContext) {
×
331
                        mainWindowDataContext.SaveStatus = locKey;
×
332
                }
×
333
        }
×
334

UNCOV
335
        public void AutoLocateMods() {
×
UNCOV
336
                logger.Debug("Clearing previously located mods...");
×
337
                AutoLocatedMods.Clear();
×
338
                logger.Debug("Autolocating mods...");
×
339

340
                // Do we have a mod path?
341
                string? modPath = null;
×
342
                foreach (var folder in RequiredFolders) {
×
343
                        if (folder.Name.Equals(ModAutoGenerationSource)) {
×
344
                                modPath = folder.Value;
×
345
                        }
×
UNCOV
346
                }
×
UNCOV
347
                if (modPath is null) {
×
UNCOV
348
                        logger.Warn("No folder found as source for mods autolocation.");
×
349
                        return;
×
350
                }
351

352
                // Does it exist?
UNCOV
353
                if (!Directory.Exists(modPath)) {
×
UNCOV
354
                        logger.Warn($"Mod path \"{modPath}\" does not exist or can not be accessed!");
×
355
                        return;
×
356
                }
357

358
                // Are we looking at documents directory?
359
                var combinedPath = Path.Combine(modPath, "mod");
×
UNCOV
360
                if (Directory.Exists(combinedPath)) {
×
UNCOV
361
                        modPath = combinedPath;
×
362
                }
×
UNCOV
363
                logger.Debug($"Mods autolocation path set to: \"{modPath}\"");
×
364

365
                // Are there mods inside?
366
                List<string> validModFiles = GetValidModFiles(modPath);
×
367

UNCOV
368
                if (validModFiles.Count == 0) {
×
369
                        logger.Debug($"No mod files could be found in \"{modPath}\"");
×
370
                        return;
×
371
                }
372

373
                foreach (var modFile in validModFiles) {
×
374
                        var path = Path.Combine(modPath, modFile);
×
375
                        Mod theMod;
376
                        try {
×
UNCOV
377
                                theMod = new Mod(path);
×
378
                        } catch (IOException ex) {
×
379
                                logger.Warn($"Failed to parse mod file {modFile}: {ex.Message}");
×
380
                                continue;
×
381
                        }
382
                        if (string.IsNullOrEmpty(theMod.Name)) {
×
383
                                logger.Warn($"Mod at \"{path}\" has no defined name, skipping.");
×
384
                                continue;
×
385
                        }
UNCOV
386
                        AutoLocatedMods.Add(theMod);
×
387
                }
×
388
                logger.Debug($"Autolocated {AutoLocatedMods.Count} mods");
×
389
        }
×
390

391
        private static List<string> GetValidModFiles(string modPath) {
×
392
                var validModFiles = new List<string>();
×
UNCOV
393
                foreach (var file in SystemUtils.GetAllFilesInFolder(modPath)) {
×
UNCOV
394
                        var lastDot = file.LastIndexOf('.');
×
395
                        if (lastDot == -1) {
×
396
                                continue;
×
397
                        }
398

UNCOV
399
                        var extension = CommonFunctions.GetExtension(file);
×
400
                        if (!extension.Equals("mod")) {
×
401
                                continue;
×
402
                        }
403

404
                        validModFiles.Add(file);
×
UNCOV
405
                }
×
406

UNCOV
407
                return validModFiles;
×
UNCOV
408
        }
×
409
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc