diff --git a/Fronter.NET.sln.DotSettings b/Fronter.NET.sln.DotSettings index f6bf17a8..d28ae2fc 100644 --- a/Fronter.NET.sln.DotSettings +++ b/Fronter.NET.sln.DotSettings @@ -21,4 +21,4 @@ True True True - True \ No newline at end of file + True diff --git a/Fronter.NET/Models/Configuration/Config.cs b/Fronter.NET/Models/Configuration/Config.cs index 824139c7..2174b0be 100644 --- a/Fronter.NET/Models/Configuration/Config.cs +++ b/Fronter.NET/Models/Configuration/Config.cs @@ -1,12 +1,15 @@ using Avalonia.Controls.ApplicationLifetimes; using commonItems; using Fronter.Models.Configuration.Options; +using Fronter.Models.Database; +using Fronter.Services; using Fronter.ViewModels; using log4net; using Sentry; using System; using System.Collections.Generic; using System.Collections.ObjectModel; +using System.Globalization; using System.IO; using System.Linq; @@ -20,8 +23,9 @@ public class Config { public string SourceGame { get; private set; } = string.Empty; public string TargetGame { get; private set; } = string.Empty; public string? SentryDsn { get; private set; } - public string? ModAutoGenerationSource { get; private set; } = null; - public ObservableCollection AutoLocatedMods { get; } = new(); + public bool TargetPlaysetSelectionEnabled { get; private set; } = false; + public ObservableCollection AutoLocatedPlaysets { get; } = []; + public Playset? SelectedPlayset { get; set; } public bool CopyToTargetGameModDirectory { get; set; } = true; public ushort ProgressOnCopyingComplete { get; set; } = 109; public bool UpdateCheckerEnabled { get; private set; } = false; @@ -92,7 +96,9 @@ private void RegisterKeys(Parser parser) { parser.RegisterKeyword("displayName", reader => DisplayName = reader.GetString()); parser.RegisterKeyword("sourceGame", reader => SourceGame = reader.GetString()); parser.RegisterKeyword("targetGame", reader => TargetGame = reader.GetString()); - parser.RegisterKeyword("autoGenerateModsFrom", reader => ModAutoGenerationSource = reader.GetString()); + parser.RegisterKeyword("targetPlaysetSelectionEnabled", reader => { + TargetPlaysetSelectionEnabled = reader.GetBool(); + }); parser.RegisterKeyword("copyToTargetGameModDirectory", reader => { CopyToTargetGameModDirectory = reader.GetString().Equals("true"); }); @@ -162,8 +168,8 @@ private void InitSentry(string dsn) { private void RegisterPreloadKeys(Parser parser) { parser.RegisterRegex(CommonRegexes.String, (reader, incomingKey) => { - var valueStringOfItem = reader.GetStringOfItem(); - var valueStr = valueStringOfItem.ToString().RemQuotes(); + StringOfItem valueStringOfItem = reader.GetStringOfItem(); + string valueStr = valueStringOfItem.ToString().RemQuotes(); var valueReader = new BufferedReader(valueStr); foreach (var folder in RequiredFolders) { @@ -187,13 +193,6 @@ private void RegisterPreloadKeys(Parser parser) { option.SetCheckBoxSelectorPreloaded(); } } - if (incomingKey.Equals("selectedMods")) { - var theList = valueReader.GetStrings(); - var matchingMods = AutoLocatedMods.Where(m => theList.Contains(m.FileName)); - foreach (var mod in matchingMods) { - mod.Enabled = true; - } - } }); parser.RegisterRegex(CommonRegexes.Catchall, ParserHelpers.IgnoreAndLogItem); } @@ -223,7 +222,7 @@ private void InitializeFolders(string documentsDir) { if (uint.TryParse(folder.SteamGameId, out uint steamId)) { possiblePath = CommonFunctions.GetSteamInstallPath(steamId); } - if (possiblePath is null && long.TryParse(folder.GOGGameId, out long gogId)) { + if (possiblePath is null && long.TryParse(folder.GOGGameId, CultureInfo.InvariantCulture, out long gogId)) { possiblePath = CommonFunctions.GetGOGInstallPath(gogId); } @@ -242,10 +241,6 @@ private void InitializeFolders(string documentsDir) { if (Directory.Exists(initialValue)) { folder.Value = initialValue; } - - if (folder.Name.Equals(ModAutoGenerationSource)) { - AutoLocateMods(); - } } } @@ -318,15 +313,9 @@ public bool ExportConfiguration() { } writer.WriteLine($"{file.Name} = \"{file.Value}\""); } - - if (ModAutoGenerationSource is not null) { - writer.WriteLine("selectedMods = {"); - foreach (var mod in AutoLocatedMods) { - if (mod.Enabled) { - writer.WriteLine($"\t\"{mod.FileName}\""); - } - } - writer.WriteLine("}"); + + if (SelectedPlayset is not null) { + writer.WriteLine($"selectedPlayset = {SelectedPlayset.Id}"); } foreach (var option in Options) { @@ -360,72 +349,32 @@ private static void SetSavingStatus(string locKey) { } } - public void AutoLocateMods() { - logger.Debug("Clearing previously located mods..."); - AutoLocatedMods.Clear(); - logger.Debug("Autolocating mods..."); - - // Do we have a mod path? - string? modPath = null; - foreach (var folder in RequiredFolders) { - if (folder.Name.Equals(ModAutoGenerationSource)) { - modPath = folder.Value; - } - } - if (modPath is null) { - logger.Warn("No folder found as source for mods autolocation."); - return; - } - - // Does it exist? - if (!Directory.Exists(modPath)) { - logger.Warn($"Mod path \"{modPath}\" does not exist or can not be accessed!"); - return; - } - - // Are we looking at documents directory? - var combinedPath = Path.Combine(modPath, "mod"); - if (Directory.Exists(combinedPath)) { - modPath = combinedPath; - } - logger.Debug($"Mods autolocation path set to: \"{modPath}\""); - - // Are there mods inside? - var validModFiles = new List(); - foreach (var file in SystemUtils.GetAllFilesInFolder(modPath)) { - var lastDot = file.LastIndexOf('.'); - if (lastDot == -1) { - continue; - } - - var extension = CommonFunctions.GetExtension(file); - if (!extension.Equals("mod")) { - continue; - } - - validModFiles.Add(file); - } - - if (validModFiles.Count == 0) { - logger.Debug($"No mod files could be found in \"{modPath}\""); - return; + public string? TargetGameModsPath { + get { + var targetGameModPath = RequiredFolders + .FirstOrDefault(f => f?.Name == "targetGameModPath", defaultValue: null); + return targetGameModPath?.Value; } + } - foreach (var modFile in validModFiles) { - var path = Path.Combine(modPath, modFile); - Mod theMod; - try { - theMod = new Mod(path); - } catch (IOException ex) { - logger.Warn($"Failed to parse mod file {modFile}: {ex.Message}"); - continue; - } - if (string.IsNullOrEmpty(theMod.Name)) { - logger.Warn($"Mod at \"{path}\" has no defined name, skipping."); - continue; + public void AutoLocatePlaysets() { + logger.Debug("Clearing previously located playsets..."); + AutoLocatedPlaysets.Clear(); + logger.Debug("Autolocating playsets..."); + + var destModsFolder = TargetGameModsPath; + var locatedPlaysetsCount = 0; + if (destModsFolder is not null) { + var dbContext = TargetDbManager.GetLauncherDbContext(this); + if (dbContext is not null) { + foreach (var playset in dbContext.Playsets.Where(p => p.IsRemoved == null || p.IsRemoved == false )) { + AutoLocatedPlaysets.Add(playset); + } } - AutoLocatedMods.Add(theMod); + + locatedPlaysetsCount = AutoLocatedPlaysets.Count; } - logger.Debug($"Autolocated {AutoLocatedMods.Count} mods"); + + logger.Debug($"Autolocated {locatedPlaysetsCount} playsets."); } } \ No newline at end of file diff --git a/Fronter.NET/Models/Configuration/Mod.cs b/Fronter.NET/Models/Configuration/Mod.cs deleted file mode 100644 index 9c3a71de..00000000 --- a/Fronter.NET/Models/Configuration/Mod.cs +++ /dev/null @@ -1,18 +0,0 @@ -using commonItems; -using Fronter.ViewModels; - -namespace Fronter.Models.Configuration; - -public class Mod : ViewModelBase { - public Mod(string modPath) { - var parser = new Parser(); - parser.RegisterKeyword("name", reader => Name = reader.GetString()); - parser.IgnoreUnregisteredItems(); - - parser.ParseFile(modPath); - FileName = CommonFunctions.TrimPath(modPath); - } - public string Name { get; private set; } = string.Empty; - public string FileName { get; private set; } - public bool Enabled { get; set; } = false; -} \ No newline at end of file diff --git a/Fronter.NET/Models/Configuration/RequiredFolder.cs b/Fronter.NET/Models/Configuration/RequiredFolder.cs index d4f06e73..5cccd839 100644 --- a/Fronter.NET/Models/Configuration/RequiredFolder.cs +++ b/Fronter.NET/Models/Configuration/RequiredFolder.cs @@ -44,9 +44,9 @@ public override string Value { base.Value = value; logger.Info($"{TranslationSource.Instance[DisplayName]} set to: {value}"); - - if (Name == config.ModAutoGenerationSource) { - config.AutoLocateMods(); + + if (config.TargetPlaysetSelectionEnabled) { + config.AutoLocatePlaysets(); } } } diff --git a/Fronter.NET/Resources/Configuration/converter_l_english.yml b/Fronter.NET/Resources/Configuration/converter_l_english.yml index 60cd6280..13a7636f 100644 --- a/Fronter.NET/Resources/Configuration/converter_l_english.yml +++ b/Fronter.NET/Resources/Configuration/converter_l_english.yml @@ -30,6 +30,7 @@ l_english: OPTIONSTAB: "Options" CONVERTTAB: "Convert" MODSTAB: "Mods" + TARGET_PLAYSET_TAB: "Target Playset" MODSDISABLED: "Mod autodetection disabled by configuration/not required." MODSNOTFOUND: "Mods not found in mod directory." MODSFOUND: "Mods autodetected:" diff --git a/Fronter.NET/Services/ModCopier.cs b/Fronter.NET/Services/ModCopier.cs index 36f70fcb..bc9eab94 100644 --- a/Fronter.NET/Services/ModCopier.cs +++ b/Fronter.NET/Services/ModCopier.cs @@ -3,7 +3,6 @@ using Fronter.Models.Database; using log4net; using System; -using System.Collections.Generic; using System.Globalization; using System.IO; using System.Linq; @@ -32,13 +31,11 @@ public bool CopyMod() { return false; } - var requiredFolders = config.RequiredFolders; - var targetGameModPath = requiredFolders.FirstOrDefault(f => f?.Name == "targetGameModPath", null); - if (targetGameModPath is null) { + string? destModsFolder = config.TargetGameModsPath; + if (destModsFolder is null) { logger.Error("Copy failed - Target Folder isn't loaded!"); return false; } - var destModsFolder = targetGameModPath.Value; if (!Directory.Exists(destModsFolder)) { logger.Error("Copy failed - Target Folder does not exist!"); return false; @@ -144,7 +141,7 @@ private void CreatePlayset(string targetModsDirectory, string modName, string de logger.Warn($"Couldn't get parent directory of \"{targetModsDirectory}\"."); return; } - var latestDbFilePath = GetLastUpdatedLauncherDbPath(gameDocsDirectory); + var latestDbFilePath = TargetDbManager.GetLastUpdatedLauncherDbPath(gameDocsDirectory); if (latestDbFilePath is null) { logger.Debug("Launcher's database not found."); return; @@ -234,16 +231,6 @@ private void CreatePlayset(string targetModsDirectory, string modName, string de } } - private static string? GetLastUpdatedLauncherDbPath(string gameDocsDirectory) { - var possibleDbFileNames = new List { "launcher-v2.sqlite", "launcher-v2_openbeta.sqlite" }; - var latestDbFilePath = possibleDbFileNames - .Select(name => Path.Join(gameDocsDirectory, name)) - .Where(File.Exists) - .OrderByDescending(File.GetLastWriteTimeUtc) - .FirstOrDefault(defaultValue: null); - return latestDbFilePath; - } - // Returns saved mod. private Mod AddModToDb(LauncherDbContext dbContext, string modName, string gameRegistryId, string dirPath) { logger.Debug($"Saving mod \"{modName}\" to DB..."); diff --git a/Fronter.NET/Services/TargetDbManager.cs b/Fronter.NET/Services/TargetDbManager.cs new file mode 100644 index 00000000..835c39c4 --- /dev/null +++ b/Fronter.NET/Services/TargetDbManager.cs @@ -0,0 +1,37 @@ +using Fronter.Models.Configuration; +using System.Collections.Generic; +using System.IO; +using System.Linq; + +namespace Fronter.Services; + +public class TargetDbManager { + public static string? GetLastUpdatedLauncherDbPath(string gameDocsDirectory) { + var possibleDbFileNames = new List { "launcher-v2.sqlite", "launcher-v2_openbeta.sqlite" }; + var latestDbFilePath = possibleDbFileNames + .Select(name => Path.Join(gameDocsDirectory, name)) + .Where(File.Exists) + .OrderByDescending(File.GetLastWriteTimeUtc) + .FirstOrDefault(defaultValue: null); + return latestDbFilePath; + } + + public static LauncherDbContext? GetLauncherDbContext(Config config) { + var targetGameModsPath = config.TargetGameModsPath; + if (string.IsNullOrWhiteSpace(targetGameModsPath)) { + return null; + } + var gameDocsDirectory = Directory.GetParent(targetGameModsPath)?.FullName; + if (gameDocsDirectory is null) { + return null; + } + + var dbPath = GetLastUpdatedLauncherDbPath(gameDocsDirectory); + if (dbPath is null) { + return null; + } + + string connectionString = $"Data Source={dbPath};"; + return new LauncherDbContext(connectionString); + } +} \ No newline at end of file diff --git a/Fronter.NET/ViewModels/MainWindowViewModel.cs b/Fronter.NET/ViewModels/MainWindowViewModel.cs index bc452ff0..340f3ba4 100644 --- a/Fronter.NET/ViewModels/MainWindowViewModel.cs +++ b/Fronter.NET/ViewModels/MainWindowViewModel.cs @@ -59,8 +59,8 @@ public sealed class MainWindowViewModel : ViewModelBase { internal Config Config { get; } internal PathPickerViewModel PathPicker { get; } - internal ModsPickerViewModel ModsPicker { get; } - public bool ModsPickerTabVisible => Config.ModAutoGenerationSource is not null; + internal TargetPlaysetPickerViewModel TargetPlaysetPicker { get; } + public bool TargetPlaysetPickerTabVisible => Config.TargetPlaysetSelectionEnabled; public OptionsViewModel Options { get; } public bool OptionsTabVisible => Options.Items.Any(); @@ -104,7 +104,7 @@ public MainWindowViewModel(DataGrid logGrid) { LogGridAppender.LogGrid = logGrid; PathPicker = new PathPickerViewModel(Config); - ModsPicker = new ModsPickerViewModel(Config); + TargetPlaysetPicker = new TargetPlaysetPickerViewModel(Config); Options = new OptionsViewModel(Config.Options); // Create reactive commands. diff --git a/Fronter.NET/ViewModels/ModsPickerViewModel.cs b/Fronter.NET/ViewModels/ModsPickerViewModel.cs deleted file mode 100644 index 2a80d0c9..00000000 --- a/Fronter.NET/ViewModels/ModsPickerViewModel.cs +++ /dev/null @@ -1,27 +0,0 @@ -using DynamicData; -using DynamicData.Binding; -using Fronter.Models.Configuration; -using System; -using System.Collections.ObjectModel; - -namespace Fronter.ViewModels; - -/// -/// The ModsPickerViewModel lets the user select paths to various stuff the converter needs to know where to find. -/// -internal sealed class ModsPickerViewModel : ViewModelBase { - public ModsPickerViewModel(Config config) { - config.AutoLocatedMods.ToObservableChangeSet() - .Bind(out autoLocatedMods) - .Subscribe(); - - if (config.ModAutoGenerationSource is null) { - ModsDisabled = true; - } - } - - private readonly ReadOnlyObservableCollection autoLocatedMods; - public ReadOnlyObservableCollection AutoLocatedMods => autoLocatedMods; - - public bool ModsDisabled { get; } = false; -} \ No newline at end of file diff --git a/Fronter.NET/ViewModels/TargetPlaysetPickerViewModel.cs b/Fronter.NET/ViewModels/TargetPlaysetPickerViewModel.cs new file mode 100644 index 00000000..3074f81b --- /dev/null +++ b/Fronter.NET/ViewModels/TargetPlaysetPickerViewModel.cs @@ -0,0 +1,42 @@ +using DynamicData; +using DynamicData.Binding; +using Fronter.Models.Configuration; +using Fronter.Models.Database; +using System; +using System.Collections.ObjectModel; + +namespace Fronter.ViewModels; + +/// +/// The TargetPlaysetPickerViewModel lets the user select paths to various stuff the converter needs to know where to find. +/// +public class TargetPlaysetPickerViewModel : ViewModelBase { + private Config config; + + public TargetPlaysetPickerViewModel(Config config) { + this.config = config; + + config.AutoLocatedPlaysets.ToObservableChangeSet() + .Bind(out targetPlaysets) + .Subscribe(); + + if (!config.TargetPlaysetSelectionEnabled) { + TabDisabled = true; + } + } + + private readonly ReadOnlyObservableCollection targetPlaysets; + public ReadOnlyObservableCollection TargetPlaysets => targetPlaysets; + + public bool TabDisabled { get; } = false; + + public void ReloadPlaysets() { + config.SelectedPlayset = null; + config.AutoLocatePlaysets(); + } + + public Playset? SelectedPlayset { + get => config.SelectedPlayset; + set => config.SelectedPlayset = value; + } +} \ No newline at end of file diff --git a/Fronter.NET/Views/MainWindow.axaml b/Fronter.NET/Views/MainWindow.axaml index f90d7f09..febf25a3 100644 --- a/Fronter.NET/Views/MainWindow.axaml +++ b/Fronter.NET/Views/MainWindow.axaml @@ -78,9 +78,9 @@ - + - + diff --git a/Fronter.NET/Views/ModsPickerView.axaml b/Fronter.NET/Views/TargetPlaysetPickerView.axaml similarity index 57% rename from Fronter.NET/Views/ModsPickerView.axaml rename to Fronter.NET/Views/TargetPlaysetPickerView.axaml index 02dd8114..08659380 100644 --- a/Fronter.NET/Views/ModsPickerView.axaml +++ b/Fronter.NET/Views/TargetPlaysetPickerView.axaml @@ -4,13 +4,13 @@ xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:viewModels="clr-namespace:Fronter.ViewModels" xmlns:ns="clr-namespace:Fronter.Extensions" - x:Class="Fronter.Views.ModsPickerView" - x:DataType="viewModels:ModsPickerViewModel" + x:Class="Fronter.Views.TargetPlaysetPickerView" + x:DataType="viewModels:TargetPlaysetPickerViewModel" mc:Ignorable="d" d:DesignHeight="300" d:DesignWidth="300"> - + @@ -37,23 +37,27 @@ - - - - - + + + - - - - - - - + + + + + + + + + +