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

ParadoxGameConverters / Fronter.NET / 17879782487

20 Sep 2025 12:23PM UTC coverage: 19.243% (+0.2%) from 19.079%
17879782487

Pull #426

github

web-flow
Merge 127dace03 into 07919407c
Pull Request #426: Target playset selection

100 of 702 branches covered (14.25%)

Branch coverage included in aggregate %.

17 of 90 new or added lines in 9 files covered. (18.89%)

4 existing lines in 1 file now uncovered.

571 of 2785 relevant lines covered (20.5%)

9.23 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.Database;
4
using log4net;
5
using System;
6
using System.Globalization;
7
using System.IO;
8
using System.Linq;
9
using Mod = Fronter.Models.Database.Mod;
10

11
namespace Fronter.Services;
12

13
internal sealed class ModCopier {
14
        private readonly Config config;
15
        private readonly ILog logger = LogManager.GetLogger("Mod copier");
×
16
        public ModCopier(Config config) {
×
17
                this.config = config;
×
18
        }
×
19

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

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

NEW
34
                string? destModsFolder = config.TargetGameModsPath;
×
NEW
35
                if (destModsFolder is null) {
×
36
                        logger.Error("Copy failed - Target Folder isn't loaded!");
×
37
                        return false;
×
38
                }
39
                if (!Directory.Exists(destModsFolder)) {
×
40
                        logger.Error("Copy failed - Target Folder does not exist!");
×
41
                        return false;
×
42
                }
43
                var options = config.Options;
×
44
                string? targetName = null;
×
45
                foreach (var option in options) {
×
46
                        var value = option.GetValue();
×
47
                        if (option.Name.Equals("output_name") && !string.IsNullOrEmpty(value)) {
×
48
                                targetName = value;
×
49
                        }
×
50
                }
×
51
                var requiredFiles = config.RequiredFiles;
×
52
                if (string.IsNullOrEmpty(targetName)) {
×
53
                        var saveGame = requiredFiles.FirstOrDefault(f => string.Equals(f?.Name, "SaveGame", StringComparison.Ordinal), defaultValue: null);
×
54
                        if (saveGame is null) {
×
55
                                logger.Error("Copy failed - SaveGame is does not exist!");
×
56
                                return false;
×
57
                        }
58
                        var saveGamePath = saveGame.Value;
×
59
                        if (string.IsNullOrEmpty(saveGamePath)) {
×
60
                                logger.Error("Copy Failed - save game path is empty, did we even convert anything?");
×
61
                                return false;
×
62
                        }
63
                        if (!File.Exists(saveGamePath)) {
×
64
                                logger.Error("Copy Failed - save game does not exist, did we even convert anything?");
×
65
                                return false;
×
66
                        }
67
                        if (Directory.Exists(saveGamePath)) {
×
68
                                logger.Error("Copy Failed - Save game is a directory...");
×
69
                                return false;
×
70
                        }
71
                        saveGamePath = CommonFunctions.TrimPath(saveGamePath);
×
72
                        saveGamePath = CommonFunctions.NormalizeStringPath(saveGamePath);
×
73
                        var pos = saveGamePath.LastIndexOf('.');
×
74
                        if (pos != -1) {
×
75
                                saveGamePath = saveGamePath[..pos];
×
76
                        }
×
77
                        targetName = saveGamePath;
×
78
                }
×
79

80
                targetName = CommonFunctions.ReplaceCharacter(targetName, '-');
×
81
                targetName = CommonFunctions.ReplaceCharacter(targetName, ' ');
×
82
                targetName = CommonFunctions.NormalizeUTF8Path(targetName);
×
83

84
                var modFolderPath = Path.Combine(outputFolder, targetName);
×
85
                if (!Directory.Exists(modFolderPath)) {
×
86
                        logger.Error($"Copy Failed - Could not find mod folder: {modFolderPath}");
×
87
                        return false;
×
88
                }
89

90
                // For games using mods with .metadata folders we need to skip .mod file requirement.
91
                bool skipModFile = false;
×
92
                var metadataPath = Path.Combine(outputFolder, $"{targetName}/.metadata");
×
93
                if (Directory.Exists(metadataPath)) {
×
94
                        skipModFile = true;
×
95
                }
×
96

97
                var modFilePath = Path.Combine(outputFolder, $"{targetName}.mod");
×
98
                if (!skipModFile && !File.Exists(modFilePath)) {
×
99
                        logger.Error($"Copy Failed - Could not find mod: {modFilePath}");
×
100
                        return false;
×
101
                }
102

103
                var destModFilePath = Path.Combine(destModsFolder, $"{targetName}.mod");
×
104
                if (!skipModFile && File.Exists(destModFilePath)) {
×
105
                        logger.Info("Previous mod file found, deleting...");
×
106
                        File.Delete(destModFilePath);
×
107
                }
×
108

109
                var destModFolderPath = Path.Combine(destModsFolder, targetName);
×
110
                if (Directory.Exists(destModFolderPath)) {
×
111
                        logger.Info("Previous mod directory found, deleting...");
×
112
                        if (!SystemUtils.TryDeleteFolder(destModFolderPath)) {
×
113
                                logger.Error($"Could not delete directory: {destModFolderPath}");
×
114
                                return false;
×
115
                        }
116
                }
×
117
                try {
×
118
                        logger.Info("Copying mod to target location...");
×
119
                        if (!skipModFile) {
×
120
                                if (!SystemUtils.TryCopyFile(modFilePath, destModFilePath)) {
×
121
                                        logger.Error($"Could not copy file: {modFilePath}\nto {destModFilePath}");
×
122
                                }
×
123
                        }
×
124
                        if (!SystemUtils.TryCopyFolder(modFolderPath, destModFolderPath)) {
×
125
                                logger.Error($"Could not copy folder: {modFolderPath}\nto {destModFolderPath}");
×
126
                        }
×
127
                } catch (Exception e) {
×
128
                        logger.Error(e.ToString());
×
129
                        return false;
×
130
                }
131
                logger.Notice($"Mod successfully copied to: {destModFolderPath}");
×
132

133
                CreatePlayset(destModsFolder, targetName, destModFolderPath);
×
134

135
                return true;
×
136
        }
