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

ParadoxGameConverters / Fronter.NET / 24615805603

18 Apr 2026 10:58PM UTC coverage: 25.99% (-2.4%) from 28.393%
24615805603

push

github

web-flow
Bump dependencies (#970)

* Bump dependencies

* Fix coverage not being uploaded

166 of 770 branches covered (21.56%)

Branch coverage included in aggregate %.

740 of 2716 relevant lines covered (27.25%)

5.14 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
                }
41
                string? targetModName = DetermineTargetModName();
×
42
                if (string.IsNullOrEmpty(targetModName)) {
×
43
                        return false;
×
44
                }
45

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

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

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

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

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

71
                SetUpPlayset(destModsFolder, targetModName, destModFolderPath);
×
72

73
                return true;
×
74
        }
×
75

76
        private string? DetermineTargetModName() {
×
77
                string? targetName = null;
×
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) {
×
88
                                logger.Error("Copy failed - SaveGame does not exist!");
×
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?");
×
94
                                return null;
×
95
                        }
96
                        if (!File.Exists(saveGamePath)) {
×
97
                                logger.Error("Copy Failed - save game does not exist, did we even convert anything?");
×
98
                                return null;
×
99
                        }
100
                        if (Directory.Exists(saveGamePath)) {
×
101
                                logger.Error("Copy Failed - Save game is a directory...");
×
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

117
                return targetName;
×
118
        }
×
119

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

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}");
×
131
                                        return false;
×
132
                                }
133
                        }
×
134
                        if (!SystemUtils.TryCopyFolder(modFolderPath, destModFolderPath)) {
×
135
                                logger.Error($"Could not copy folder: {modFolderPath}\nto {destModFolderPath}");
×
136
                                return false;
×
137
                        }
138
                } catch (Exception e) {
×
139
                        logger.Error("Error while copying mod to target location.", e);
×
140
                        return false;
×
141
                }
142

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

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

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

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

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

171
        private void SetUpPlayset(string targetModsDirectory, string targetModName, 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
                        using var dbContext = TargetDbManager.GetLauncherDbContext(config);
×
179
                        if (dbContext is null) {
×
180
                                logger.Debug("Launcher's database not found.");
×
181
                                return;
×
182
                        }
183

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

188
                        DeactivateCurrentPlayset(dbContext);
×
189

190
                        // Check if a playset with the same name already exists.
191
                        var playset = dbContext.Playsets.FirstOrDefault(p => p.Name == playsetName);
×
192
                        if (playset is not null) {
×
193
                                UpdateExistingPlayset(dbContext, playset, unixTimeMilliSeconds);
×
194
                        } else {
×
195
                                playset = CreateNewPlayset(dbContext, playsetName, unixTimeMilliSeconds);
×
196
                        }
×
197

198
                        AddModsToPlayset(targetModName, destModFolder, gameDocsDirectory, dbContext, playset);
×
199

200
                        logger.Notice("Successfully set up playset.");
×
201
                } catch (Exception e) {
×
202
                        logger.Error(e);
×
203
                }
×
204
        }
×
205

206
        private void UpdateExistingPlayset(LauncherDbContext dbContext, Playset playset, string unixTimeMilliSeconds) {
×
207
                logger.Debug("Removing mods from existing playset...");
×
208
                dbContext.PlaysetsMods.RemoveRange(dbContext.PlaysetsMods.Where(pm => pm.PlaysetId == playset.Id));
×
209
                dbContext.SaveChanges();
×
210

211
                logger.Debug("Re-activating existing playset...");
×
212
                playset.IsActive = true;
×
213
                playset.UpdatedOn = unixTimeMilliSeconds;
×
214
                dbContext.SaveChanges();
×
215

216
                logger.Notice("Updated existing playset.");
×
217
        }
×
218

219
        private Playset CreateNewPlayset(LauncherDbContext dbContext, string playsetName, string unixTimeMilliSeconds) {
×
220
                logger.Debug("Creating new playset...");
×
221
                var playset = new Playset {
×
222
                        Id = Guid.NewGuid().ToString(),
×
223
                        Name = playsetName,
×
224
                        IsActive = true,
×
225
                        IsRemoved = false,
×
226
                        HasNotApprovedChanges = false,
×
227
                        CreatedOn = unixTimeMilliSeconds,
×
228
                };
×
229
                dbContext.Playsets.Add(playset);
×
230
                dbContext.SaveChanges();
×
231

232
                return playset;
×
233
        }
×
234

