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

ParadoxGameConverters / Fronter.NET / 22039151462

15 Feb 2026 04:31PM UTC coverage: 22.973% (-0.1%) from 23.114%
22039151462

push

github

web-flow
Fix warning about CopyMod method being too long (#947)

* Fix warning about CopyMod method being too long

* Update Fronter.NET/Services/ModCopier.cs

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update Fronter.NET/Services/ModCopier.cs

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Apply suggestions from code review

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Simplify ShouldModFileBeSkipped

* Update Fronter.NET/Services/ModCopier.cs

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Remove options parameter from DetermineTargetModName (#948)

* Initial plan

* Initial plan for addressing DetermineTargetModName feedback

Co-authored-by: IhateTrains <29546927+IhateTrains@users.noreply.github.com>

* Simplify DetermineTargetModName by removing options parameter

Co-authored-by: IhateTrains <29546927+IhateTrains@users.noreply.github.com>

* Update global.json

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: IhateTrains <29546927+IhateTrains@users.noreply.github.com>
Co-authored-by: iht <IhateTrains@users.noreply.github.com>

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com>
Co-authored-by: IhateTrains <29546927+IhateTrains@users.noreply.github.com>

142 of 770 branches covered (18.44%)

Branch coverage included in aggregate %.

0 of 56 new or added lines in 1 file covered. (0.0%)

2 existing lines in 1 file now uncovered.

728 of 3017 relevant lines covered (24.13%)

8.81 hits per line

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

0.0
/Fronter.NET/Services/ModCopier.cs
1
using commonItems;
2
using Fronter.Models.Configuration;
3
using Fronter.Models.Configuration.Options;
4
using Fronter.Models.Database;
5
using log4net;
6
using System;
7
using System.Collections.Generic;
8
using System.Globalization;
9
using System.IO;
10
using System.Linq;
11
using Mod = Fronter.Models.Database.Mod;
12

13
namespace Fronter.Services;
14

15
internal sealed class ModCopier(Config config) {
×
16
        private readonly ILog logger = LogManager.GetLogger("Mod copier");
×
17

18
        public bool CopyMod() {
×
19
                logger.Notice("Mod Copying Started.");
×
20
                var converterFolder = config.ConverterFolder;
×
21
                if (!Directory.Exists(converterFolder)) {
×
22
                        logger.Error("Copy failed - where is the converter?");
×
23
                        return false;
×
24
                }
25

26
                var outputFolder = Path.Combine(converterFolder, "output");
×
27
                if (!Directory.Exists(outputFolder)) {
×
28
                        logger.Error("Copy failed - where is the converter's output folder?");
×
29
                        return false;
×
30
                }
31

32
                string? destModsFolder = config.TargetGameModsPath;
×
33
                if (destModsFolder is null) {
×
34
                        logger.Error("Copy failed - Target Folder isn't loaded!");
×
35
                        return false;
×
36
                }
37
                if (!Directory.Exists(destModsFolder)) {
×
38
                        logger.Error("Copy failed - Target Folder does not exist!");
×
39
                        return false;
×
40
                }
NEW
41
                string? targetName = DetermineTargetModName();
×
NEW
42
                if (string.IsNullOrEmpty(targetName)) {
×
NEW
43
                        return false;
×
44
                }
45

NEW
46
                var modFolderPath = Path.Combine(outputFolder, targetName);
×
NEW
47
                if (!Directory.Exists(modFolderPath)) {
×
NEW
48
                        logger.Error($"Copy Failed - Could not find mod folder: {modFolderPath}");
×
NEW
49
                        return false;
×
50
                }
51

52
                // For games using mods with .metadata folders we need to skip .mod file requirement.
NEW
53
                bool skipModFile = ShouldModFileBeSkipped(outputFolder, targetName);
×
54

NEW
55
                var modFilePath = Path.Combine(outputFolder, $"{targetName}.mod");
×
NEW
56
                if (!skipModFile && !File.Exists(modFilePath)) {
×
NEW
57
                        logger.Error($"Copy Failed - Could not find mod: {modFilePath}");
×
NEW
58
                        return false;
×
59
                }
60

NEW
61
                var destModFilePath = Path.Combine(destModsFolder, $"{targetName}.mod");
×
NEW
62
                var destModFolderPath = Path.Combine(destModsFolder, targetName);
×
NEW
63
                if (!TryDeletePreviousModFileAndFolder(skipModFile, destModFilePath, destModFolderPath)) {
×
NEW
64
                        return false;
×
65
                }
66

NEW
67
                if (!TryCopyModFileAndFolder(skipModFile, modFilePath, modFolderPath, destModFilePath, destModFolderPath)) {
×
NEW
68
                        return false;
×
69
                }
70

NEW
71
                CreatePlayset(destModsFolder, targetName, destModFolderPath);
×
72

NEW
73
                return true;
×
NEW
74
        }
×
75

NEW
76
        private string? DetermineTargetModName() {
×
77
                string? targetName = null;
×
NEW
78
                foreach (var option in config.Options) {
×
79
                        var value = option.GetValue();
×
80
                        if (option.Name.Equals("output_name") && !string.IsNullOrEmpty(value)) {
×
81
                                targetName = value;
×
82
                        }
×
83
                }
×
84
                var requiredFiles = config.RequiredFiles;
×
85
                if (string.IsNullOrEmpty(targetName)) {
×
86
                        var saveGame = requiredFiles.FirstOrDefault(f => string.Equals(f?.Name, "SaveGame", StringComparison.Ordinal), defaultValue: null);
×
87
                        if (saveGame is null) {
×
NEW
88
                                logger.Error("Copy failed - SaveGame does not exist!");
×
NEW
89
                                return null;
×
90
                        }
91
                        var saveGamePath = saveGame.Value;
×
92
                        if (string.IsNullOrEmpty(saveGamePath)) {
×
93
                                logger.Error("Copy Failed - save game path is empty, did we even convert anything?");
×
NEW
94
                                return null;
×
95
                        }
96
                        if (!File.Exists(saveGamePath)) {
×
97
                                logger.Error("Copy Failed - save game does not exist, did we even convert anything?");
×
NEW
98
                                return null;
×
99
                        }
100
                        if (Directory.Exists(saveGamePath)) {
×
101
                                logger.Error("Copy Failed - Save game is a directory...");
×
NEW
102
                                return null;
×
103
                        }
104
                        saveGamePath = CommonFunctions.TrimPath(saveGamePath);
×
105
                        saveGamePath = CommonFunctions.NormalizeStringPath(saveGamePath);
×
106
                        var pos = saveGamePath.LastIndexOf('.');
×
107
                        if (pos != -1) {
×
108
                                saveGamePath = saveGamePath[..pos];
×
109
                        }
×
110
                        targetName = saveGamePath;
×
111
                }
×
112

113
                targetName = CommonFunctions.ReplaceCharacter(targetName, '-');
×
114
                targetName = CommonFunctions.ReplaceCharacter(targetName, ' ');
×
115
                targetName = CommonFunctions.NormalizeUTF8Path(targetName);
×
116

NEW
117
                return targetName;
×
NEW
118
        }
×
119

NEW
120
        private static bool ShouldModFileBeSkipped(string outputFolder, string targetModName) {
×
NEW
121
                var metadataPath = Path.Combine(outputFolder, $"{targetModName}/.metadata");
×
NEW
122
                return Directory.Exists(metadataPath);
×
NEW
123
        }
×
124

NEW
125
        private bool TryCopyModFileAndFolder(bool skipModFile, string modFilePath, string modFolderPath, string destModFilePath, string destModFolderPath) {
×
126
                try {
×
127
                        logger.Info("Copying mod to target location...");
×
128
                        if (!skipModFile) {
×
129
                                if (!SystemUtils.TryCopyFile(modFilePath, destModFilePath)) {
×
130
                                        logger.Error($"Could not copy file: {modFilePath}\nto {destModFilePath}");
×
NEW
131
                                        return false;
×
132
                                }
133
                        }
×
134
                        if (!SystemUtils.TryCopyFolder(modFolderPath, destModFolderPath)) {
×
135
                                logger.Error($"Could not copy folder: {modFolderPath}\nto {destModFolderPath}");
×
NEW
136
                                return false;
×
137
                        }
138
                } catch (Exception e) {
×
NEW
139
                        logger.Error("Error while copying mod to target location.", e);
×
140
                        return false;
×
141
                }
142

UNCOV
143
                logger.Notice($"Mod successfully copied to: {destModFolderPath}");
×
NEW
144
                return true;
×
NEW
145
        }
×
146

NEW
147
        private bool TryDeletePreviousModFileAndFolder(bool skipModFile, string destModFilePath, string destModFolderPath) {
×
NEW
148
                if (!skipModFile && File.Exists(destModFilePath)) {
×
NEW
149
                        logger.Info("Previous mod file found, deleting...");
×
NEW
150
                        try {
×
NEW
151
                                File.Delete(destModFilePath);
×
NEW
152
                        } catch (Exception e) {
×
NEW
153
                                logger.Error($"Could not delete file: {destModFilePath}", e);
×
NEW
154
                                return false;
×
155
                        }
NEW
156
                }
×
157

NEW
158
                if (!Directory.Exists(destModFolderPath)) {
×
NEW
159
                        return true;
×
160
                }
161

NEW
162
                logger.Info("Previous mod directory found, deleting...");
×
NEW
163
                if (SystemUtils.TryDeleteFolder(destModFolderPath)) {
×
NEW
164
                        return true;
×
165
                }
166

NEW
167
                logger.Error($"Could not delete directory: {destModFolderPath}");
×
NEW
168
                return false;
×
UNCOV
169
        }
×
170

171
        private void CreatePlayset(string targetModsDirectory, string modName, string destModFolder) {
×
172
                var gameDocsDirectory = Directory.GetParent(targetModsDirectory)?.FullName;
×
173
                if (gameDocsDirectory is null) {
×
174
                        logger.Warn($"Couldn't get parent directory of \"{targetModsDirectory}\".");
×
175
                        return;
×
176
                }
177
                try {
×
178
                        var dbContext = TargetDbManager.GetLauncherDbContext(config);
×
179
                        if (dbContext is null) {
×
180
                                logger.Debug("Launcher's database not found.");
×
181
                                return;
×
182
                        }
183
                        logger.Debug("Connecting to launcher's DB...");
×
184

185
                        var playsetName = $"{config.Name}: {modName}";
×
186
                        var dateTimeOffset = new DateTimeOffset(DateTime.UtcNow);
×
187
                        string unixTimeMilliSeconds = dateTimeOffset.ToUnixTimeMilliseconds().ToString(CultureInfo.InvariantCulture);
×
188

189
                        DeactivateCurrentPlayset(dbContext);
×
190

191
                        // Check if a playset with the same name already exists.
192
                        var playset = dbContext.Playsets.FirstOrDefault(p => p.Name == playsetName);
×
193
                        if (playset is not null) {
×
194
                                logger.Debug("Removing mods from existing playset...");
×
195
                                dbContext.PlaysetsMods.RemoveRange(dbContext.PlaysetsMods.Where(pm => pm.PlaysetId == playset.Id));
×
196
                                dbContext.SaveChanges();
×
197

198
                                logger.Debug("Re-activating existing playset...");
×
199
                                // Set isActive to true and updatedOn to current time.
200
                                playset.IsActive = true;
×
201
                                playset.UpdatedOn = unixTimeMilliSeconds;
×
202
                                dbContext.SaveChanges();
×
203

204
                                logger.Notice("Updated existing playset.");
×
205
                        } else {
×
206
                                logger.Debug("Creating new playset...");
×
207
                                playset = new Playset {
×
208
                                        Id = Guid.NewGuid().ToString(),
×
209
                                        Name = playsetName,
×
210
                                        IsActive = true,
×
211
                                        IsRemoved = false,
×
212
                                        HasNotApprovedChanges = false,
×
213
                                        CreatedOn = unixTimeMilliSeconds
×
214
                                };
×
215
                                dbContext.Playsets.Add(playset);
×
216
                                dbContext.SaveChanges();
×
217
                        }
×
218

219
                        Logger.Debug("Adding mods to playset...");
×
220
                        var playsetInfo = LoadPlaysetInfo();
×
221
                        if (playsetInfo.Count == 0) {
×
222
                                var gameRegistryId = $"mod/{modName}.mod";
×
223
                                var mod = AddModToDb(dbContext, modName, gameRegistryId, destModFolder);
×
224
                                AddModToPlayset(dbContext, mod, playset);
×
225
                        }
×
226
                        foreach (var (playsetModName, playsetModPath) in playsetInfo) {
×
227
                                string playsetModPathWithBackSlashes = playsetModPath.Replace('/', '\\');
×
228

229
                                // Try to get an ID of existing matching mod.
230
                                var mod = dbContext.Mods.FirstOrDefault(m => m.Name == playsetModName ||
×
231
                                                                                                                          m.DirPath == playsetModPath ||
×
232
                                                                                                                          m.DirPath == playsetModPathWithBackSlashes);
×
233
                                if (mod is not null) {
×
234
                                        AddModToPlayset(dbContext, mod, playset);
×
235
                                } else {
×
236
                                        var gameRegistryId = playsetModPath;
×
237
                                        if (!gameRegistryId.StartsWith("mod/", StringComparison.Ordinal)) {
×
238
                                                gameRegistryId = $"mod/{gameRegistryId}";
×
239
                                        }
×
240
                                        if (!gameRegistryId.EndsWith(".mod", StringComparison.Ordinal)) {
×
241
                                                gameRegistryId = $"{gameRegistryId}.mod";
×
242
                                        }
×
243

244
                                        string dirPath;
245
                                        if (Path.IsPathRooted(playsetModPath)) {
×
246
                                                dirPath = playsetModPath;
×
247
                                        } else {
×
248
                                                dirPath = Path.Combine(gameDocsDirectory, gameRegistryId);
×
249
                                        }
×
250

251
                                        mod = AddModToDb(dbContext, modName, gameRegistryId, dirPath);
×
252
                                        AddModToPlayset(dbContext, mod, playset);
×
253
                                }
×
254
                        }
×
255

256
                        logger.Notice("Successfully set up playset.");
×
257
                } catch (Exception e) {
×
258
                        logger.Error(e);
×
259
                }
×
260
        }
×
261

262
        // Returns saved mod.
263
        private Mod AddModToDb(LauncherDbContext dbContext, string modName, string gameRegistryId, string dirPath) {
×
264
                logger.Debug($"Saving mod \"{modName}\" to DB...");
×
265

266
                var mod = new Mod {
×
267
                        Id = Guid.NewGuid().ToString(),
×
268
                        Status = "ready_to_play",
×
269
                        Source = "local",
×
270
                        Version = "1",
×
271
                        GameRegistryId = gameRegistryId,
×
272
                        Name = modName,
×
273
                        DirPath = dirPath,
×
274
                };
×
275
                dbContext.Mods.Add(mod);
×
276
                dbContext.SaveChanges();
×
277

278
                return mod;
×
279
        }
×
280

281
        private static void AddModToPlayset(LauncherDbContext dbContext, Mod mod, Playset playset) {
×
282
                var playsetMod = new PlaysetsMod {
×
283
                        Playset = playset,
×
284
                        Mod = mod,
×
285
                };
×
286
                dbContext.PlaysetsMods.Add(playsetMod);
×
287
                dbContext.SaveChanges();
×
288
        }
×
289

290
        // Loads playset info generated by converter backend.
291
        private Open.Collections.OrderedDictionary<string, string> LoadPlaysetInfo() {
×
292
                logger.Debug("Loading playset info from converter backend...");
×
293
                var toReturn = new Open.Collections.OrderedDictionary<string, string>();
×
294

295
                var filePath = Path.Combine(config.ConverterFolder, "playset_info.txt");
×
296
                if (!File.Exists(filePath)) {
×
297
                        return toReturn;
×
298
                }
299

300
                var parser = new Parser();
×
301
                parser.RegisterRegex(CommonRegexes.QuotedString, (reader, modName) => {
×
302
                        toReturn.Add(modName, reader.GetString().RemQuotes());
×
303
                });
×
304
                parser.ParseFile(filePath);
×
305
                return toReturn;
×
306
        }
×
307

308
        private void DeactivateCurrentPlayset(LauncherDbContext dbContext) {
×
309
                logger.Debug("Deactivating currently active playset...");
×
310
                dbContext.Playsets
×
311
                        .Where(p => p.IsActive == true)
×
312
                        .ToList()
×
313
                        .ForEach(p => p.IsActive = false);
×
314
                dbContext.SaveChanges();
×
315
        }
×
316
}
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