×
137

138
        private void CreatePlayset(string targetModsDirectory, string modName, string destModFolder) {
×
139
                var gameDocsDirectory = Directory.GetParent(targetModsDirectory)?.FullName;
×
140
                if (gameDocsDirectory is null) {
×
141
                        logger.Warn($"Couldn't get parent directory of \"{targetModsDirectory}\".");
×
142
                        return;
×
143
                }
NEW
144
                var latestDbFilePath = TargetDbManager.GetLastUpdatedLauncherDbPath(gameDocsDirectory);
×
145
                if (latestDbFilePath is null) {
×
146
                        logger.Debug("Launcher's database not found.");
×
147
                        return;
×
148
                }
149
                logger.Debug($"Launcher's database found at \"{latestDbFilePath}\".");
×
150

151
                logger.Info("Setting up playset...");
×
152
                string connectionString = $"Data Source={latestDbFilePath};";
×
153
                try {
×
154
                        logger.Debug("Connecting to launcher's DB...");
×
155
                        var dbContext = new LauncherDbContext(connectionString);
×
156

157
                        var playsetName = $"{config.Name}: {modName}";
×
158
                        var dateTimeOffset = new DateTimeOffset(DateTime.UtcNow);
×
159
                        string unixTimeMilliSeconds = dateTimeOffset.ToUnixTimeMilliseconds().ToString(CultureInfo.InvariantCulture);
×
160

161
                        DeactivateCurrentPlayset(dbContext);
×
162

163
                        // Check if a playset with the same name already exists.
164
                        var playset = dbContext.Playsets.FirstOrDefault(p => p.Name == playsetName);
×
165
                        if (playset is not null) {
×
166
                                logger.Debug("Removing mods from existing playset...");
×
167
                                dbContext.PlaysetsMods.RemoveRange(dbContext.PlaysetsMods.Where(pm => pm.PlaysetId == playset.Id));
×
168
                                dbContext.SaveChanges();
×
169

170
                                logger.Debug("Re-activating existing playset...");
×
171
                                // Set isActive to true and updatedOn to current time.
172
                                playset.IsActive = true;
×
173
                                playset.UpdatedOn = unixTimeMilliSeconds;
×
174
                                dbContext.SaveChanges();
×
175

176
                                logger.Notice("Updated existing playset.");
×
177
                        } else {
×
178
                                logger.Debug("Creating new playset...");
×
179
                                playset = new Playset {
×
180
                                        Id = Guid.NewGuid().ToString(),
×
181
                                        Name = playsetName,
×
182
                                        IsActive = true,
×
183
                                        IsRemoved = false,
×
184
                                        HasNotApprovedChanges = false,
×
185
                                        CreatedOn = unixTimeMilliSeconds
×
186
                                };
×
187
                                dbContext.Playsets.Add(playset);
×
188
                                dbContext.SaveChanges();
×
189
                        }
×
190

191
                        Logger.Debug("Adding mods to playset...");
×
192
                        var playsetInfo = LoadPlaysetInfo();
×
193
                        if (playsetInfo.Count == 0) {
×
194
                                var gameRegistryId = $"mod/{modName}.mod";
×
195
                                var mod = AddModToDb(dbContext, modName, gameRegistryId, destModFolder);
×
196
                                AddModToPlayset(dbContext, mod, playset);
×
197
                        }
×
198
                        foreach (var (playsetModName, playsetModPath) in playsetInfo) {
×
199
                                string playsetModPathWithBackSlashes = playsetModPath.Replace('/', '\\');
×
200

201
                                // Try to get an ID of existing matching mod.
202
                                var mod = dbContext.Mods.FirstOrDefault(m => m.Name == playsetModName ||
×
203
                                                                                                                          m.DirPath == playsetModPath ||
×
204
                                                                                                                          m.DirPath == playsetModPathWithBackSlashes);
×
205
                                if (mod is not null) {
×
206
                                        AddModToPlayset(dbContext, mod, playset);
×
207
                                } else {
×
208
                                        var gameRegistryId = playsetModPath;
×
209
                                        if (!gameRegistryId.StartsWith("mod/", StringComparison.Ordinal)) {
×
210
                                                gameRegistryId = $"mod/{gameRegistryId}";
×
211
                                        }
×
212
                                        if (!gameRegistryId.EndsWith(".mod", StringComparison.Ordinal)) {
×
213
                                                gameRegistryId = $"{gameRegistryId}.mod";
×
214
                                        }
×
215

216
                                        string dirPath;
217
                                        if (Path.IsPathRooted(playsetModPath)) {
×
218
                                                dirPath = playsetModPath;
×
219
                                        } else {
×
220
                                                dirPath = Path.Combine(gameDocsDirectory, gameRegistryId);
×
221
                                        }
×
222

223
                                        mod = AddModToDb(dbContext, modName, gameRegistryId, dirPath);
×
224
                                        AddModToPlayset(dbContext, mod, playset);
×
225
                                }
×
226
                        }
×
227

228
                        logger.Notice("Successfully set up playset.");
×
229
                } catch (Exception e) {
×
230
                        logger.Error(e);
×
231
                }
×
232
        }