235
        private void AddModsToPlayset(string targetModName, string destModFolder, string gameDocsDirectory, LauncherDbContext dbContext, Playset playset) {
×
236
                logger.Debug("Adding mods to playset...");
×
237

238
                var playsetInfo = LoadPlaysetInfo();
×
239
                if (playsetInfo.Count == 0) {
×
240
                        var gameRegistryId = $"mod/{targetModName}.mod";
×
241
                        var mod = AddModToDb(dbContext, targetModName, gameRegistryId, destModFolder);
×
242
                        AddModToPlayset(dbContext, mod, playset);
×
243
                }
×
244
                foreach (var (playsetModName, playsetModPath) in playsetInfo) {
×
245
                        string playsetModPathWithBackSlashes = playsetModPath.Replace('/', '\\');
×
246

247
                        // Try to get an ID of existing matching mod.
248
                        var mod = dbContext.Mods.FirstOrDefault(m => m.Name == playsetModName ||
×
249
                                                                                                        m.DirPath == playsetModPath ||
×
250
                                                                                                        m.DirPath == playsetModPathWithBackSlashes);
×
251
                        if (mod is not null) {
×
252
                                AddModToPlayset(dbContext, mod, playset);
×
253
                        } else {
×
254
                                var gameRegistryId = playsetModPath;
×
255
                                if (!gameRegistryId.StartsWith("mod/", StringComparison.Ordinal)) {
×
256
                                        gameRegistryId = $"mod/{gameRegistryId}";
×
257
                                }
×
258
                                if (!gameRegistryId.EndsWith(".mod", StringComparison.Ordinal)) {
×
259
                                        gameRegistryId = $"{gameRegistryId}.mod";
×
260
                                }
×
261

262
                                string dirPath;
263
                                if (Path.IsPathRooted(playsetModPath)) {
×
264
                                        dirPath = playsetModPath;
×
265
                                } else {
×
266
                                        dirPath = Path.Combine(gameDocsDirectory, gameRegistryId);
×
267
                                }
×
268

269
                                mod = AddModToDb(dbContext, playsetModName, gameRegistryId, dirPath);
×
270
                                AddModToPlayset(dbContext, mod, playset);
×
271
                        }
×
272
                }
×
273
        }
×
274

275
        // Returns saved mod.
276
        private Mod AddModToDb(LauncherDbContext dbContext, string modName, string gameRegistryId, string dirPath) {
×
277
                logger.Debug($"Saving mod \"{modName}\" to DB...");
×
278

279
                var mod = new Mod {
×
280
                        Id = Guid.NewGuid().ToString(),
×
281
                        Status = "ready_to_play",
×
282
                        Source = "local",
×
283
                        Version = "1",
×
284
                        GameRegistryId = gameRegistryId,
×
285
                        Name = modName,
×
286
                        DirPath = dirPath,
×
287
                };
×
288
                dbContext.Mods.Add(mod);
×
289
                dbContext.SaveChanges();
×
290

291
                return mod;
×
292
        }
×
293

294
        private static void AddModToPlayset(LauncherDbContext dbContext, Mod mod, Playset playset) {
×
295
                var playsetMod = new PlaysetsMod {
×
296
                        Playset = playset,
×
297
                        Mod = mod,
×
298
                };
×
299
                dbContext.PlaysetsMods.Add(playsetMod);
×
300
                dbContext.SaveChanges();
×
301
        }
×
302

303
        // Loads playset info generated by converter backend.
304
        private Open.Collections.OrderedDictionary<string, string> LoadPlaysetInfo() {
×
305
                logger.Debug("Loading playset info from converter backend...");
×
306
                var toReturn = new Open.Collections.OrderedDictionary<string, string>();
×
307

308
                var filePath = Path.Combine(config.ConverterFolder, "playset_info.txt");
×
309
                if (!File.Exists(filePath)) {
×
310
                        return toReturn;
×
311
                }
312

313
                var parser = new Parser();
×
314
                parser.RegisterRegex(CommonRegexes.QuotedString, (reader, modName) => {
×
315
                        toReturn.Add(modName, reader.GetString().RemQuotes());
×
316
                });
×
317
                parser.ParseFile(filePath);
×
318
                return toReturn;
×
319
        }
×
320

321
        private void DeactivateCurrentPlayset(LauncherDbContext dbContext) {
×
322
                logger.Debug("Deactivating currently active playset...");
×
323
                dbContext.Playsets
×
324
                        .Where(p => p.IsActive == true)
×
325
                        .ToList()
×
326
                        .ForEach(p => p.IsActive = false);
×
327
                dbContext.SaveChanges();
×
328
        }
×
329
}
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