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

ParadoxGameConverters / Fronter.NET / 21217561952

21 Jan 2026 04:33PM UTC coverage: 23.05% (+0.07%) from 22.977%
21217561952

push

github

web-flow
Target playset selection (#426)

* TargetPlaysetPickerView

* TargetPlaylist class

* TargetDbManager

* Button for reloading the mods

* selectedPlaysetID

* TargetPlayset was an unneed wrap

* styling

* cleanup

* Post-merge fixes

* Log selected playset change

* Write the selected playset's mods to configuration.txt

* Add margin to SELECT_PLAYSET_INFO TextBlock

* Add blank option to target playset picker

* Load selectedPlayset from configuration.txt

144 of 774 branches covered (18.6%)

Branch coverage included in aggregate %.

28 of 147 new or added lines in 9 files covered. (19.05%)

7 existing lines in 1 file now uncovered.

728 of 3009 relevant lines covered (24.19%)

8.83 hits per line

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

29.5
/Fronter.NET/Models/Configuration/Config.cs
1
using Avalonia.Controls.ApplicationLifetimes;
2
using commonItems;
3
using Fronter.Models.Configuration.Options;
4
using Fronter.Models.Database;
5
using Fronter.Services;
6
using Fronter.ViewModels;
7
using log4net;
8
using Microsoft.EntityFrameworkCore;
9
using System;
10
using System.Collections.Generic;
11
using System.Collections.ObjectModel;
12
using System.Globalization;
13
using System.IO;
14
using System.Linq;
15

16
namespace Fronter.Models.Configuration;
17

18
internal sealed class Config {
19
        public string Name { get; private set; } = string.Empty;
13✔
20
        public string ConverterFolder { get; private set; } = string.Empty;
29✔
21
        public string BackendExePath { get; private set; } = string.Empty; // relative to ConverterFolder
13✔
22
        public string DisplayName { get; private set; } = string.Empty;
13✔
23
        public string SourceGame { get; private set; } = string.Empty;
13✔
24
        public string TargetGame { get; private set; } = string.Empty;
13✔
25
        public string? SentryDsn { get; private set; }
×
26
        public bool TargetPlaysetSelectionEnabled { get; private set; } = false;
8✔
27
        public ObservableCollection<Playset> AutoLocatedPlaysets { get; } = [];
10✔
NEW
28
        public Playset? SelectedPlayset { get; set; }
×
29
        public bool CopyToTargetGameModDirectory { get; set; } = true;
6✔
30
        public ushort ProgressOnCopyingComplete { get; set; } = 109;
12✔
31
        public bool UpdateCheckerEnabled { get; private set; } = false;
13✔
32
        public bool CheckForUpdatesOnStartup { get; private set; } = false;
13✔
33
        public string ConverterReleaseForumThread { get; private set; } = string.Empty;
13✔
34
        public string LatestGitHubConverterReleaseUrl { get; private set; } = string.Empty;
13✔
35
        public string PagesCommitIdUrl { get; private set; } = string.Empty;
13✔
36
        public List<RequiredFile> RequiredFiles { get; } = [];
135✔
37
        public List<RequiredFolder> RequiredFolders { get; } = [];
153✔
38
        public List<Option> Options { get; } = [];
192✔
39
        private int optionCounter;
40

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

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

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

62
                InitializePaths();
6✔
63

64
                LoadExistingConfiguration();
6✔
65
        }
6✔
66

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

122
        private void RegisterPreloadKeys(Parser parser) {
8✔
123
                parser.RegisterRegex(CommonRegexes.String, (reader, incomingKey) => {
128✔
124
                        StringOfItem valueStringOfItem = reader.GetStringOfItem();
120✔
125
                        string valueStr = valueStringOfItem.ToString().RemQuotes();
120✔
126
                        var valueReader = new BufferedReader(valueStr);
120✔
127

8✔
128
                        foreach (var folder in RequiredFolders) {
1,800✔
129
                                if (folder.Name.Equals(incomingKey) && Directory.Exists(valueStr)) {
480✔
130
                                        folder.Value = valueStr;
×
131
                                }
×
132
                        }
480✔
133

8✔
134
                        foreach (var file in RequiredFiles) {
720✔
135
                                if (file.Name.Equals(incomingKey) && File.Exists(valueStr)) {
120✔
136
                                        file.Value = valueStr;
×
137
                                }
×
138
                        }
120✔
139
                        foreach (var option in Options) {
3,600✔
140
                                if (option.Name.Equals(incomingKey) && option.CheckBoxSelector is null) {
1,152✔
141
                                        option.SetValue(valueStr);
72✔
142
                                } else if (option.Name.Equals(incomingKey) && option.CheckBoxSelector is not null) {
1,080✔
143
                                        var selections = valueReader.GetStrings();
×
144
                                        var values = selections.ToHashSet(StringComparer.Ordinal);
×
145
                                        option.SetValue(values);
×
146
                                        option.SetCheckBoxSelectorPreloaded();
×
147
                                }
×
148
                        }
1,080✔
149
                        if (incomingKey.Equals("selectedPlayset", StringComparison.OrdinalIgnoreCase)) {
120✔
NEW
150
                                string selectedPlaysetId = valueStr;
×
151

8✔
NEW
152
                                if (!string.IsNullOrWhiteSpace(selectedPlaysetId)) {
×
NEW
153
                                        SelectedPlayset = AutoLocatedPlaysets.FirstOrDefault(p =>
×
NEW
154
                                                string.Equals(p.Id, selectedPlaysetId, StringComparison.Ordinal));
×
UNCOV
155
                                }
×
UNCOV
156
                        }
×
157
                });
128✔
158
                parser.RegisterRegex(CommonRegexes.Catchall, ParserHelpers.IgnoreAndLogItem);
8✔
159
        }
8✔
160

161
        private void InitializePaths() {
6✔
162
                if (!OperatingSystem.IsWindows()) {
12!
163
                        return;
6✔
164
                }
165

166
                string documentsDir = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
×
167
                InitializeFolders(documentsDir);
×
168
                InitializeFiles(documentsDir);
×
169
        }
6✔
170

171
        private void InitializeFolders(string documentsDir) {
×
172
                foreach (var folder in RequiredFolders.Where(f => string.IsNullOrEmpty(f.Value))) {
×
173
                        string? initialValue = null;
×
174

175
                        if (folder.SearchPathType.Equals("windowsUsersFolder")) {
×
176
                                initialValue = Path.Combine(documentsDir, folder.SearchPath);
×
177
                        } else if (folder.SearchPathType.Equals("storeFolder")) {
×
178
                                string? possiblePath = null;
×
179
                                if (uint.TryParse(folder.SteamGameId, out uint steamId)) {
×
180
                                        possiblePath = CommonFunctions.GetSteamInstallPath(steamId);
×
181
                                }
×
NEW
182
                                if (possiblePath is null && long.TryParse(folder.GOGGameId, CultureInfo.InvariantCulture, out long gogId)) {
×
183
                                        possiblePath = CommonFunctions.GetGOGInstallPath(gogId);
×
184
                                }
×
185

186
                                if (possiblePath is null) {
×
187
                                        continue;
×
188
                                }
189

190
                                initialValue = possiblePath;
×
191
                                if (!string.IsNullOrEmpty(folder.SearchPath)) {
×
192
                                        initialValue = Path.Combine(initialValue, folder.SearchPath);
×
193
                                }
×
194
                        } else if (folder.SearchPathType.Equals("direct")) {
×
195
                                initialValue = folder.SearchPath;
×
196
                        }
×
197

198
                        if (Directory.Exists(initialValue)) {
×
199
                                folder.Value = initialValue;
×
200
                        }
×
UNCOV
201
                }
×
202
        }
×
203

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

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

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

228
                        if (Directory.Exists(initialDirectory)) {
×
229
                                file.InitialDirectory = initialDirectory;
×
230
                        }
×
231
                }