×
233

234
        // Returns saved mod.
235
        private Mod AddModToDb(LauncherDbContext dbContext, string modName, string gameRegistryId, string dirPath) {
×
236
                logger.Debug($"Saving mod \"{modName}\" to DB...");
×
237

238
                var mod = new Mod {
×
239
                        Id = Guid.NewGuid().ToString(),
×
240
                        Status = "ready_to_play",
×
241
                        Source = "local",
×
242
                        Version = "1",
×
243
                        GameRegistryId = gameRegistryId,
×
244
                        Name = modName,
×
245
                        DirPath = dirPath,
×
246
                };
×
247
                dbContext.Mods.Add(mod);
×
248
                dbContext.SaveChanges();
×
249

250
                return mod;
×
251
        }
×
252

253
        private static void AddModToPlayset(LauncherDbContext dbContext, Mod mod, Playset playset) {
×
254
                var playsetMod = new PlaysetsMod {
×
255
                        Playset = playset,
×
256
                        Mod = mod,
×
257
                };
×
258
                dbContext.PlaysetsMods.Add(playsetMod);
×
259
                dbContext.SaveChanges();
×
260
        }
×
261

262
        // Loads playset info generated by converter backend.
263
        private Open.Collections.OrderedDictionary<string, string> LoadPlaysetInfo() {
×
264
                logger.Debug("Loading playset info from converter backend...");
×
265
                var toReturn = new Open.Collections.OrderedDictionary<string, string>();
×
266

267
                var filePath = Path.Combine(config.ConverterFolder, "playset_info.txt");
×
268
                if (!File.Exists(filePath)) {
×
269
                        return toReturn;
×
270
                }
271

272
                var parser = new Parser();
×
273
                parser.RegisterRegex(CommonRegexes.QuotedString, (reader, modName) => {
×
274
                        toReturn.Add(modName, reader.GetString().RemQuotes());
×
275
                });
×
276
                parser.ParseFile(filePath);
×
277
                return toReturn;
×
278
        }
×
279

280
        private void DeactivateCurrentPlayset(LauncherDbContext dbContext) {
×
281
                logger.Debug("Deactivating currently active playset...");
×
282
                dbContext.Playsets
×
283
                        .Where(p => p.IsActive == true)
×
284
                        .ToList()
×
285
                        .ForEach(p => p.IsActive = false);
×
286
                dbContext.SaveChanges();
×
287
        }
×
288
}
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