Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Treat mods with missing files as upgradeable/reinstallable #4067

Merged
merged 12 commits into from
Mar 25, 2024
3 changes: 1 addition & 2 deletions Cmdline/Action/List.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using System;
using System.IO;
using System.Collections.Generic;
using System.Linq;

Expand Down Expand Up @@ -55,7 +54,7 @@ public int RunCommand(CKAN.GameInstance instance, object raw_options)
{
var installed = new SortedDictionary<string, ModuleVersion>(registry.Installed());
var upgradeable = registry
.CheckUpgradeable(instance.VersionCriteria(), new HashSet<string>())
.CheckUpgradeable(instance, new HashSet<string>())
[true]
.Select(m => m.identifier)
.ToHashSet();
Expand Down
4 changes: 2 additions & 2 deletions Cmdline/Action/Upgrade.cs
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ public int RunCommand(CKAN.GameInstance instance, object raw_options)
if (options.upgrade_all)
{
var to_upgrade = registry
.CheckUpgradeable(instance.VersionCriteria(), new HashSet<string>())
.CheckUpgradeable(instance, new HashSet<string>())
[true];
if (to_upgrade.Count == 0)
{
Expand Down Expand Up @@ -225,7 +225,7 @@ private void UpgradeModules(GameInstanceManager manager,
.ToList();
// Modules allowed by THOSE modules' relationships
var upgradeable = registry
.CheckUpgradeable(crit, heldIdents, limiters)
.CheckUpgradeable(instance, heldIdents, limiters)
[true]
.ToDictionary(m => m.identifier,
m => m);
Expand Down
2 changes: 1 addition & 1 deletion ConsoleUI/InstallScreen.cs
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ public override void Run(ConsoleTheme theme, Action<ConsoleTheme> process = null
}
if (plan.Upgrade.Count > 0) {
var upgGroups = registry
.CheckUpgradeable(manager.CurrentInstance.VersionCriteria(),
.CheckUpgradeable(manager.CurrentInstance,
// Hold identifiers not chosen for upgrading
registry.Installed(false)
.Keys
Expand Down
2 changes: 1 addition & 1 deletion ConsoleUI/ModInfoScreen.cs
Original file line number Diff line number Diff line change
Expand Up @@ -267,7 +267,7 @@ private int addDependencies(int top = 8)
int nameW = midL - 2 - lblW - 2 - 1;
int depsH = (h - 2) * numDeps / (numDeps + numConfs);
var upgradeableGroups = registry
.CheckUpgradeable(manager.CurrentInstance.VersionCriteria(),
.CheckUpgradeable(manager.CurrentInstance,
new HashSet<string>());

AddObject(new ConsoleFrame(
Expand Down
2 changes: 1 addition & 1 deletion ConsoleUI/ModListScreen.cs
Original file line number Diff line number Diff line change
Expand Up @@ -607,7 +607,7 @@ private List<CkanModule> GetAllMods(ConsoleTheme theme, bool force = false)
}
}
upgradeableGroups = registry
.CheckUpgradeable(crit, new HashSet<string>());
.CheckUpgradeable(manager.CurrentInstance, new HashSet<string>());
}
return allMods;
}
Expand Down
25 changes: 17 additions & 8 deletions Core/Registry/IRegistryQuerier.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.IO;
using System.Linq;
using System.Collections.Generic;
using System.Collections.ObjectModel;
Expand Down Expand Up @@ -172,7 +173,7 @@ public static bool IsAutodetected(this IRegistryQuerier querier, string identifi
/// </summary>
public static bool HasUpdate(this IRegistryQuerier querier,
string identifier,
GameVersionCriteria versionCrit,
GameInstance instance,
out CkanModule latestMod,
ICollection<CkanModule> installed = null)
{
Expand All @@ -186,7 +187,7 @@ public static bool HasUpdate(this IRegistryQuerier querier,
// Check if it's available
try
{
latestMod = querier.LatestAvailable(identifier, versionCrit, null, installed);
latestMod = querier.LatestAvailable(identifier, instance.VersionCriteria(), null, installed);
}
catch
{
Expand All @@ -199,7 +200,15 @@ public static bool HasUpdate(this IRegistryQuerier querier,
// Check if the installed module is up to date
var comp = latestMod.version.CompareTo(instVer);
if (comp == -1
|| (comp == 0 && !querier.MetadataChanged(identifier)))
|| (comp == 0 && !querier.MetadataChanged(identifier)
// Check if any of the files or directories are missing
&& (instance == null
|| (querier.InstalledModule(identifier)
?.Files
.Select(instance.ToAbsoluteGameDir)
.All(p => Directory.Exists(p) || File.Exists(p))
// Manually installed, consider up to date
?? true))))
{
latestMod = null;
return false;
Expand All @@ -212,26 +221,26 @@ public static bool HasUpdate(this IRegistryQuerier querier,
}

public static Dictionary<bool, List<CkanModule>> CheckUpgradeable(this IRegistryQuerier querier,
GameVersionCriteria versionCrit,
GameInstance instance,
HashSet<string> heldIdents)
{
// Get the absolute latest versions ignoring restrictions,
// to break out of mutual version-depending deadlocks
var unlimited = querier.Installed(false)
.Keys
.Select(ident => !heldIdents.Contains(ident)
&& querier.HasUpdate(ident, versionCrit,
&& querier.HasUpdate(ident, instance,
out CkanModule latest)
&& !latest.IsDLC
? latest
: querier.GetInstalledVersion(ident))
.Where(m => m != null)
.ToList();
return querier.CheckUpgradeable(versionCrit, heldIdents, unlimited);
return querier.CheckUpgradeable(instance, heldIdents, unlimited);
}

public static Dictionary<bool, List<CkanModule>> CheckUpgradeable(this IRegistryQuerier querier,
GameVersionCriteria versionCrit,
GameInstance instance,
HashSet<string> heldIdents,
List<CkanModule> initial)
{
Expand All @@ -241,7 +250,7 @@ public static Dictionary<bool, List<CkanModule>> CheckUpgradeable(this IRegistry
foreach (var ident in initial.Select(module => module.identifier))
{
if (!heldIdents.Contains(ident)
&& querier.HasUpdate(ident, versionCrit,
&& querier.HasUpdate(ident, instance,
out CkanModule latest, initial)
&& !latest.IsDLC)
{
Expand Down
47 changes: 2 additions & 45 deletions Core/Registry/InstalledModule.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
using System.ComponentModel;
using System.Collections.Generic;
using System.IO;
using System.Security.Cryptography;
using System.Runtime.Serialization;

using Newtonsoft.Json;
Expand All @@ -12,51 +11,9 @@ namespace CKAN
[JsonObject(MemberSerialization.OptIn)]
public class InstalledModuleFile
{

// TODO: This class should also record file paths as well.
// It's just sha1 now for registry compatibility.

[JsonProperty("sha1_sum", NullValueHandling = NullValueHandling.Ignore)]
private readonly string sha1_sum;

public string Sha1 => sha1_sum;

public InstalledModuleFile(string path, GameInstance ksp)
{
string absolute_path = ksp.ToAbsoluteGameDir(path);
// TODO: What is the net performance cost of calculating this? Big files are not quick to hash!
sha1_sum = Sha1Sum(absolute_path);
}

// We need this because otherwise JSON.net tries to pass in
// our sha1's as paths, and things go wrong.
[JsonConstructor]
private InstalledModuleFile()
{
}

/// <summary>
/// Returns the sha1 sum of the given filename.
/// Returns null if passed a directory.
/// Throws an exception on failure to access the file.
/// </summary>
private static string Sha1Sum(string path)
public InstalledModuleFile()
{
if (Directory.Exists(path))
{
return null;
}

SHA1 hasher = SHA1.Create();

// Even if we throw an exception, the using block here makes sure
// we close our file.
using (var fh = File.OpenRead(path))
{
string sha1 = BitConverter.ToString(hasher.ComputeHash(fh));
fh.Close();
return sha1;
}
}
}

Expand Down Expand Up @@ -127,7 +84,7 @@ public InstalledModule(GameInstance ksp, CkanModule module, IEnumerable<string>
}

// IMF needs a KSP object so it can compute the SHA1.
installed_files[file] = new InstalledModuleFile(file, ksp);
installed_files[file] = new InstalledModuleFile();
}
}
}
Expand Down
9 changes: 7 additions & 2 deletions GUI/Controls/Changeset.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@
changes?.Select(ch => new ChangesetRow(ch, AlertLabels, conflicts))
.ToList()
?? new List<ChangesetRow>());
ChangesGrid.AutoResizeColumns();
}

public CkanModule SelectedItem => SelectedRow?.Change.Mod;
Expand Down Expand Up @@ -70,6 +69,7 @@
}
}
}
ChangesGrid.AutoResizeColumns();
}

private void ChangesGrid_CellClick(object sender, DataGridViewCellEventArgs e)
Expand Down Expand Up @@ -101,8 +101,13 @@

private void ChangesGrid_SelectionChanged(object sender, EventArgs e)
{
if (ChangesGrid.SelectedRows.Count > 0 && !Visible)
{
// Suppress selection while inactive
ChangesGrid.ClearSelection();
}
// Don't pop up mod info when they click the X icons
if (ChangesGrid.CurrentCell?.OwningColumn is DataGridViewTextBoxColumn)
else if (ChangesGrid.CurrentCell?.OwningColumn is DataGridViewTextBoxColumn)
{
OnSelectedItemsChanged?.Invoke(SelectedRow?.Change.Mod);
}
Expand Down Expand Up @@ -135,7 +140,7 @@
{
Change = change;
WarningLabel = alertLabels?.FirstOrDefault(l =>
l.ContainsModule(Main.Instance.CurrentInstance.game,

Check warning on line 143 in GUI/Controls/Changeset.cs

View workflow job for this annotation

GitHub Actions / build (6.8, Debug)

'Main.Instance' is obsolete: 'Main.Instance is a global singleton. Find a better way to access this object.'

Check warning on line 143 in GUI/Controls/Changeset.cs

View workflow job for this annotation

GitHub Actions / build (6.8, Release)

'Main.Instance' is obsolete: 'Main.Instance is a global singleton. Find a better way to access this object.'
Change.Mod.identifier));
conflicts?.TryGetValue(Change.Mod, out Conflict);
Reasons = Conflict != null
Expand All @@ -159,11 +164,11 @@

public bool ConfirmUncheck()
=> Change.IsAutoRemoval
? Main.Instance.YesNoDialog(

Check warning on line 167 in GUI/Controls/Changeset.cs

View workflow job for this annotation

GitHub Actions / build (6.8, Debug)

'Main.Instance' is obsolete: 'Main.Instance is a global singleton. Find a better way to access this object.'

Check warning on line 167 in GUI/Controls/Changeset.cs

View workflow job for this annotation

GitHub Actions / build (6.8, Release)

'Main.Instance' is obsolete: 'Main.Instance is a global singleton. Find a better way to access this object.'
string.Format(Properties.Resources.ChangesetConfirmRemoveAutoRemoval, Change.Mod),
Properties.Resources.ChangesetConfirmRemoveAutoRemovalYes,
Properties.Resources.ChangesetConfirmRemoveAutoRemovalNo)
: Main.Instance.YesNoDialog(

Check warning on line 171 in GUI/Controls/Changeset.cs

View workflow job for this annotation

GitHub Actions / build (6.8, Debug)

'Main.Instance' is obsolete: 'Main.Instance is a global singleton. Find a better way to access this object.'

Check warning on line 171 in GUI/Controls/Changeset.cs

View workflow job for this annotation

GitHub Actions / build (6.8, Release)

'Main.Instance' is obsolete: 'Main.Instance is a global singleton. Find a better way to access this object.'
string.Format(Properties.Resources.ChangesetConfirmRemoveUserRequested, ChangeType, Change.Mod),
Properties.Resources.ChangesetConfirmRemoveUserRequestedYes,
Properties.Resources.ChangesetConfirmRemoveUserRequestedNo);
Expand Down
1 change: 0 additions & 1 deletion GUI/Controls/ChooseRecommendedMods.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading