Skip to content

Commit

Permalink
Auto-uninstall auto-installed modules
Browse files Browse the repository at this point in the history
  • Loading branch information
HebaruSan committed May 6, 2019
1 parent df8fb70 commit 7df5118
Show file tree
Hide file tree
Showing 12 changed files with 195 additions and 76 deletions.
65 changes: 22 additions & 43 deletions Core/ModuleInstaller.cs
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,8 @@ public static string CachedOrDownload(CkanModule module, NetModuleCache cache, s
public void InstallList(List<string> 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);
}

/// <summary>
Expand Down Expand Up @@ -214,7 +215,7 @@ public void InstallList(ICollection<CkanModule> 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);
Expand Down Expand Up @@ -243,31 +244,6 @@ public void InstallList(ICollection<CkanModule> 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();
}
}

/// <summary>
/// Returns the module contents if and only if we have it
/// available in our cache. Returns null, otherwise.
Expand All @@ -291,19 +267,17 @@ public IEnumerable<string> GetModuleContentsList(CkanModule module)
/// <summary>
/// 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.
///
/// Propagates a BadMetadataKraken if our install metadata is bad.
/// 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.
/// </summary>
//
// 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);

Expand Down Expand Up @@ -337,7 +311,7 @@ private void Install(CkanModule module, string filename = null)
IEnumerable<string> 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.
Expand Down Expand Up @@ -762,9 +736,13 @@ public void UninstallList(IEnumerable<string> mods, bool ConfirmPrompt = true, I
}

