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);