From 27dffe62cc30a1a32903cc8df80c4bddde8570f2 Mon Sep 17 00:00:00 2001 From: Paul Hebble Date: Thu, 6 Jun 2024 15:51:22 -0500 Subject: [PATCH] Install from .ckan file option for ConsoleUI --- Cmdline/Action/Upgrade.cs | 14 +----- ConsoleUI/DownloadImportDialog.cs | 1 + ConsoleUI/InstallFromCkanDialog.cs | 50 +++++++++++++++++++ ConsoleUI/InstallScreen.cs | 15 +++--- ConsoleUI/ModListScreen.cs | 32 ++++++++++-- ConsoleUI/Properties/Resources.resx | 7 ++- .../Toolkit/ConsoleFileMultiSelectDialog.cs | 27 +++++----- ConsoleUI/Toolkit/ConsoleListBox.cs | 2 +- Core/Types/RelationshipDescriptor.cs | 2 +- Core/Utilities.cs | 12 +++++ 10 files changed, 123 insertions(+), 39 deletions(-) create mode 100644 ConsoleUI/InstallFromCkanDialog.cs diff --git a/Cmdline/Action/Upgrade.cs b/Cmdline/Action/Upgrade.cs index 3bfc9dd7b8..7c730fdf06 100644 --- a/Cmdline/Action/Upgrade.cs +++ b/Cmdline/Action/Upgrade.cs @@ -217,7 +217,7 @@ private void UpgradeModules(GameInstanceManager manager, .ToHashSet(); // The modules we'll have after upgrading as aggressively as possible var limiters = identsAndVersions.Select(req => CkanModule.FromIDandVersion(registry, req, crit) - ?? DefaultIfThrows( + ?? Utilities.DefaultIfThrows( () => registry.LatestAvailable(req, crit)) ?? registry.GetInstalledVersion(req)) .Concat(heldIdents.Select(ident => registry.GetInstalledVersion(ident))) @@ -254,18 +254,6 @@ private void UpgradeModules(GameInstanceManager manager, m => identsAndVersions.Add(m.identifier)); } - public static T DefaultIfThrows(Func func) - { - try - { - return func(); - } - catch - { - return default; - } - } - private static string UpToFirst(string orig, char toFind) => UpTo(orig, orig.IndexOf(toFind)); diff --git a/ConsoleUI/DownloadImportDialog.cs b/ConsoleUI/DownloadImportDialog.cs index 116a96b420..d3f219340a 100644 --- a/ConsoleUI/DownloadImportDialog.cs +++ b/ConsoleUI/DownloadImportDialog.cs @@ -24,6 +24,7 @@ public static void ImportDownloads(ConsoleTheme theme, GameInstance gameInst, Re Properties.Resources.ImportSelectTitle, FindDownloadsPath(gameInst), "*.zip", + Properties.Resources.ImportSelectHeader, Properties.Resources.ImportSelectHeader ); HashSet files = cfmsd.Run(theme); diff --git a/ConsoleUI/InstallFromCkanDialog.cs b/ConsoleUI/InstallFromCkanDialog.cs new file mode 100644 index 0000000000..748db6e299 --- /dev/null +++ b/ConsoleUI/InstallFromCkanDialog.cs @@ -0,0 +1,50 @@ +using System; +using System.IO; + +using CKAN.ConsoleUI.Toolkit; +using System.Linq; + +namespace CKAN.ConsoleUI { + + /// + /// A popup to let the user import manually downloaded zip files into the mod cache. + /// + public static class InstallFromCkanDialog { + + /// + /// Let the user choose some zip files, then import them to the mod cache. + /// + /// The visual theme to use to draw the dialog + /// Game instance to import into + public static CkanModule[] ChooseCkanFiles(ConsoleTheme theme, + GameInstance gameInst) + { + var cfmsd = new ConsoleFileMultiSelectDialog( + Properties.Resources.CkanFileSelectTitle, + FindDownloadsPath(gameInst), + "*.ckan", + Properties.Resources.CkanFileSelectHeader, + Properties.Resources.CkanFileSelectHeader); + return cfmsd.Run(theme) + .Select(f => CkanModule.FromFile(f.FullName)) + .ToArray(); + } + + private static readonly string[] downloadPaths = new string[] { + Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), "Downloads"), + Environment.GetFolderPath(Environment.SpecialFolder.Desktop) + }; + + private static string FindDownloadsPath(GameInstance gameInst) + { + foreach (string p in downloadPaths) { + if (!string.IsNullOrEmpty(p) && Directory.Exists(p)) { + return p; + } + } + return gameInst.GameDir(); + } + + } + +} diff --git a/ConsoleUI/InstallScreen.cs b/ConsoleUI/InstallScreen.cs index 969c49c830..d9fd9bcb1c 100644 --- a/ConsoleUI/InstallScreen.cs +++ b/ConsoleUI/InstallScreen.cs @@ -75,13 +75,14 @@ public override void Run(ConsoleTheme theme, Action process = null NetAsyncModulesDownloader dl = new NetAsyncModulesDownloader(this, manager.Cache); if (plan.Install.Count > 0) { var iList = plan.Install - .Select(m => registry.LatestAvailable(m.identifier, - manager.CurrentInstance.VersionCriteria(), - null, - registry.InstalledModules - .Select(im => im.Module) - .ToArray(), - plan.Install) + .Select(m => Utilities.DefaultIfThrows(() => + registry.LatestAvailable(m.identifier, + manager.CurrentInstance.VersionCriteria(), + null, + registry.InstalledModules + .Select(im => im.Module) + .ToArray(), + plan.Install)) ?? m) .ToArray(); inst.InstallList(iList, resolvOpts, regMgr, ref possibleConfigOnlyDirs, dl); diff --git a/ConsoleUI/ModListScreen.cs b/ConsoleUI/ModListScreen.cs index 6963bf4c9d..f6b128a999 100644 --- a/ConsoleUI/ModListScreen.cs +++ b/ConsoleUI/ModListScreen.cs @@ -315,6 +315,9 @@ public ModListScreen(GameInstanceManager mgr, RepositoryDataManager repoData, Re new ConsoleMenuOption(Properties.Resources.ModListExportMenu, "", Properties.Resources.ModListExportMenuTip, true, ExportInstalled), + new ConsoleMenuOption(Properties.Resources.ModListInstallFromCkanMenu, "", + Properties.Resources.ModListInstallFromCkanMenuTip, + true, InstallFromCkan), null, new ConsoleMenuOption(Properties.Resources.ModListInstanceSettingsMenu, "", Properties.Resources.ModListInstanceSettingsMenuTip, @@ -597,10 +600,7 @@ private List GetAllMods(ConsoleTheme theme, bool force = false) var crit = manager.CurrentInstance.VersionCriteria(); allMods = new List(registry.CompatibleModules(crit)); foreach (InstalledModule im in registry.InstalledModules) { - CkanModule m = null; - try { - m = registry.LatestAvailable(im.identifier, crit); - } catch (ModuleNotFoundKraken) { } + var m = Utilities.DefaultIfThrows(() => registry.LatestAvailable(im.identifier, crit)); if (m == null) { // Add unavailable installed mods to the list allMods.Add(im.Module); @@ -629,6 +629,30 @@ private bool ExportInstalled(ConsoleTheme theme) return true; } + private bool InstallFromCkan(ConsoleTheme theme) + { + var modules = InstallFromCkanDialog.ChooseCkanFiles(theme, manager.CurrentInstance); + if (modules.Length > 0) { + var crit = manager.CurrentInstance.VersionCriteria(); + var installed = regMgr.registry.InstalledModules.Select(inst => inst.Module).ToList(); + var cp = new ChangePlan(); + cp.Install.UnionWith( + modules.Concat( + modules.Where(m => m.IsMetapackage && m.depends != null) + .SelectMany(m => m.depends.Where(rel => !rel.MatchesAny(installed, null, null)) + .Select(rel => + // If there's a compatible match, return it + // Metapackages aren't intending to prompt users to choose providing mods + rel.ExactMatch(regMgr.registry, crit, installed, modules) + // Otherwise look for incompatible + ?? rel.ExactMatch(regMgr.registry, null, installed, modules)) + .Where(mod => mod != null)))); + LaunchSubScreen(theme, new InstallScreen(manager, repoData, cp, debug)); + RefreshList(theme); + } + return true; + } + private bool Help(ConsoleTheme theme) { ModListHelpDialog hd = new ModListHelpDialog(); diff --git a/ConsoleUI/Properties/Resources.resx b/ConsoleUI/Properties/Resources.resx index 8818cf4cf7..d8aed13430 100644 --- a/ConsoleUI/Properties/Resources.resx +++ b/ConsoleUI/Properties/Resources.resx @@ -153,7 +153,7 @@ Details Up Down - Directory + Directory: {0} selected, {1} Name Size @@ -341,6 +341,11 @@ If you uninstall it, CKAN will not be able to re-install it. Select manually downloaded mods to import into CKAN Export installed... Save your mod list + Install from .ckan file... + Install modpacks or custom modules + Install from .ckan file failed! + Choose .ckan Files to Install + Install Game instance settings... Configure the current game instance Select game instance... diff --git a/ConsoleUI/Toolkit/ConsoleFileMultiSelectDialog.cs b/ConsoleUI/Toolkit/ConsoleFileMultiSelectDialog.cs index 869a8971e3..613dc99583 100644 --- a/ConsoleUI/Toolkit/ConsoleFileMultiSelectDialog.cs +++ b/ConsoleUI/Toolkit/ConsoleFileMultiSelectDialog.cs @@ -17,7 +17,12 @@ public class ConsoleFileMultiSelectDialog : ConsoleDialog { /// Path of directory to start in /// Glob-style wildcard string for matching files to show /// Header for the column with checkmarks for selected files - public ConsoleFileMultiSelectDialog(string title, string startPath, string filPat, string toggleHeader) + /// Description of the F9 action to accept selections + public ConsoleFileMultiSelectDialog(string title, + string startPath, + string filPat, + string toggleHeader, + string acceptTip) : base() { CenterHeader = () => title; @@ -40,7 +45,7 @@ public ConsoleFileMultiSelectDialog(string title, string startPath, string filPa )); pathField = new ConsoleField( - left + 2 + labelW, top + 2, right - 2, + left + 2 + labelW + 1, top + 2, right - 2, curDir.FullName ); pathField.OnChange += pathFieldChanged; @@ -128,10 +133,8 @@ public ConsoleFileMultiSelectDialog(string title, string startPath, string filPa return true; }); - AddTip("F9", Properties.Resources.FileSelectImport, () => chosenFiles.Count > 0); - AddBinding(Keys.F9, (object sender, ConsoleTheme theme) => { - return false; - }); + AddTip("F9", acceptTip, () => chosenFiles.Count > 0); + AddBinding(Keys.F9, (object sender, ConsoleTheme theme) => false); } private bool selectRow() @@ -141,7 +144,7 @@ private bool selectRow() { curDir = di; pathField.Value = curDir.FullName; - fileList.SetData(getFileList()); + fileList.SetData(getFileList(), true); } } else { if (fileList.Selection is FileInfo fi) @@ -300,11 +303,11 @@ private int compareNames(FileSystemInfo a, FileSystemInfo b) private static readonly string chosen = Symbols.checkmark; - private const int idealW = 76; - private int labelW => Properties.Resources.FileSelectDirectory.Length; - private const int hPad = 2; - private const int top = 2; - private const int bottom = -2; + private const int idealW = 76; + private static int labelW => Properties.Resources.FileSelectDirectory.Length; + private const int hPad = 2; + private const int top = 2; + private const int bottom = -2; } } diff --git a/ConsoleUI/Toolkit/ConsoleListBox.cs b/ConsoleUI/Toolkit/ConsoleListBox.cs index 8539ec685e..c19c628dce 100644 --- a/ConsoleUI/Toolkit/ConsoleListBox.cs +++ b/ConsoleUI/Toolkit/ConsoleListBox.cs @@ -139,7 +139,7 @@ public override void Draw(ConsoleTheme theme, bool focused) topRow = selectedRow - h + 2; } - var remainingWidth = contentR - l + 1 + var remainingWidth = contentR - l - 1 - columns.Select(col => col.Width ?? 0) .Sum() - (padding.Length * (columns.Count - 1)); diff --git a/Core/Types/RelationshipDescriptor.cs b/Core/Types/RelationshipDescriptor.cs index a2c928c79c..be41fd14cf 100644 --- a/Core/Types/RelationshipDescriptor.cs +++ b/Core/Types/RelationshipDescriptor.cs @@ -133,7 +133,7 @@ public override CkanModule ExactMatch(IRegistryQuerier registry, GameVersionCriteria crit, ICollection installed = null, ICollection toInstall = null) - => registry.LatestAvailable(name, crit, this, installed, toInstall); + => Utilities.DefaultIfThrows(() => registry.LatestAvailable(name, crit, this, installed, toInstall)); public override bool Equals(RelationshipDescriptor other) => Equals(other as ModuleRelationshipDescriptor); diff --git a/Core/Utilities.cs b/Core/Utilities.cs index 3300e98a98..f687cdb4c9 100644 --- a/Core/Utilities.cs +++ b/Core/Utilities.cs @@ -27,6 +27,18 @@ public static class Utilities "nl-NL", }; + public static T DefaultIfThrows(Func func) + { + try + { + return func(); + } + catch + { + return default; + } + } + /// /// Copies a directory and optionally its subdirectories as a transaction. ///