// Find all the things which need uninstalling.
IEnumerable<string> goners = mods.Union(
IEnumerable<string> revdep = mods.Union(
registry_manager.registry.FindReverseDependencies(
mods.Except(installing ?? new string[] {})));
IEnumerable<string> 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())
Expand Down Expand Up @@ -999,22 +977,23 @@ public HashSet<string> AddParentDirectories(HashSet<string> directories)
/// </summary>
/// <param name="add">Add.</param>
/// <param name="remove">Remove.</param>
public void AddRemove(IEnumerable<CkanModule> add = null, IEnumerable<string> remove = null, bool enforceConsistency = true)
public void AddRemove(IEnumerable<CkanModule> add = null, IEnumerable<InstalledModule> 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.

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);
Expand Down Expand Up @@ -1050,7 +1029,7 @@ public void Upgrade(IEnumerable<CkanModule> 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<string>();
var to_remove = new List<InstalledModule>();

// Let's discover what we need to do with each module!
foreach (CkanModule module in modules)
Expand All @@ -1071,7 +1050,7 @@ public void Upgrade(IEnumerable<CkanModule> 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))
Expand Down Expand Up @@ -1105,7 +1084,7 @@ public void Replace(IEnumerable<ModuleReplacement> replacements, RelationshipRes
{
log.Debug("Using Replace method");
List<CkanModule> modsToInstall = new List<CkanModule>();
var modsToRemove = new List<string>();
var modsToRemove = new List<InstalledModule>();
foreach (ModuleReplacement repl in replacements)
{
modsToInstall.Add(repl.ReplaceWith);
Expand Down Expand Up @@ -1139,7 +1118,7 @@ public void Replace(IEnumerable<ModuleReplacement> 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
Expand All @@ -1150,7 +1129,7 @@ public void Replace(IEnumerable<ModuleReplacement> 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))
Expand Down
11 changes: 11 additions & 0 deletions Core/Registry/IRegistryQuerier.cs
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,17 @@ List<CkanModule> LatestAvailableWithProvides(
/// </summary>
HashSet<string> FindReverseDependencies(IEnumerable<string> modules);

/// <summary>
/// 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.
/// </summary>
/// <param name="installedModules">The modules currently installed</param>
/// <returns>
/// Sequence of removable auto-installed modules, if any
/// </returns>
IEnumerable<InstalledModule> FindRemovableAutoInstalled(IEnumerable<InstalledModule> installedModules);

/// <summary>
/// Gets the installed version of a mod. Does not check for provided or autodetected mods.
Expand Down
11 changes: 9 additions & 2 deletions Core/Registry/InstalledModule.cs
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ public class InstalledModule

[JsonProperty] private CkanModule source_module;

// private static readonly ILog log = LogManager.GetLogger(typeof(InstalledModule));
[JsonProperty] 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
Expand All @@ -110,15 +110,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<string> relative_files)
public InstalledModule(KSP ksp, CkanModule module, IEnumerable<string> relative_files, bool autoInstalled)
{
install_time = DateTime.Now;
source_module = module;
installed_files = new Dictionary<string, InstalledModuleFile>();
auto_installed = autoInstalled;

foreach (string file in relative_files)
{
Expand Down
45 changes: 42 additions & 3 deletions Core/Registry/Registry.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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.
/// </summary>
public void RegisterModule(CkanModule mod, IEnumerable<string> absolute_files, KSP ksp)
public void RegisterModule(CkanModule mod, IEnumerable<string> absolute_files, KSP ksp, bool autoInstalled)
{
SealionTransaction();

Expand Down Expand Up @@ -791,7 +792,7 @@ public void RegisterModule(CkanModule mod, IEnumerable<string> 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);
}

Expand Down Expand Up @@ -1118,6 +1119,44 @@ public HashSet<string> FindReverseDependencies(IEnumerable<string> modules_to_re
return FindReverseDependencies(modules_to_remove, installed, new HashSet<string>(installed_dlls.Keys), _installedDlcModules);
}

/// <summary>
/// Find auto-installed modules that have no depending modules
/// or only auto-installed depending modules.
/// </summary>
/// <param name="installedModules">The modules currently installed</param>
/// <param name="dlls">The DLLs that are manually installed</param>
/// <param name="dlc">The DLCs that are installed</param>
/// <returns>
/// Sequence of removable auto-installed modules, if any
/// </returns>
private static IEnumerable<InstalledModule> FindRemovableAutoInstalled(
IEnumerable<InstalledModule> installedModules,
IEnumerable<string> dlls,
IDictionary<string, UnmanagedModuleVersion> 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<string> { im.identifier }, instCkanMods, dlls, dlc)
.All(id => installedModules.First(orig => orig.identifier == id).AutoInstalled));
}

/// <summary>
/// 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.
/// </summary>
/// <param name="installedModules">The modules currently installed</param>
/// <returns>
/// Sequence of removable auto-installed modules, if any
/// </returns>
public IEnumerable<InstalledModule> FindRemovableAutoInstalled(IEnumerable<InstalledModule> installedModules)
{
return FindRemovableAutoInstalled(installedModules, InstalledDlls, InstalledDlc);
}

/// <summary>
/// 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.
Expand Down
8 changes: 8 additions & 0 deletions Core/Relationships/RelationshipResolver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand Down
30 changes: 26 additions & 4 deletions GUI/GUIMod.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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; }
Expand Down Expand Up @@ -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("-"))
Expand Down Expand Up @@ -285,7 +289,7 @@ public static implicit operator CkanModule(GUIMod mod)
public void SetUpgradeChecked(DataGridViewRow row, bool? set_value_to = null)
{
//Contract.Requires<ArgumentException>(row.Cells[1] is DataGridViewCheckBoxCell);
var update_cell = row.Cells[1] as DataGridViewCheckBoxCell;
var update_cell = row.Cells["UpdateCol"] as DataGridViewCheckBoxCell;
if (update_cell != null)
{
var old_value = (bool) update_cell.Value;
Expand All @@ -300,7 +304,7 @@ public void SetUpgradeChecked(DataGridViewRow row, bool? set_value_to = null)
public void SetInstallChecked(DataGridViewRow row, bool? set_value_to = null)
{
//Contract.Requires<ArgumentException>(row.Cells[0] is DataGridViewCheckBoxCell);
var install_cell = row.Cells[0] as DataGridViewCheckBoxCell;
var install_cell = row.Cells["Installed"] as DataGridViewCheckBoxCell;
if (install_cell != null)
{
bool changeTo = set_value_to ?? (bool)install_cell.Value;
Expand All @@ -326,7 +330,7 @@ public void SetInstallChecked(DataGridViewRow row, bool? set_value_to = null)

public void SetReplaceChecked(DataGridViewRow row, bool? set_value_to = null)
{
var replace_cell = row.Cells[2] as DataGridViewCheckBoxCell;
var replace_cell = row.Cells["ReplaceCol"] as DataGridViewCheckBoxCell;
if (replace_cell != null)
{
var old_value = (bool) replace_cell.Value;
Expand All @@ -337,6 +341,24 @@ public void SetReplaceChecked(DataGridViewRow row, bool? set_value_to = null)
replace_cell.Value = value;
}
}

public void SetAutoInstallChecked(DataGridViewRow row, bool? set_value_to = null)
{
var auto_cell = row.Cells["AutoInstalled"] 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)
{
Expand Down
Loading

0 comments on commit 7df5118

Please sign in to comment.