diff --git a/Cmdline/Action/AuthToken.cs b/Cmdline/Action/AuthToken.cs index 008220950d..3e027e1f10 100644 --- a/Cmdline/Action/AuthToken.cs +++ b/Cmdline/Action/AuthToken.cs @@ -19,6 +19,8 @@ public AuthToken() { } /// /// Run the subcommand /// + /// Manager to provide game instances + /// Command line parameters paritally handled by parser /// Command line parameters not yet handled by parser /// /// Exit code diff --git a/Cmdline/Action/List.cs b/Cmdline/Action/List.cs index 2a6117652a..871f71cab9 100644 --- a/Cmdline/Action/List.cs +++ b/Cmdline/Action/List.cs @@ -72,6 +72,7 @@ public int RunCommand(CKAN.KSP ksp, object raw_options) log.DebugFormat("Check if upgrades are available for {0}", mod.Key); CkanModule latest = registry.LatestAvailable(mod.Key, ksp.VersionCriteria()); CkanModule current = registry.GetInstalledVersion(mod.Key); + InstalledModule inst = registry.InstalledModule(mod.Key); if (latest == null) { @@ -80,13 +81,13 @@ public int RunCommand(CKAN.KSP ksp, object raw_options) bullet = "X"; if ( current == null ) log.DebugFormat( " {0} installed version not found in registry", mod.Key); - //Check if mod is replaceable - if ( current.replaced_by != null ) + // Check if mod is replaceable + if (current.replaced_by != null) { ModuleReplacement replacement = registry.GetReplacement(mod.Key, ksp.VersionCriteria()); - if ( replacement != null ) + if (replacement != null) { - //Replaceable! + // Replaceable! bullet = ">"; modInfo = string.Format("{0} > {1} {2}", modInfo, replacement.ReplaceWith.name, replacement.ReplaceWith.version); } @@ -96,14 +97,14 @@ public int RunCommand(CKAN.KSP ksp, object raw_options) { // Up to date log.InfoFormat("Latest {0} is {1}", mod.Key, latest.version); - bullet = "-"; - //Check if mod is replaceable - if ( current.replaced_by != null ) + bullet = (inst?.AutoInstalled ?? false) ? "+" : "-"; + // Check if mod is replaceable + if (current.replaced_by != null) { ModuleReplacement replacement = registry.GetReplacement(latest.identifier, ksp.VersionCriteria()); - if ( replacement != null ) + if (replacement != null) { - //Replaceable! + // Replaceable! bullet = ">"; modInfo = string.Format("{0} > {1} {2}", modInfo, replacement.ReplaceWith.name, replacement.ReplaceWith.version); } @@ -134,7 +135,7 @@ public int RunCommand(CKAN.KSP ksp, object raw_options) if (!(options.porcelain) && exportFileType == null) { - user.RaiseMessage("\r\nLegend: -: Up to date. X: Incompatible. ^: Upgradable. >: Replaceable\r\n A: Autodetected. ?: Unknown. *: Broken. "); + user.RaiseMessage("\r\nLegend: -: Up to date. +:Auto-installed. X: Incompatible. ^: Upgradable. >: Replaceable\r\n A: Autodetected. ?: Unknown. *: Broken. "); // Broken mods are in a state that CKAN doesn't understand, and therefore can't handle automatically } diff --git a/Cmdline/Action/Mark.cs b/Cmdline/Action/Mark.cs new file mode 100644 index 0000000000..485dc1b9fb --- /dev/null +++ b/Cmdline/Action/Mark.cs @@ -0,0 +1,158 @@ +using System.Collections.Generic; +using CommandLine; +using CommandLine.Text; +using log4net; + +namespace CKAN.CmdLine +{ + /// + /// Subcommand for setting flags on modules, + /// currently the auto-installed flag + /// + public class Mark : ISubCommand + { + /// + /// Initialize the subcommand + /// + public Mark() { } + + /// + /// Run the subcommand + /// + /// Manager to provide game instances + /// Command line parameters paritally handled by parser + /// Command line parameters not yet handled by parser + /// + /// Exit code + /// + public int RunSubCommand(KSPManager mgr, CommonOptions opts, SubCommandOptions unparsed) + { + string[] args = unparsed.options.ToArray(); + int exitCode = Exit.OK; + // Parse and process our sub-verbs + Parser.Default.ParseArgumentsStrict(args, new MarkSubOptions(), (string option, object suboptions) => + { + // ParseArgumentsStrict calls us unconditionally, even with bad arguments + if (!string.IsNullOrEmpty(option) && suboptions != null) + { + CommonOptions options = (CommonOptions)suboptions; + options.Merge(opts); + user = new ConsoleUser(options.Headless); + manager = mgr ?? new KSPManager(user); + exitCode = options.Handle(manager, user); + if (exitCode != Exit.OK) + return; + + switch (option) + { + case "auto": + exitCode = MarkAuto((MarkAutoOptions)suboptions, true, option, "auto-installed"); + break; + + case "user": + exitCode = MarkAuto((MarkAutoOptions)suboptions, false, option, "user-selected"); + break; + + default: + user.RaiseMessage("Unknown command: mark {0}", option); + exitCode = Exit.BADOPT; + break; + } + } + }, () => { exitCode = MainClass.AfterHelp(); }); + return exitCode; + } + + private int MarkAuto(MarkAutoOptions opts, bool value, string verb, string descrip) + { + if (opts.modules.Count < 1) + { + user.RaiseMessage("Usage: ckan mark {0} Mod [Mod2 ...]", verb); + return Exit.BADOPT; + } + + int exitCode = opts.Handle(manager, user); + if (exitCode != Exit.OK) + { + return exitCode; + } + + var ksp = MainClass.GetGameInstance(manager); + var regMgr = RegistryManager.Instance(ksp); + bool needSave = false; + Search.AdjustModulesCase(ksp, opts.modules); + foreach (string id in opts.modules) + { + InstalledModule im = regMgr.registry.InstalledModule(id); + if (im == null) + { + user.RaiseError("{0} is not installed.", id); + } + else if (im.AutoInstalled == value) + { + user.RaiseError("{0} is already marked as {1}.", id, descrip); + } + else + { + user.RaiseMessage("Marking {0} as {1}...", id, descrip); + im.AutoInstalled = value; + needSave = true; + } + } + if (needSave) + { + regMgr.Save(false); + user.RaiseMessage("Changes made!"); + } + return Exit.OK; + } + + private KSPManager manager { get; set; } + private IUser user { get; set; } + + private static readonly ILog log = LogManager.GetLogger(typeof(Mark)); + } + + internal class MarkSubOptions : VerbCommandOptions + { + [VerbOption("auto", HelpText = "Mark modules as auto installed")] + public MarkAutoOptions MarkAutoOptions { get; set; } + + [VerbOption("user", HelpText = "Mark modules as user selected (opposite of auto installed)")] + public MarkAutoOptions MarkUserOptions { get; set; } + + [HelpVerbOption] + public string GetUsage(string verb) + { + HelpText ht = HelpText.AutoBuild(this, verb); + // Add a usage prefix line + ht.AddPreOptionsLine(" "); + if (string.IsNullOrEmpty(verb)) + { + ht.AddPreOptionsLine("ckan mark - Edit flags on modules"); + ht.AddPreOptionsLine($"Usage: ckan mark [options]"); + } + else + { + ht.AddPreOptionsLine("ksp " + verb + " - " + GetDescription(verb)); + switch (verb) + { + case "auto": + ht.AddPreOptionsLine($"Usage: ckan mark {verb} [options] Mod [Mod2 ...]"); + break; + + case "user": + ht.AddPreOptionsLine($"Usage: ckan mark {verb} [options] Mod [Mod2 ...]"); + break; + } + } + return ht; + } + } + + internal class MarkAutoOptions : InstanceSpecificOptions + { + [ValueList(typeof(List))] + public List modules { get; set; } + } +} diff --git a/Cmdline/CKAN-cmdline.csproj b/Cmdline/CKAN-cmdline.csproj index e2813837f6..9d518d7be7 100644 --- a/Cmdline/CKAN-cmdline.csproj +++ b/Cmdline/CKAN-cmdline.csproj @@ -66,6 +66,7 @@ + diff --git a/Cmdline/Main.cs b/Cmdline/Main.cs index 130a596bfa..31c3e065c7 100644 --- a/Cmdline/Main.cs +++ b/Cmdline/Main.cs @@ -90,7 +90,9 @@ public static int Execute(KSPManager manager, CommonOptions opts, string[] args) case "cache": return (new Cache()).RunSubCommand(manager, opts, new SubCommandOptions(args)); - + + case "mark": + return (new Mark()).RunSubCommand(manager, opts, new SubCommandOptions(args)); } } catch (NoGameInstanceKraken) diff --git a/Cmdline/Options.cs b/Cmdline/Options.cs index 55325c0524..6bb5fb1e3a 100644 --- a/Cmdline/Options.cs +++ b/Cmdline/Options.cs @@ -94,6 +94,9 @@ internal class Actions : VerbCommandOptions [VerbOption("repo", HelpText = "Manage CKAN repositories")] public SubCommandOptions Repo { get; set; } + [VerbOption("mark", HelpText = "Edit flags on modules")] + public SubCommandOptions Mark { get; set; } + [VerbOption("ksp", HelpText = "Manage KSP installs")] public SubCommandOptions KSP { get; set; } diff --git a/ConsoleUI/ModListHelpDialog.cs b/ConsoleUI/ModListHelpDialog.cs index 900386c3ed..fee27901ea 100644 --- a/ConsoleUI/ModListHelpDialog.cs +++ b/ConsoleUI/ModListHelpDialog.cs @@ -30,6 +30,7 @@ public ModListHelpDialog() : base() symbolTb.AddLine("Status Symbols"); symbolTb.AddLine("=============="); symbolTb.AddLine($"{installed} Installed"); + symbolTb.AddLine($"{autoInstalled} Auto-installed"); symbolTb.AddLine($"{upgradable} Upgradeable"); symbolTb.AddLine($"{autodetected} Manually installed"); symbolTb.AddLine($"{replaceable} Replaceable"); @@ -65,10 +66,11 @@ public ModListHelpDialog() : base() )); } - private static readonly string installed = Symbols.checkmark; - private static readonly string upgradable = Symbols.greaterEquals; - private static readonly string autodetected = Symbols.infinity; - private static readonly string replaceable = Symbols.doubleGreater; + private static readonly string installed = Symbols.checkmark; + private static readonly string autoInstalled = Symbols.feminineOrdinal; + private static readonly string upgradable = Symbols.greaterEquals; + private static readonly string autodetected = Symbols.infinity; + private static readonly string replaceable = Symbols.doubleGreater; } } diff --git a/ConsoleUI/ModListScreen.cs b/ConsoleUI/ModListScreen.cs index 42c5b19cfb..bafee9d080 100644 --- a/ConsoleUI/ModListScreen.cs +++ b/ConsoleUI/ModListScreen.cs @@ -205,6 +205,23 @@ public ModListScreen(KSPManager mgr, bool dbg) } return true; }); + + moduleList.AddTip("F8", "Mark auto-installed", + () => moduleList.Selection != null + && (!registry.InstalledModule(moduleList.Selection.identifier)?.AutoInstalled ?? false) + ); + moduleList.AddTip("F8", "Mark user-selected", + () => moduleList.Selection != null + && (registry.InstalledModule(moduleList.Selection.identifier)?.AutoInstalled ?? false) + ); + moduleList.AddBinding(Keys.F8, (object sender) => { + InstalledModule im = registry.InstalledModule(moduleList.Selection.identifier); + if (im != null) { + im.AutoInstalled = !im.AutoInstalled; + RegistryManager.Instance(manager.CurrentInstance).Save(false); + } + return true; + }); AddTip("F9", "Apply changes", plan.NonEmpty); AddBinding(Keys.F9, (object sender) => { @@ -552,17 +569,18 @@ public string StatusSymbol(CkanModule m) public static string StatusSymbol(InstallStatus st) { switch (st) { - case InstallStatus.Unavailable: return unavailable; - case InstallStatus.Removing: return removing; - case InstallStatus.Upgrading: return upgrading; - case InstallStatus.Upgradeable: return upgradable; - case InstallStatus.Installed: return installed; - case InstallStatus.Installing: return installing; - case InstallStatus.NotInstalled: return notinstalled; - case InstallStatus.AutoDetected: return autodetected; - case InstallStatus.Replaceable: return replaceable; - case InstallStatus.Replacing: return replacing; - default: return ""; + case InstallStatus.Unavailable: return unavailable; + case InstallStatus.Removing: return removing; + case InstallStatus.Upgrading: return upgrading; + case InstallStatus.Upgradeable: return upgradable; + case InstallStatus.Installed: return installed; + case InstallStatus.AutoInstalled: return autoInstalled; + case InstallStatus.Installing: return installing; + case InstallStatus.NotInstalled: return notinstalled; + case InstallStatus.AutoDetected: return autodetected; + case InstallStatus.Replaceable: return replaceable; + case InstallStatus.Replacing: return replacing; + default: return ""; } } @@ -589,16 +607,17 @@ private long totalInstalledDownloadSize() private const int daysTillStale = 7; private const int daystillVeryStale = 30; - private static readonly string unavailable = "!"; - private static readonly string notinstalled = " "; - private static readonly string installed = Symbols.checkmark; - private static readonly string installing = "+"; - private static readonly string upgradable = Symbols.greaterEquals; - private static readonly string upgrading = "^"; - private static readonly string removing = "-"; - private static readonly string autodetected = Symbols.infinity; - private static readonly string replaceable = Symbols.doubleGreater; - private static readonly string replacing = Symbols.plusMinus; + private static readonly string unavailable = "!"; + private static readonly string notinstalled = " "; + private static readonly string installed = Symbols.checkmark; + private static readonly string autoInstalled = Symbols.feminineOrdinal; + private static readonly string installing = "+"; + private static readonly string upgradable = Symbols.greaterEquals; + private static readonly string upgrading = "^"; + private static readonly string removing = "-"; + private static readonly string autodetected = Symbols.infinity; + private static readonly string replaceable = Symbols.doubleGreater; + private static readonly string replacing = Symbols.plusMinus; } /// @@ -703,6 +722,8 @@ public InstallStatus GetModStatus(KSPManager manager, IRegistryQuerier registry, return InstallStatus.Replaceable; } else if (!IsAnyAvailable(registry, identifier)) { return InstallStatus.Unavailable; + } else if (registry.InstalledModule(identifier)?.AutoInstalled ?? false) { + return InstallStatus.AutoInstalled; } else { return InstallStatus.Installed; } @@ -813,6 +834,11 @@ public enum InstallStatus { /// This mod is installed and not upgradeable or planned to be removed /// Installed, + + /// + /// Like Installed, but can be auto-removed if depending mods are removed + /// + AutoInstalled, /// /// This mod is not installed but we are planning to install it diff --git a/ConsoleUI/Toolkit/Keys.cs b/ConsoleUI/Toolkit/Keys.cs index 26076d65ee..5f39473767 100644 --- a/ConsoleUI/Toolkit/Keys.cs +++ b/ConsoleUI/Toolkit/Keys.cs @@ -53,6 +53,13 @@ public static class Keys { (System.Char)0, ConsoleKey.F5, false, false, false ); + /// + /// Representation of F8 for key bindings + /// + public static readonly ConsoleKeyInfo F8 = new ConsoleKeyInfo( + (System.Char)0, ConsoleKey.F8, false, false, false + ); + /// /// Representation of F9 for key bindings /// diff --git a/ConsoleUI/Toolkit/Symbols.cs b/ConsoleUI/Toolkit/Symbols.cs index bb877732f8..7105eda7a6 100644 --- a/ConsoleUI/Toolkit/Symbols.cs +++ b/ConsoleUI/Toolkit/Symbols.cs @@ -23,6 +23,10 @@ public static class Symbols { /// public static readonly string checkmark = cp437s(0xfb); /// + /// Double tilde + /// + public static readonly string feminineOrdinal = cp437s(0xa6); + /// /// >= symbol /// public static readonly string greaterEquals = cp437s(0xf2); diff --git a/Core/ModuleInstaller.cs b/Core/ModuleInstaller.cs index 0142081cd4..0de428fafd 100644 --- a/Core/ModuleInstaller.cs +++ b/Core/ModuleInstaller.cs @@ -136,7 +136,8 @@ public static string CachedOrDownload(CkanModule module, NetModuleCache cache, s public void InstallList(List modules, RelationshipResolverOptions options, IDownloader downloader = null) { var resolver = new RelationshipResolver(modules, null, options, registry_manager.registry, ksp.VersionCriteria()); - InstallList(resolver.ModList().ToList(), options, downloader); + // Only pass the CkanModules of the parameters, so we can tell which are auto + InstallList(resolver.ModList().Where(m => modules.Contains(m.identifier)).ToList(), options, downloader); } /// @@ -214,7 +215,7 @@ public void InstallList(ICollection modules, RelationshipResolverOpt User.RaiseProgress(String.Format("Installing mod \"{0}\"", modsToInstall[i]), percent_complete); - Install(modsToInstall[i]); + Install(modsToInstall[i], resolver.ReasonFor(modsToInstall[i]) is SelectionReason.Depends); } User.RaiseProgress("Updating registry", 70); @@ -243,31 +244,6 @@ public void InstallList(ICollection modules, RelationshipResolverOpt User.RaiseProgress("Done!", 100); } - public void InstallList(ModuleResolution modules, RelationshipResolverOptions options) - { - // We're about to install all our mods; so begin our transaction. - using (TransactionScope transaction = CkanTransaction.CreateTransactionScope()) - { - var enumeratedMods = modules.Select((m, i) => new { Idx = i, Module = m }); - foreach (var item in enumeratedMods) - { - var percentComplete = (item.Idx * 100) / modules.Count; - User.RaiseProgress(string.Format("Installing mod \"{0}\"", item.Module), percentComplete); - Install(item.Module); - } - - User.RaiseProgress("Updating registry", 70); - - registry_manager.Save(!options.without_enforce_consistency); - - User.RaiseProgress("Committing filesystem changes", 80); - - transaction.Complete(); - - EnforceCacheSizeLimit(); - } - } - /// /// Returns the module contents if and only if we have it /// available in our cache. Returns null, otherwise. @@ -291,7 +267,7 @@ public IEnumerable GetModuleContentsList(CkanModule module) /// /// Install our mod from the filename supplied. /// If no file is supplied, we will check the cache or throw FileNotFoundKraken. - /// Does *not* resolve dependencies; this actually does the heavy listing. + /// Does *not* resolve dependencies; this does the heavy lifting. /// Does *not* save the registry. /// Do *not* call this directly, use InstallList() instead. /// @@ -299,11 +275,9 @@ public IEnumerable GetModuleContentsList(CkanModule module) /// Propagates a FileExistsKraken if we were going to overwrite a file. /// Throws a FileNotFoundKraken if we can't find the downloaded module. /// + /// TODO: The name of this and InstallModule() need to be made more distinctive. /// - // - // TODO: The name of this and InstallModule() need to be made more distinctive. - - private void Install(CkanModule module, string filename = null) + private void Install(CkanModule module, bool autoInstalled, string filename = null) { CheckMetapackageInstallationKraken(module); @@ -337,7 +311,7 @@ private void Install(CkanModule module, string filename = null) IEnumerable files = InstallModule(module, filename); // Register our module and its files. - registry.RegisterModule(module, files, ksp); + registry.RegisterModule(module, files, ksp, autoInstalled); // Finish our transaction, but *don't* save the registry; we may be in an // intermediate, inconsistent state. @@ -762,9 +736,13 @@ public void UninstallList(IEnumerable mods, bool ConfirmPrompt = true, I } // Find all the things which need uninstalling. - IEnumerable goners = mods.Union( + IEnumerable revdep = mods.Union( registry_manager.registry.FindReverseDependencies( mods.Except(installing ?? new string[] {}))); + IEnumerable goners = revdep.Union( + registry_manager.registry.FindRemovableAutoInstalled( + registry_manager.registry.InstalledModules.Where(im => !revdep.Contains(im.identifier)) + ).Select(im => im.identifier)); // If there us nothing to uninstall, skip out. if (!goners.Any()) @@ -999,7 +977,7 @@ public HashSet AddParentDirectories(HashSet directories) /// /// Add. /// Remove. - public void AddRemove(IEnumerable add = null, IEnumerable remove = null, bool enforceConsistency = true) + public void AddRemove(IEnumerable add = null, IEnumerable remove = null, bool enforceConsistency = true) { // TODO: We should do a consistency check up-front, rather than relying // upon our registry catching inconsistencies at the end. @@ -1007,14 +985,15 @@ public void AddRemove(IEnumerable add = null, IEnumerable re using (var tx = CkanTransaction.CreateTransactionScope()) { - foreach (string identifier in remove) + foreach (InstalledModule instMod in remove) { - Uninstall(identifier); + Uninstall(instMod.Module.identifier); } foreach (CkanModule module in add) { - Install(module); + var previous = remove?.FirstOrDefault(im => im.Module.identifier == module.identifier); + Install(module, previous?.AutoInstalled ?? false); } registry_manager.Save(enforceConsistency); @@ -1050,7 +1029,7 @@ public void Upgrade(IEnumerable modules, IDownloader netAsyncDownloa // adding everything that needs installing (which may involve new mods to // satisfy dependencies). We always know the list passed in is what we need to // install, but we need to calculate what needs to be removed. - var to_remove = new List(); + var to_remove = new List(); // Let's discover what we need to do with each module! foreach (CkanModule module in modules) @@ -1071,7 +1050,7 @@ public void Upgrade(IEnumerable modules, IDownloader netAsyncDownloa else { // Module already installed. We'll need to remove it first. - to_remove.Add(module.identifier); + to_remove.Add(installed_mod); CkanModule installed = installed_mod.Module; if (installed.version.IsEqualTo(module.version)) @@ -1105,7 +1084,7 @@ public void Replace(IEnumerable replacements, RelationshipRes { log.Debug("Using Replace method"); List modsToInstall = new List(); - var modsToRemove = new List(); + var modsToRemove = new List(); foreach (ModuleReplacement repl in replacements) { modsToInstall.Add(repl.ReplaceWith); @@ -1139,7 +1118,7 @@ public void Replace(IEnumerable replacements, RelationshipRes else { // Obviously, we need to remove the mod we are replacing - modsToRemove.Add(repl.ToReplace.identifier); + modsToRemove.Add(installedMod); log.DebugFormat("Ok, we are removing {0}", repl.ToReplace.identifier); //Check whether our Replacement target is already installed @@ -1150,7 +1129,7 @@ public void Replace(IEnumerable replacements, RelationshipRes { //Module already installed. We'll need to treat it as an upgrade. log.DebugFormat("It turns out {0} is already installed, we'll upgrade it.", installed_replacement.identifier); - modsToRemove.Add(installed_replacement.identifier); + modsToRemove.Add(installed_replacement); CkanModule installed = installed_replacement.Module; if (installed.version.IsEqualTo(repl.ReplaceWith.version)) diff --git a/Core/Registry/IRegistryQuerier.cs b/Core/Registry/IRegistryQuerier.cs index 86804cf452..22128911df 100644 --- a/Core/Registry/IRegistryQuerier.cs +++ b/Core/Registry/IRegistryQuerier.cs @@ -77,6 +77,17 @@ List LatestAvailableWithProvides( /// HashSet FindReverseDependencies(IEnumerable modules); + /// + /// Find auto-installed modules that have no depending modules + /// or only auto-installed depending modules. + /// installedModules is a parameter so we can experiment with + /// changes that have not yet been made, such as removing other modules. + /// + /// The modules currently installed + /// + /// Sequence of removable auto-installed modules, if any + /// + IEnumerable FindRemovableAutoInstalled(IEnumerable installedModules); /// /// Gets the installed version of a mod. Does not check for provided or autodetected mods. diff --git a/Core/Registry/InstalledModule.cs b/Core/Registry/InstalledModule.cs index 2c692013ba..732cc7e9f0 100644 --- a/Core/Registry/InstalledModule.cs +++ b/Core/Registry/InstalledModule.cs @@ -1,4 +1,5 @@ using System; +using System.ComponentModel; using System.Collections.Generic; using System.IO; using System.Security.Cryptography; @@ -83,7 +84,9 @@ public class InstalledModule [JsonProperty] private CkanModule source_module; -// private static readonly ILog log = LogManager.GetLogger(typeof(InstalledModule)); + [JsonProperty(DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate)] + [DefaultValue(false)] + private bool auto_installed; // TODO: Our InstalledModuleFile already knows its path, so this could just // be a list. However we've left it as a dictionary for now to maintain @@ -110,15 +113,22 @@ public DateTime InstallTime get { return install_time; } } + public bool AutoInstalled + { + get { return auto_installed; } + set { auto_installed = value; } + } + #endregion #region Constructors - public InstalledModule(KSP ksp, CkanModule module, IEnumerable relative_files) + public InstalledModule(KSP ksp, CkanModule module, IEnumerable relative_files, bool autoInstalled) { install_time = DateTime.Now; source_module = module; installed_files = new Dictionary(); + auto_installed = autoInstalled; foreach (string file in relative_files) { diff --git a/Core/Registry/Registry.cs b/Core/Registry/Registry.cs index 29dc11174f..fa865c332f 100644 --- a/Core/Registry/Registry.cs +++ b/Core/Registry/Registry.cs @@ -209,7 +209,8 @@ private void DeSerialisationFixes(StreamingContext context) var new_control_lock_installed = new InstalledModule( ksp, control_lock_mod, - control_lock_entry.Files + control_lock_entry.Files, + control_lock_entry.AutoInstalled ); // Re-insert into registry. @@ -743,7 +744,7 @@ public CkanModule GetModuleByVersion(string ident, ModuleVersion version) /// Register the supplied module as having been installed, thereby keeping /// track of its metadata and files. /// - public void RegisterModule(CkanModule mod, IEnumerable absolute_files, KSP ksp) + public void RegisterModule(CkanModule mod, IEnumerable absolute_files, KSP ksp, bool autoInstalled) { SealionTransaction(); @@ -791,7 +792,7 @@ public void RegisterModule(CkanModule mod, IEnumerable absolute_files, K } // Finally, register our module proper. - var installed = new InstalledModule(ksp, mod, relative_files); + var installed = new InstalledModule(ksp, mod, relative_files, autoInstalled); installed_modules.Add(mod.identifier, installed); } @@ -1118,6 +1119,44 @@ public HashSet FindReverseDependencies(IEnumerable modules_to_re return FindReverseDependencies(modules_to_remove, installed, new HashSet(installed_dlls.Keys), _installedDlcModules); } + /// + /// Find auto-installed modules that have no depending modules + /// or only auto-installed depending modules. + /// + /// The modules currently installed + /// The DLLs that are manually installed + /// The DLCs that are installed + /// + /// Sequence of removable auto-installed modules, if any + /// + private static IEnumerable FindRemovableAutoInstalled( + IEnumerable installedModules, + IEnumerable dlls, + IDictionary dlc + ) + { + var instCkanMods = installedModules.Select(im => im.Module); + // ToList ensures that the collection isn't modified while the enumeration operation is executing + return installedModules.ToList().Where( + im => FindReverseDependencies(new List { im.identifier }, instCkanMods, dlls, dlc) + .All(id => installedModules.First(orig => orig.identifier == id).AutoInstalled)); + } + + /// + /// Find auto-installed modules that have no depending modules + /// or only auto-installed depending modules. + /// installedModules is a parameter so we can experiment with + /// changes that have not yet been made, such as removing other modules. + /// + /// The modules currently installed + /// + /// Sequence of removable auto-installed modules, if any + /// + public IEnumerable FindRemovableAutoInstalled(IEnumerable installedModules) + { + return FindRemovableAutoInstalled(installedModules, InstalledDlls, InstalledDlc); + } + /// /// Get a dictionary of all mod versions indexed by their downloads' SHA-1 hash. /// Useful for finding the mods for a group of files without repeatedly searching the entire registry. diff --git a/Core/Relationships/RelationshipResolver.cs b/Core/Relationships/RelationshipResolver.cs index ae597a3820..c4083e563e 100644 --- a/Core/Relationships/RelationshipResolver.cs +++ b/Core/Relationships/RelationshipResolver.cs @@ -673,6 +673,14 @@ public override string Reason get { return " Requested by user.\r\n"; } } } + + public class NoLongerUsed: SelectionReason + { + public override string Reason + { + get { return " Auto-installed, depending modules removed.\r\n"; } + } + } public class Replacement : SelectionReason { diff --git a/GUI/GUIMod.cs b/GUI/GUIMod.cs index 2f28e4f96c..5df944b036 100644 --- a/GUI/GUIMod.cs +++ b/GUI/GUIMod.cs @@ -8,10 +8,12 @@ namespace CKAN { public sealed class GUIMod { - private CkanModule Mod { get; set; } + private CkanModule Mod { get; set; } + private InstalledModule InstalledMod { get; set; } public string Name { get; private set; } public bool IsInstalled { get; private set; } + public bool IsAutoInstalled { get; private set; } public bool HasUpdate { get; private set; } public bool HasReplacement { get; private set; } public bool IsIncompatible { get; private set; } @@ -82,6 +84,8 @@ public GUIMod(InstalledModule instMod, IRegistryQuerier registry, KspVersionCrit { IsInstalled = true; IsInstallChecked = true; + InstalledMod = instMod; + IsAutoInstalled = instMod.AutoInstalled; InstallDate = instMod.InstallTime; InstalledVersion = instMod.Module.version.ToString(); if (LatestVersion == null || LatestVersion.Equals("-")) @@ -282,10 +286,9 @@ public static implicit operator CkanModule(GUIMod mod) return mod.ToModule(); } - public void SetUpgradeChecked(DataGridViewRow row, bool? set_value_to = null) + public void SetUpgradeChecked(DataGridViewRow row, DataGridViewColumn col, bool? set_value_to = null) { - //Contract.Requires(row.Cells[1] is DataGridViewCheckBoxCell); - var update_cell = row.Cells[1] as DataGridViewCheckBoxCell; + var update_cell = row.Cells[col.Index] as DataGridViewCheckBoxCell; if (update_cell != null) { var old_value = (bool) update_cell.Value; @@ -297,10 +300,9 @@ public void SetUpgradeChecked(DataGridViewRow row, bool? set_value_to = null) } } - public void SetInstallChecked(DataGridViewRow row, bool? set_value_to = null) + public void SetInstallChecked(DataGridViewRow row, DataGridViewColumn col, bool? set_value_to = null) { - //Contract.Requires(row.Cells[0] is DataGridViewCheckBoxCell); - var install_cell = row.Cells[0] as DataGridViewCheckBoxCell; + var install_cell = row.Cells[col.Index] as DataGridViewCheckBoxCell; if (install_cell != null) { bool changeTo = set_value_to ?? (bool)install_cell.Value; @@ -324,9 +326,9 @@ public void SetInstallChecked(DataGridViewRow row, bool? set_value_to = null) } } - public void SetReplaceChecked(DataGridViewRow row, bool? set_value_to = null) + public void SetReplaceChecked(DataGridViewRow row, DataGridViewColumn col, bool? set_value_to = null) { - var replace_cell = row.Cells[2] as DataGridViewCheckBoxCell; + var replace_cell = row.Cells[col.Index] as DataGridViewCheckBoxCell; if (replace_cell != null) { var old_value = (bool) replace_cell.Value; @@ -337,6 +339,24 @@ public void SetReplaceChecked(DataGridViewRow row, bool? set_value_to = null) replace_cell.Value = value; } } + + public void SetAutoInstallChecked(DataGridViewRow row, DataGridViewColumn col, bool? set_value_to = null) + { + var auto_cell = row.Cells[col.Index] as DataGridViewCheckBoxCell; + if (auto_cell != null) + { + var old_value = (bool) auto_cell.Value; + + bool value = set_value_to ?? old_value; + IsAutoInstalled = value; + InstalledMod.AutoInstalled = value; + + if (old_value != value) + { + auto_cell.Value = value; + } + } + } private bool Equals(GUIMod other) { diff --git a/GUI/Main.Designer.cs b/GUI/Main.Designer.cs index 242eab799c..e8d01f30a5 100644 --- a/GUI/Main.Designer.cs +++ b/GUI/Main.Designer.cs @@ -72,6 +72,7 @@ private void InitializeComponent() this.ModList = new CKAN.MainModListGUI(); this.InstallAllCheckbox = new System.Windows.Forms.CheckBox(); this.Installed = new System.Windows.Forms.DataGridViewCheckBoxColumn(); + this.AutoInstalled = new System.Windows.Forms.DataGridViewCheckBoxColumn(); this.UpdateCol = new System.Windows.Forms.DataGridViewCheckBoxColumn(); this.ReplaceCol = new System.Windows.Forms.DataGridViewCheckBoxColumn(); this.ModName = new System.Windows.Forms.DataGridViewTextBoxColumn(); @@ -536,6 +537,7 @@ private void InitializeComponent() this.ModList.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize; this.ModList.Columns.AddRange(new System.Windows.Forms.DataGridViewColumn[] { this.Installed, + this.AutoInstalled, this.UpdateCol, this.ReplaceCol, this.ModName, @@ -571,6 +573,14 @@ private void InitializeComponent() this.Installed.DefaultCellStyle.Alignment = System.Windows.Forms.DataGridViewContentAlignment.MiddleCenter; this.Installed.Width = 50; // + // AutoInstalled + // + this.AutoInstalled.HeaderText = "Auto-installed"; + this.AutoInstalled.Name = "AutoInstalled"; + this.AutoInstalled.SortMode = System.Windows.Forms.DataGridViewColumnSortMode.Programmatic; + this.AutoInstalled.DefaultCellStyle.Alignment = System.Windows.Forms.DataGridViewContentAlignment.MiddleCenter; + this.AutoInstalled.Width = 50; + // // UpdateCol // this.UpdateCol.HeaderText = "Update"; @@ -1373,6 +1383,7 @@ private void InitializeComponent() public CKAN.MainModListGUI ModList; private System.Windows.Forms.CheckBox InstallAllCheckbox; private System.Windows.Forms.DataGridViewCheckBoxColumn Installed; + private System.Windows.Forms.DataGridViewCheckBoxColumn AutoInstalled; private System.Windows.Forms.DataGridViewCheckBoxColumn UpdateCol; private System.Windows.Forms.DataGridViewCheckBoxColumn ReplaceCol; private System.Windows.Forms.DataGridViewTextBoxColumn ModName; diff --git a/GUI/Main.cs b/GUI/Main.cs index 37b4d0d71b..9ac3919f3a 100644 --- a/GUI/Main.cs +++ b/GUI/Main.cs @@ -45,6 +45,8 @@ public KSPManager Manager get { return manager; } set { manager = value; } } + + private bool needRegistrySave = false; public MainModList mainModList { get; } @@ -502,6 +504,13 @@ protected override void OnFormClosing(FormClosingEventArgs e) // Save settings. configuration.Save(); + + if (needRegistrySave) + { + // Save registry + RegistryManager.Instance(CurrentInstance).Save(false); + } + base.OnFormClosing(e); } @@ -679,7 +688,7 @@ private async Task UpdateChangeSetAndConflicts(IRegistryQuerier registry) Dictionary new_conflicts = null; bool too_many_provides_thrown = false; - var user_change_set = mainModList.ComputeUserChangeSet(); + var user_change_set = mainModList.ComputeUserChangeSet(registry); try { var module_installer = ModuleInstaller.GetInstance(CurrentInstance, Manager.Cache, GUI.user); @@ -689,7 +698,7 @@ private async Task UpdateChangeSetAndConflicts(IRegistryQuerier registry) { // Need to be recomputed due to ComputeChangeSetFromModList possibly changing it with too many provides handling. AddStatusMessage(k.ShortDescription); - user_change_set = mainModList.ComputeUserChangeSet(); + user_change_set = mainModList.ComputeUserChangeSet(registry); new_conflicts = MainModList.ComputeConflictsFromModList(registry, user_change_set, CurrentInstance.VersionCriteria()); full_change_set = null; } @@ -780,9 +789,8 @@ private void Filter(GUIModFilter filter) // Ask the configuration which columns to show. foreach (DataGridViewColumn col in ModList.Columns) { - // Start with the third column, because the first one is always shown - // and the 2nd/3rd are handled by UpdateModsList(). - if (col.Index > 2) + // Some columns are always shown, and others are handled by UpdateModsList() + if (col.Name != "Installed" && col.Name != "UpdateCol" && col.Name != "ReplaceCol") { col.Visible = !configuration.HiddenColumnNames.Contains(col.Name); } @@ -800,9 +808,10 @@ private void Filter(GUIModFilter filter) case GUIModFilter.Replaceable: FilterToolButton.Text = "Filter (Replaceable)"; break; case GUIModFilter.Cached: FilterToolButton.Text = "Filter (Cached)"; break; case GUIModFilter.NewInRepository: FilterToolButton.Text = "Filter (New)"; break; - case GUIModFilter.NotInstalled: FilterToolButton.Text = "Filter (Not installed)"; - ModList.Columns[5].Visible = false; - ModList.Columns[9].Visible = false; break; + case GUIModFilter.NotInstalled: ModList.Columns["InstalledVersion"].Visible = false; + ModList.Columns["InstallDate"].Visible = false; + ModList.Columns["AutoInstalled"].Visible = false; + FilterToolButton.Text = "Filter (Not installed)"; break; default: FilterToolButton.Text = "Filter (Compatible)"; break; } } @@ -1186,8 +1195,11 @@ private void reinstallToolStripMenuItem_Click(object sender, EventArgs e) new ModChange(module, GUIModChangeType.Remove, null) }; // Then everything we need to re-install: - HashSet goners = registry.FindReverseDependencies( - new List() { module.Identifier } + var revdep = registry.FindReverseDependencies(new List() { module.Identifier }); + var goners = revdep.Union( + registry.FindRemovableAutoInstalled( + registry.InstalledModules.Where(im => !revdep.Contains(im.identifier)) + ).Select(im => im.Module.identifier) ); foreach (string id in goners) { diff --git a/GUI/MainChangeset.cs b/GUI/MainChangeset.cs index 7245e97132..6fcb2e1d0d 100644 --- a/GUI/MainChangeset.cs +++ b/GUI/MainChangeset.cs @@ -85,11 +85,10 @@ private void ClearChangeSet() GUIMod mod = row.Tag as GUIMod; if (mod.IsInstallChecked != mod.IsInstalled) { - mod.SetInstallChecked(row, mod.IsInstalled); - + mod.SetInstallChecked(row, Installed, mod.IsInstalled); } - mod.SetUpgradeChecked(row, false); - mod.SetReplaceChecked(row, false); + mod.SetUpgradeChecked(row, UpdateCol, false); + mod.SetReplaceChecked(row, ReplaceCol, false); } } @@ -139,7 +138,7 @@ private void ConfirmChangesButton_Click(object sender, EventArgs e) // TODO Work out why this is. installWorker.RunWorkerAsync( new KeyValuePair, RelationshipResolverOptions>( - mainModList.ComputeUserChangeSet().ToList(), + mainModList.ComputeUserChangeSet(RegistryManager.Instance(Main.Instance.CurrentInstance).registry).ToList(), RelationshipResolver.DependsOnlyOpts() ) ); diff --git a/GUI/MainModList.cs b/GUI/MainModList.cs index 63f7dcad2a..c85935d9e7 100644 --- a/GUI/MainModList.cs +++ b/GUI/MainModList.cs @@ -32,10 +32,11 @@ private IEnumerable _SortRowsByColumn(IEnumerable (r.Tag as GUIMod)?.DownloadCount ?? 0); + case 0: case 1: + case 2: case 3: return Sort(rows, CheckboxSorter); + case 9: return Sort(rows, DownloadSizeSorter); + case 10: return Sort(rows, InstallDateSorter); + case 11: return Sort(rows, r => (r.Tag as GUIMod)?.DownloadCount ?? 0); } return Sort(rows, DefaultSorter); } @@ -249,7 +250,8 @@ private void _UpdateModsList(IEnumerable mc, Dictionary AddLogMessage("Updating filters..."); - var has_any_updates = gui_mods.Any(mod => mod.HasUpdate); + var has_any_updates = gui_mods.Any(mod => mod.HasUpdate); + var has_any_installed = gui_mods.Any(mod => mod.IsInstalled); var has_any_replacements = gui_mods.Any(mod => mod.IsInstalled && mod.HasReplacement); //TODO Consider using smart enumeration pattern so stuff like this is easier @@ -282,8 +284,9 @@ private void _UpdateModsList(IEnumerable mc, Dictionary // Hide update and replacement columns if not needed. // Write it to the configuration, else they are hidden agian after a filter change. // After the update / replacement, they are hidden again. - ModList.Columns[1].Visible = has_any_updates; - ModList.Columns[2].Visible = has_any_replacements; + ModList.Columns["UpdateCol"].Visible = has_any_updates; + ModList.Columns["AutoInstalled"].Visible = has_any_installed && !configuration.HiddenColumnNames.Contains("AutoInstalled"); + ModList.Columns["ReplaceCol"].Visible = has_any_replacements; AddLogMessage("Updating tray..."); UpdateTrayInfo(); @@ -311,7 +314,7 @@ private void _MarkModForInstall(string identifier, bool uninstall) var mod = (GUIMod)row.Tag; if (mod.Identifier == identifier) { - mod.SetInstallChecked(row, !uninstall); + mod.SetInstallChecked(row, Installed, !uninstall); } } @@ -324,7 +327,7 @@ public void _MarkModForUpdate(string identifier) { DataGridViewRow row = mainModList.full_list_of_mod_rows[identifier]; var mod = (GUIMod)row.Tag; - mod.SetUpgradeChecked(row, true); + mod.SetUpgradeChecked(row, UpdateCol, true); } private void ModList_SelectedIndexChanged(object sender, EventArgs e) @@ -373,7 +376,7 @@ private void ModList_HeaderMouseClick(object sender, DataGridViewCellMouseEventA ModListHeaderContextMenuStrip.Items.AddRange( ModList.Columns.Cast() - .Where(col => col.Index > 2) + .Where(col => col.Name != "Installed" && col.Name != "UpdateCol" && col.Name != "ReplaceCol") .Select(col => new ToolStripMenuItem() { Name = col.Name, @@ -431,7 +434,7 @@ private void ModList_KeyDown(object sender, KeyEventArgs e) case Keys.Space: // If they've focused one of the checkbox columns, don't intercept - if (ModList.CurrentCell.ColumnIndex > 2) + if (ModList.CurrentCell.ColumnIndex > 3) { DataGridViewRow row = ModList.CurrentRow; // Toggle Update column if enabled, otherwise Install @@ -553,23 +556,27 @@ private async void ModList_CellValueChanged(object sender, DataGridViewCellEvent if (!string.IsNullOrEmpty(cmd)) Process.Start(cmd); } - else if (column_index <= 2) + else { GUIMod gui_mod = row?.Tag as GUIMod; if (gui_mod != null) { - switch (column_index) + switch (ModList.Columns[column_index].Name) { - case 0: - gui_mod.SetInstallChecked(row); + case "Installed": + gui_mod.SetInstallChecked(row, Installed); if (gui_mod.IsInstallChecked) last_mod_to_have_install_toggled.Push(gui_mod); break; - case 1: - gui_mod.SetUpgradeChecked(row); + case "AutoInstalled": + gui_mod.SetAutoInstallChecked(row, AutoInstalled); + needRegistrySave = true; break; - case 2: - gui_mod.SetReplaceChecked(row); + case "UpdateCol": + gui_mod.SetUpgradeChecked(row, UpdateCol); + break; + case "ReplaceCol": + gui_mod.SetReplaceChecked(row, ReplaceCol); break; } await UpdateChangeSetAndConflicts( @@ -594,7 +601,7 @@ private void InstallAllCheckbox_CheckChanged(object sender, EventArgs e) GUIMod mod = row.Tag as GUIMod; if (mod.IsInstallChecked) { - mod.SetInstallChecked(row, false); + mod.SetInstallChecked(row, Installed, false); } } } @@ -775,6 +782,13 @@ public async Task> ComputeChangeSetFromModList( changeSet.Add(new ModChange(new GUIMod(module_by_version, registry, version), GUIModChangeType.Remove, null)); modules_to_remove.Add(module_by_version); } + foreach (var im in registry.FindRemovableAutoInstalled( + registry.InstalledModules.Where(im => !modules_to_remove.Any(m => m.identifier == im.identifier)) + )) + { + changeSet.Add(new ModChange(new GUIMod(im.Module, registry, version), GUIModChangeType.Remove, new SelectionReason.NoLongerUsed())); + modules_to_remove.Add(im.Module); + } bool handled_all_too_many_provides = false; while (!handled_all_too_many_provides) @@ -879,6 +893,16 @@ private DataGridViewRow MakeRow(GUIMod mod, List changes, bool hideEp Value = mod.IsAutodetected ? "AD" : "-" }; + var autoInstalled = mod.IsInstalled + ? (DataGridViewCell) new DataGridViewCheckBoxCell() + { + Value = mod.IsAutoInstalled + } + : new DataGridViewTextBoxCell() + { + Value = "-" + }; + var updating = mod.IsInstallable() && mod.HasUpdate ? (DataGridViewCell) new DataGridViewCheckBoxCell() { @@ -933,10 +957,11 @@ private DataGridViewRow MakeRow(GUIMod mod, List changes, bool hideEp var installDate = new DataGridViewTextBoxCell() { Value = mod.InstallDate }; var desc = new DataGridViewTextBoxCell() { Value = mod.Abstract }; - item.Cells.AddRange(selecting, updating, replacing, name, author, installVersion, latestVersion, compat, size, installDate, downloadCount, desc); + item.Cells.AddRange(selecting, autoInstalled, updating, replacing, name, author, installVersion, latestVersion, compat, size, installDate, downloadCount, desc); - selecting.ReadOnly = selecting is DataGridViewTextBoxCell; - updating.ReadOnly = updating is DataGridViewTextBoxCell; + selecting.ReadOnly = selecting is DataGridViewTextBoxCell; + autoInstalled.ReadOnly = autoInstalled is DataGridViewTextBoxCell; + updating.ReadOnly = updating is DataGridViewTextBoxCell; return item; } @@ -1055,8 +1080,10 @@ public static Dictionary ComputeConflictsFromModList(IRegistryQu item => item.Value); } - public HashSet ComputeUserChangeSet() + public HashSet ComputeUserChangeSet(IRegistryQuerier registry) { + var removableAuto = registry?.FindRemovableAutoInstalled(registry?.InstalledModules) + ?? new InstalledModule[] {}; return new HashSet( Modules .Where(mod => mod.IsInstallable()) @@ -1064,6 +1091,10 @@ public HashSet ComputeUserChangeSet() .Where(change => change.HasValue) .Select(change => change.Value) .Select(change => new ModChange(change.Key, change.Value, null)) + .Union(removableAuto.Select(im => new ModChange( + new GUIMod(im, registry, Main.Instance.CurrentInstance.VersionCriteria()), + GUIModChangeType.Remove, + new SelectionReason.NoLongerUsed()))) ); } } diff --git a/Tests/GUI/GH1866.cs b/Tests/GUI/GH1866.cs index b28b71ad2d..9ecc807a37 100644 --- a/Tests/GUI/GH1866.cs +++ b/Tests/GUI/GH1866.cs @@ -64,7 +64,7 @@ public void Up() // install it and set it as pre-installed _manager.Cache.Store(TestData.DogeCoinFlag_101_module(), TestData.DogeCoinFlagZip()); - _registry.RegisterModule(_anyVersionModule, new string[] { }, _instance.KSP); + _registry.RegisterModule(_anyVersionModule, new string[] { }, _instance.KSP, false); _registry.AddAvailable(_anyVersionModule); ModuleInstaller.GetInstance(_instance.KSP, _manager.Cache, _manager.User).InstallList( @@ -83,7 +83,9 @@ public void Up() // todo: refactor the column header code to allow mocking of the GUI without creating columns _listGui.Columns.Add(new DataGridViewCheckBoxColumn()); _listGui.Columns.Add(new DataGridViewCheckBoxColumn()); - for (int i = 0; i < 10; i++) + _listGui.Columns.Add(new DataGridViewCheckBoxColumn()); + _listGui.Columns.Add(new DataGridViewCheckBoxColumn()); + for (int i = 0; i < 9; i++) { _listGui.Columns.Add(i.ToString(), "Column" + i); } @@ -121,7 +123,7 @@ public void TestSimple() // the header row adds one to the count Assert.AreEqual(modules.Count + 1, _listGui.Rows.Count); - // sort by version, this is the fuse-lighting + // sort by a text column, this is the fuse-lighting _listGui.Sort(_listGui.Columns[6], ListSortDirection.Descending); // mark the mod for install, after completion we will get an exception @@ -135,7 +137,7 @@ public void TestSimple() { // perform the install of the "other" module - now we need to sort ModuleInstaller.GetInstance(_instance.KSP, _manager.Cache, _manager.User).InstallList( - _modList.ComputeUserChangeSet().Select(change => change.Mod.ToCkanModule()).ToList(), + _modList.ComputeUserChangeSet(null).Select(change => change.Mod.ToCkanModule()).ToList(), new RelationshipResolverOptions(), new NetAsyncModulesDownloader(_manager.User) ); diff --git a/Tests/GUI/GUIMod.cs b/Tests/GUI/GUIMod.cs index 1153b4d5c8..ef7d0b7b32 100644 --- a/Tests/GUI/GUIMod.cs +++ b/Tests/GUI/GUIMod.cs @@ -44,7 +44,7 @@ public void HasUpdateReturnsTrueWhenUpdateAvailible() var new_version = generatror.GeneratorRandomModule(version: new ModuleVersion("0.25"), ksp_version: tidy.KSP.Version(), identifier:old_version.identifier); var registry = Registry.Empty(); - registry.RegisterModule(old_version, Enumerable.Empty(), null); + registry.RegisterModule(old_version, Enumerable.Empty(), null, false); registry.AddAvailable(new_version); var mod = new GUIMod(old_version, registry, tidy.KSP.VersionCriteria()); diff --git a/Tests/GUI/MainModList.cs b/Tests/GUI/MainModList.cs index 472fdb90a9..2c57fdd2d9 100644 --- a/Tests/GUI/MainModList.cs +++ b/Tests/GUI/MainModList.cs @@ -50,7 +50,7 @@ public void OnModTypeFilterChanges_CallsEventHandler() public void ComputeChangeSetFromModList_WithEmptyList_HasEmptyChangeSet() { var item = new MainModList(delegate { }, delegate { return null; }); - Assert.That(item.ComputeUserChangeSet(), Is.Empty); + Assert.That(item.ComputeUserChangeSet(null), Is.Empty); } [Test] @@ -64,7 +64,7 @@ public async Task ComputeChangeSetFromModList_WithConflictingMods_ThrowsInconsis module.conflicts = new List { new ModuleRelationshipDescriptor { name = "kOS" } }; registry.AddAvailable(module); registry.AddAvailable(TestData.kOS_014_module()); - registry.RegisterModule(module, Enumerable.Empty(), tidy.KSP); + registry.RegisterModule(module, Enumerable.Empty(), tidy.KSP, false); var mainList = new MainModList(null, null, new GUIUser()); var mod = new GUIMod(module, registry, tidy.KSP.VersionCriteria()); @@ -73,7 +73,7 @@ public async Task ComputeChangeSetFromModList_WithConflictingMods_ThrowsInconsis mainList.ConstructModList(mods, null, true); mainList.Modules = new ReadOnlyCollection(mods); mod2.IsInstallChecked = true; - var computeTask = mainList.ComputeChangeSetFromModList(registry, mainList.ComputeUserChangeSet(), null, + var computeTask = mainList.ComputeChangeSetFromModList(registry, mainList.ComputeUserChangeSet(registry), null, tidy.KSP.VersionCriteria()); await UtilStatic.Throws(() => computeTask);