×
232
        }
×
233

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

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

246
        public bool ExportConfiguration() {
×
247
                SetSavingStatus("CONVERTSTATUSIN");
×
248

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

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

264
                        WriteRequiredFolders(writer);
×
265
                        WriteRequiredFiles(writer);
×
NEW
266
                        if (SelectedPlayset is not null) {
×
NEW
267
                                writer.WriteLine($"selectedPlayset = {SelectedPlayset.Id}");
×
NEW
268
                                WriteSelectedMods(writer, SelectedPlayset);
×
UNCOV
269
                        }
×
270

271
                        WriteOptions(writer);
×
272

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

282
        private void WriteOptions(StreamWriter writer) {
×
283
                foreach (var option in Options) {
×
284
                        if (option.CheckBoxSelector is not null) {
×
285
                                writer.Write($"{option.Name} = {{ ");
×
286
                                foreach (var value in option.GetValues()) {
×
287
                                        writer.Write($"\"{value}\" ");
×
288
                                }
×
289

290
                                writer.WriteLine("}");
×
291
                        } else {
×
292
                                writer.WriteLine($"{option.Name} = \"{option.GetValue()}\"");
×
293
                        }
×
294
                }
×
295
        }
×
296

297
        private void WriteRequiredFiles(StreamWriter writer) {
×
298
                foreach (var file in RequiredFiles) {
×
299
                        if (!file.Outputtable) {
×
300
                                continue;
×
301
                        }
302

303
                        // In the file path, replace backslashes with forward slashes.
304
                        string pathToWrite = file.Value.Replace('\\', '/');
×
305
                        writer.WriteLine($"{file.Name} = \"{pathToWrite}\"");
×
306
                }
×
307
        }
×
308

309
        private void WriteRequiredFolders(StreamWriter writer) {
×
310
                foreach (var folder in RequiredFolders) {
×
311
                        // In the folder path, replace backslashes with forward slashes.
312
                        string pathToWrite = folder.Value.Replace('\\', '/');
×
313
                        writer.WriteLine($"{folder.Name} = \"{pathToWrite}\"");
×
314
                }
×
315
        }
×
316

317
        private static void SetSavingStatus(string locKey) {
×
318
                if (Avalonia.Application.Current?.ApplicationLifetime is not IClassicDesktopStyleApplicationLifetime desktop) {
×
319
                        return;
×
320
                }
321

322
                if (desktop.MainWindow?.DataContext is MainWindowViewModel mainWindowDataContext) {
×
323
                        mainWindowDataContext.SaveStatus = locKey;
×
324
                }
×
325
        }
×
326

NEW
327
        private void WriteSelectedMods(StreamWriter writer, Playset selectedPlayset) {
×
NEW
328
                writer.WriteLine("selectedMods = {");
×
NEW
329
                var dbContext = TargetDbManager.GetLauncherDbContext(this);
×
NEW
330
                if (dbContext is null) {
×
NEW
331
                        writer.WriteLine("}");
×
UNCOV
332
                        return;
×
333
                }
334

NEW
335
                var playsetMods = dbContext.PlaysetsMods
×
NEW
336
                        .Include(playsetMod => playsetMod.Mod)
×
NEW
337
                        .Where(playsetMod => playsetMod.PlaysetId == selectedPlayset.Id)
×
NEW
338
                        .OrderBy(playsetMod => playsetMod.Position ?? long.MaxValue)
×
NEW
339
                        .ToList();
×
340

NEW
341
                foreach (var playsetMod in playsetMods) {
×
NEW
342
                        if (!IsPlaysetModEnabled(playsetMod)) {
×
NEW
343
                                continue;
×
344
                        }
345

NEW
346
                        var modPath = GetModPath(playsetMod.Mod);
×
NEW
347
                        if (string.IsNullOrWhiteSpace(modPath)) {
×
NEW
348
                                continue;
×
349
                        }
350

NEW
351
                        modPath = modPath.Replace('\\', '/');
×
NEW
352
                        writer.WriteLine($"\t\"{modPath}\"");
×
353
                }
×
354

NEW
355
                writer.WriteLine("}");
×
NEW
356
        }
×
357

NEW
358
        private static bool IsPlaysetModEnabled(PlaysetsMod playsetMod) {
×
NEW
359
                var enabled = playsetMod.Enabled;
×
NEW
360
                if (enabled is null || enabled.Length == 0) {
×
NEW
361
                        return true;
×
362
                }
363

NEW
364
                return enabled.Any(b => b != 0);
×
NEW
365
        }
×
366

NEW
367
        private static string? GetModPath(Mod mod) {
×
NEW
368
                if (!string.IsNullOrWhiteSpace(mod.GameRegistryId)) {
×
NEW
369
                        return mod.GameRegistryId;
×
370
                }
371

NEW
372
                Logger.Warn($"Mod {mod.Name} has no GameRegistryId set in the launcher's DB, cannot determine its path!");
×
NEW
373
                return null;
×
UNCOV
374
        }
×
375

376
        public string? TargetGameModsPath {
NEW
377
                get {
×
NEW
378
                        var targetGameModPath = RequiredFolders
×
NEW
379
                                .FirstOrDefault(f => string.Equals(f?.Name, "targetGameModPath", StringComparison.OrdinalIgnoreCase), defaultValue: null);
×
NEW
380
                        return targetGameModPath?.Value;
×
NEW
381
                }
×
382
        }
383

NEW
384
        public void AutoLocatePlaysets() {
×
NEW
385
                logger.Debug("Clearing previously located playsets...");
×
NEW
386
                AutoLocatedPlaysets.Clear();
×
NEW
387
                logger.Debug("Autolocating playsets...");
×
388

NEW
389
                var destModsFolder = TargetGameModsPath;
×
NEW
390
                var locatedPlaysetsCount = 0;
×
NEW
391
                if (destModsFolder is not null) {
×
NEW
392
                        var dbContext = TargetDbManager.GetLauncherDbContext(this);
×
NEW
393
                        if (dbContext is not null) {
×
NEW
394
                                foreach (var playset in dbContext.Playsets.Where(p => p.IsRemoved == null || p.IsRemoved == false )) {
×
NEW
395
                                        AutoLocatedPlaysets.Add(playset);
×
NEW
396
                                }
×
UNCOV
397
                        }
×
398

NEW
399
                        locatedPlaysetsCount = AutoLocatedPlaysets.Count;
×
400
                }
×
401

NEW
402
                logger.Debug($"Autolocated {locatedPlaysetsCount} playsets.");
×
403
        }
×
404
}
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