diff --git a/CHANGELOG.md b/CHANGELOG.md index 8420f5199..0717bcaef 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ All notable changes to this project will be documented in this file. - [ConsoleUI] Add downloads column for ConsoleUI (#4063 by: HebaruSan) - [ConsoleUI] Play game option for ConsoleUI (#4064 by: HebaruSan) - [ConsoleUI] ConsoleUI prompt to delete non-empty folders after uninstall (#4066 by: HebaruSan) +- [Multiple] Treat mods with missing files as upgradeable/reinstallable (#4067 by: HebaruSan) ### Bugfixes diff --git a/Cmdline/Action/List.cs b/Cmdline/Action/List.cs index 67040b184..051d33d5c 100644 --- a/Cmdline/Action/List.cs +++ b/Cmdline/Action/List.cs @@ -1,5 +1,4 @@ using System; -using System.IO; using System.Collections.Generic; using System.Linq; @@ -55,7 +54,7 @@ public int RunCommand(CKAN.GameInstance instance, object raw_options) { var installed = new SortedDictionary(registry.Installed()); var upgradeable = registry - .CheckUpgradeable(instance.VersionCriteria(), new HashSet()) + .CheckUpgradeable(instance, new HashSet()) [true] .Select(m => m.identifier) .ToHashSet(); diff --git a/Cmdline/Action/Upgrade.cs b/Cmdline/Action/Upgrade.cs index 4ec188bdc..3bfc9dd7b 100644 --- a/Cmdline/Action/Upgrade.cs +++ b/Cmdline/Action/Upgrade.cs @@ -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()) + .CheckUpgradeable(instance, new HashSet()) [true]; if (to_upgrade.Count == 0) { @@ -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); diff --git a/ConsoleUI/InstallScreen.cs b/ConsoleUI/InstallScreen.cs index 6258c4548..46c6e9987 100644 --- a/ConsoleUI/InstallScreen.cs +++ b/ConsoleUI/InstallScreen.cs @@ -88,7 +88,7 @@ public override void Run(ConsoleTheme theme, Action 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 diff --git a/ConsoleUI/ModInfoScreen.cs b/ConsoleUI/ModInfoScreen.cs index de4e26f76..aa14ec1bb 100644 --- a/ConsoleUI/ModInfoScreen.cs +++ b/ConsoleUI/ModInfoScreen.cs @@ -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()); AddObject(new ConsoleFrame( diff --git a/ConsoleUI/ModListScreen.cs b/ConsoleUI/ModListScreen.cs index 7d92de5bb..72b89aafa 100644 --- a/ConsoleUI/ModListScreen.cs +++ b/ConsoleUI/ModListScreen.cs @@ -607,7 +607,7 @@ private List GetAllMods(ConsoleTheme theme, bool force = false) } } upgradeableGroups = registry - .CheckUpgradeable(crit, new HashSet()); + .CheckUpgradeable(manager.CurrentInstance, new HashSet()); } return allMods; } diff --git a/Core/Registry/IRegistryQuerier.cs b/Core/Registry/IRegistryQuerier.cs index d259660f2..5acfcad1f 100644 --- a/Core/Registry/IRegistryQuerier.cs +++ b/Core/Registry/IRegistryQuerier.cs @@ -1,4 +1,5 @@ using System; +using System.IO; using System.Linq; using System.Collections.Generic; using System.Collections.ObjectModel; @@ -172,7 +173,7 @@ public static bool IsAutodetected(this IRegistryQuerier querier, string identifi /// public static bool HasUpdate(this IRegistryQuerier querier, string identifier, - GameVersionCriteria versionCrit, + GameInstance instance, out CkanModule latestMod, ICollection installed = null) { @@ -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 { @@ -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; @@ -212,7 +221,7 @@ public static bool HasUpdate(this IRegistryQuerier querier, } public static Dictionary> CheckUpgradeable(this IRegistryQuerier querier, - GameVersionCriteria versionCrit, + GameInstance instance, HashSet heldIdents) { // Get the absolute latest versions ignoring restrictions, @@ -220,18 +229,18 @@ public static Dictionary> CheckUpgradeable(this IRegistry 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> CheckUpgradeable(this IRegistryQuerier querier, - GameVersionCriteria versionCrit, + GameInstance instance, HashSet heldIdents, List initial) { @@ -241,7 +250,7 @@ public static Dictionary> 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) { diff --git a/Core/Registry/InstalledModule.cs b/Core/Registry/InstalledModule.cs index 53ac85775..0d1434ebc 100644 --- a/Core/Registry/InstalledModule.cs +++ b/Core/Registry/InstalledModule.cs @@ -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; @@ -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() - { - } - - /// - /// Returns the sha1 sum of the given filename. - /// Returns null if passed a directory. - /// Throws an exception on failure to access the file. - /// - 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; - } } } @@ -127,7 +84,7 @@ public InstalledModule(GameInstance ksp, CkanModule module, IEnumerable } // IMF needs a KSP object so it can compute the SHA1. - installed_files[file] = new InstalledModuleFile(file, ksp); + installed_files[file] = new InstalledModuleFile(); } } } diff --git a/GUI/Controls/Changeset.cs b/GUI/Controls/Changeset.cs index a5514f40d..fa4e18843 100644 --- a/GUI/Controls/Changeset.cs +++ b/GUI/Controls/Changeset.cs @@ -40,7 +40,6 @@ public void LoadChangeset(List changes, changes?.Select(ch => new ChangesetRow(ch, AlertLabels, conflicts)) .ToList() ?? new List()); - ChangesGrid.AutoResizeColumns(); } public CkanModule SelectedItem => SelectedRow?.Change.Mod; @@ -70,6 +69,7 @@ private void ChangesGrid_DataBindingComplete(object sender, DataGridViewBindingC } } } + ChangesGrid.AutoResizeColumns(); } private void ChangesGrid_CellClick(object sender, DataGridViewCellEventArgs e) @@ -101,8 +101,13 @@ private void BackButton_Click(object sender, EventArgs e) 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); } diff --git a/GUI/Controls/ChooseRecommendedMods.Designer.cs b/GUI/Controls/ChooseRecommendedMods.Designer.cs index 959383f7d..daff9b8d3 100644 --- a/GUI/Controls/ChooseRecommendedMods.Designer.cs +++ b/GUI/Controls/ChooseRecommendedMods.Designer.cs @@ -58,7 +58,6 @@ private void InitializeComponent() this.Toolbar.ImageScalingSize = new System.Drawing.Size(24, 24); this.Toolbar.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { this.UncheckAllButton, - this.AlwaysUncheckAllButton, this.UncheckCheckSeparator, this.CheckAllButton, this.CheckRecommendationsButton}); diff --git a/GUI/Controls/ManageMods.cs b/GUI/Controls/ManageMods.cs index d56b0eb01..9e2c330ca 100644 --- a/GUI/Controls/ManageMods.cs +++ b/GUI/Controls/ManageMods.cs @@ -334,16 +334,16 @@ private void labelMenuItem_Click(object sender, EventArgs e) { mlbl.Remove(currentInstance.game, module.Identifier); } - if (mlbl.HoldVersion) - { - UpdateAllToolButton.Enabled = mainModList.Modules.Any(mod => - mod.HasUpdate && !Main.Instance.LabelsHeld(mod.Identifier)); - } var registry = RegistryManager.Instance(currentInstance, repoData).registry; mainModList.ReapplyLabels(module, Conflicts?.ContainsKey(module) ?? false, currentInstance.Name, currentInstance.game, registry); mainModList.ModuleLabels.Save(ModuleLabelList.DefaultPath); UpdateHiddenTagsAndLabels(); + if (mlbl.HoldVersion) + { + UpdateCol.Visible = UpdateAllToolButton.Enabled = + mainModList.ResetHasUpdate(currentInstance, registry, ChangeSet, ModGrid.Rows); + } } private void editLabelsToolStripMenuItem_Click(object sender, EventArgs e) @@ -359,6 +359,8 @@ private void editLabelsToolStripMenuItem_Click(object sender, EventArgs e) currentInstance.Name, currentInstance.game, registry); } UpdateHiddenTagsAndLabels(); + UpdateCol.Visible = UpdateAllToolButton.Enabled = + mainModList.ResetHasUpdate(currentInstance, registry, ChangeSet, ModGrid.Rows); } #endregion @@ -877,17 +879,26 @@ private void ModGrid_CellValueChanged(object sender, DataGridViewCellEventArgs e switch (ModGrid.Columns[e.ColumnIndex].Name) { case "Installed": - gmod.SelectedMod = nowChecked ? gmod.InstalledMod?.Module - ?? gmod.LatestAvailableMod + gmod.SelectedMod = nowChecked ? gmod.SelectedMod + ?? gmod.InstalledMod?.Module + ?? gmod.LatestAvailableMod : null; break; case "UpdateCol": gmod.SelectedMod = nowChecked ? gmod.SelectedMod != null - && gmod.InstalledMod.Module.version < gmod.SelectedMod.version + && (gmod.InstalledMod == null + || gmod.InstalledMod.Module.version < gmod.SelectedMod.version) ? gmod.SelectedMod : gmod.LatestAvailableMod : gmod.InstalledMod?.Module; + + if (nowChecked && gmod.SelectedMod == gmod.LatestAvailableMod) + { + // Reinstall, force update without change + UpdateChangeSetAndConflicts(currentInstance, + RegistryManager.Instance(currentInstance, repoData).registry); + } break; case "AutoInstalled": gmod.SetAutoInstallChecked(row, AutoInstalled); @@ -909,23 +920,38 @@ private void guiModule_PropertyChanged(object sender, PropertyChangedEventArgs e switch (e.PropertyName) { case "SelectedMod": - if (row.Cells[Installed.Index] is DataGridViewCheckBoxCell instCell) - { - instCell.Value = gmod.SelectedMod != null; - } - if (row.Cells[UpdateCol.Index] is DataGridViewCheckBoxCell upgCell) + Util.Invoke(this, () => { - upgCell.Value = gmod.InstalledMod != null - && gmod.SelectedMod != null - && gmod.InstalledMod.Module.version < gmod.SelectedMod.version; - } + if (row.Cells[Installed.Index] is DataGridViewCheckBoxCell instCell) + { + bool newVal = gmod.SelectedMod != null; + if ((bool)instCell.Value != newVal) + { + instCell.Value = newVal; + } + } + if (row.Cells[UpdateCol.Index] is DataGridViewCheckBoxCell upgCell) + { + bool newVal = gmod.SelectedMod != null + && (gmod.InstalledMod == null + || gmod.InstalledMod.Module.version < gmod.SelectedMod.version); + if ((bool)upgCell.Value != newVal) + { + upgCell.Value = newVal; + } + } - // This call is needed to force the UI to update, - // otherwise the checkboxes can look checked when unchecked or vice versa - ModGrid.RefreshEdit(); - // Update the changeset - UpdateChangeSetAndConflicts(currentInstance, - RegistryManager.Instance(currentInstance, repoData).registry); + if (Platform.IsWindows) + { + // This call is needed to force the UI to update on Windows, + // otherwise the checkboxes can look checked when unchecked or vice versa. + // Unfortunately, it crashes on Mono. + ModGrid.RefreshEdit(); + } + // Update the changeset + UpdateChangeSetAndConflicts(currentInstance, + RegistryManager.Instance(currentInstance, repoData).registry); + }); break; } } @@ -954,6 +980,12 @@ public void RemoveChangesetItem(ModChange change) checkCell.Value = false; } break; + case GUIModChangeType.Update: + if (row.Cells[UpdateCol.Index] is DataGridViewCheckBoxCell updateCell) + { + updateCell.Value = false; + } + break; } } UpdateChangeSetAndConflicts( @@ -1018,6 +1050,10 @@ public void ClearChangeSet() { checkCell.Value = false; } + if (row.Cells[UpdateCol.Index] is DataGridViewCheckBoxCell updateCell) + { + updateCell.Value = false; + } } // Marking a mod as AutoInstalled can immediately queue it for removal if there is no dependent mod. // Reset the state of the AutoInstalled checkbox for these by deducing it from the changeset. @@ -1409,7 +1445,7 @@ private bool _UpdateModsList(Dictionary old_modules = null) // After the update / replacement, they are hidden again. Util.Invoke(ModGrid, () => { - UpdateCol.Visible = mainModList.Modules.Any(mod => mod.HasUpdate); + UpdateCol.Visible = has_unheld_updates; ReplaceCol.Visible = mainModList.Modules.Any(mod => mod.IsInstalled && mod.HasReplacement); }); @@ -1821,7 +1857,8 @@ public HashSet ComputeUserChangeSet() => mainModList.ComputeUserChangeSet( RegistryManager.Instance(currentInstance, repoData).registry, currentInstance.VersionCriteria(), - ReplaceCol); + currentInstance, + UpdateCol, ReplaceCol); [ForbidGUICalls] public void UpdateChangeSetAndConflicts(GameInstance inst, IRegistryQuerier registry) @@ -1836,7 +1873,7 @@ public void UpdateChangeSetAndConflicts(GameInstance inst, IRegistryQuerier regi Dictionary new_conflicts = null; var gameVersion = inst.VersionCriteria(); - var user_change_set = mainModList.ComputeUserChangeSet(registry, gameVersion, ReplaceCol); + var user_change_set = mainModList.ComputeUserChangeSet(registry, gameVersion, inst, UpdateCol, ReplaceCol); try { // Set the target versions of upgrading mods based on what's actually allowed diff --git a/GUI/Main/Main.Designer.cs b/GUI/Main/Main.Designer.cs index 751af526f..eff31e8d8 100644 --- a/GUI/Main/Main.Designer.cs +++ b/GUI/Main/Main.Designer.cs @@ -85,7 +85,7 @@ private void InitializeComponent() this.InstallationHistory = new CKAN.GUI.InstallationHistory(); this.ChooseProvidedModsTabPage = new System.Windows.Forms.TabPage(); this.ChooseProvidedMods = new CKAN.GUI.ChooseProvidedMods(); - this.minimizeNotifyIcon = new System.Windows.Forms.NotifyIcon(this.components); + this.minimizeNotifyIcon = new System.Windows.Forms.NotifyIcon(); this.minimizedContextMenuStrip = new System.Windows.Forms.ContextMenuStrip(this.components); this.updatesToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.toolStripSeparator4 = new System.Windows.Forms.ToolStripSeparator(); diff --git a/GUI/Main/Main.cs b/GUI/Main/Main.cs index 3c0fc0de7..8806ed22a 100644 --- a/GUI/Main/Main.cs +++ b/GUI/Main/Main.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Diagnostics; using System.Drawing; using System.Globalization; using System.IO; @@ -548,6 +547,10 @@ protected override void OnFormClosing(FormClosingEventArgs e) } } + // Remove the tray icon + minimizeNotifyIcon.Visible = false; + minimizeNotifyIcon.Dispose(); + base.OnFormClosing(e); } diff --git a/GUI/Model/GUIMod.cs b/GUI/Model/GUIMod.cs index 80fff23cc..bdb3aa29b 100644 --- a/GUI/Model/GUIMod.cs +++ b/GUI/Model/GUIMod.cs @@ -304,16 +304,16 @@ public CkanModule ToCkanModule() /// The CkanModule associated with this GUIMod or null if there is none public CkanModule ToModule() => Mod; - public IEnumerable GetModChanges(bool replaceChecked) + public IEnumerable GetModChanges(bool upgradeChecked, bool replaceChecked) { if (replaceChecked) { yield return new ModChange(Mod, GUIModChangeType.Replace); } else if (!(SelectedMod?.Equals(InstalledMod?.Module) - ?? InstalledMod?.Module.Equals(SelectedMod) - // Both null - ?? true)) + ?? InstalledMod?.Module?.Equals(SelectedMod) + // Both null + ?? true)) { if (InstalledMod != null && SelectedMod == LatestAvailableMod) { @@ -334,6 +334,14 @@ public IEnumerable GetModChanges(bool replaceChecked) } } } + else if (upgradeChecked) + { + // Reinstall + yield return new ModUpgrade(Mod, + GUIModChangeType.Update, + SelectedMod, + false); + } } public void SetAutoInstallChecked(DataGridViewRow row, DataGridViewColumn col, bool? set_value_to = null) diff --git a/GUI/Model/ModList.cs b/GUI/Model/ModList.cs index e404a69c0..511856245 100644 --- a/GUI/Model/ModList.cs +++ b/GUI/Model/ModList.cs @@ -422,19 +422,26 @@ public string StripEpoch(string version) private static readonly Regex ContainsEpoch = new Regex(@"^[0-9][0-9]*:[^:]+$", RegexOptions.Compiled); private static readonly Regex RemoveEpoch = new Regex(@"^([^:]+):([^:]+)$", RegexOptions.Compiled); - private IEnumerable rowChanges(DataGridViewRow row, DataGridViewColumn replaceCol) + private IEnumerable rowChanges(DataGridViewRow row, + DataGridViewColumn upgradeCol, + DataGridViewColumn replaceCol) => (row.Tag as GUIMod).GetModChanges( + upgradeCol != null && upgradeCol.Visible + && row.Cells[upgradeCol.Index] is DataGridViewCheckBoxCell upgradeCell + && (bool)upgradeCell.Value, replaceCol != null && replaceCol.Visible && row.Cells[replaceCol.Index] is DataGridViewCheckBoxCell replaceCell && (bool)replaceCell.Value); public HashSet ComputeUserChangeSet(IRegistryQuerier registry, GameVersionCriteria crit, + GameInstance instance, + DataGridViewColumn upgradeCol, DataGridViewColumn replaceCol) { log.Debug("Computing user changeset"); var modChanges = full_list_of_mod_rows?.Values - .SelectMany(row => rowChanges(row, replaceCol)) + .SelectMany(row => rowChanges(row, upgradeCol, replaceCol)) .ToList() ?? new List(); @@ -443,10 +450,12 @@ public HashSet ComputeUserChangeSet(IRegistryQuerier registry, if (registry != null) { var upgrades = modChanges.OfType() + // Skip reinstalls + .Where(upg => upg.Mod != upg.targetMod) .ToArray(); if (upgrades.Length > 0) { - var upgradeable = registry.CheckUpgradeable(crit, + var upgradeable = registry.CheckUpgradeable(instance, // Hold identifiers not chosen for upgrading registry.Installed(false) .Select(kvp => kvp.Key) @@ -463,8 +472,12 @@ public HashSet ComputeUserChangeSet(IRegistryQuerier registry, ? allowedMod // Not upgradeable! : change.Mod; + if (change.Mod == change.targetMod) + { + // This upgrade was voided by dependencies or conflicts + modChanges.Remove(change); + } } - modChanges.RemoveAll(ch => ch is ModUpgrade upg && upg.Mod == upg.targetMod); } } @@ -491,33 +504,50 @@ public bool ResetHasUpdate(GameInstance inst, List ChangeSet, DataGridViewRowCollection rows) { - var upgGroups = registry.CheckUpgradeable(inst.VersionCriteria(), + var upgGroups = registry.CheckUpgradeable(inst, ModuleLabels.HeldIdentifiers(inst) .ToHashSet()); + var dlls = registry.InstalledDlls.ToList(); foreach ((var upgradeable, var mods) in upgGroups) { foreach (var ident in mods.Select(m => m.identifier)) { - var row = full_list_of_mod_rows[ident]; - if (row.Tag is GUIMod gmod && gmod.HasUpdate != upgradeable) - { - gmod.HasUpdate = upgradeable; - if (row.Visible) - { - // Swap whether the row has an upgrade checkbox - var newRow = - full_list_of_mod_rows[ident] = - MakeRow(gmod, ChangeSet, inst.Name, inst.game); - var rowIndex = row.Index; - rows.Remove(row); - rows.Insert(rowIndex, newRow); - } - } + dlls.Remove(ident); + CheckRowUpgradeable(inst, ChangeSet, rows, ident, upgradeable); } } + // AD mods don't have CkanModules in the return value of CheckUpgradeable + foreach (var ident in dlls) + { + CheckRowUpgradeable(inst, ChangeSet, rows, ident, false); + } return upgGroups[true].Count > 0; } + private void CheckRowUpgradeable(GameInstance inst, + List ChangeSet, + DataGridViewRowCollection rows, + string ident, + bool upgradeable) + { + if (full_list_of_mod_rows.TryGetValue(ident, out DataGridViewRow row) + && row.Tag is GUIMod gmod + && gmod.HasUpdate != upgradeable) + { + gmod.HasUpdate = upgradeable; + if (row.Visible) + { + // Swap whether the row has an upgrade checkbox + var newRow = + full_list_of_mod_rows[ident] = + MakeRow(gmod, ChangeSet, inst.Name, inst.game); + var rowIndex = row.Index; + rows.Remove(row); + rows.Insert(rowIndex, newRow); + } + } + } + /// /// Get all the GUI mods for the given instance. /// @@ -542,18 +572,24 @@ private IEnumerable GetGUIMods(IRegistryQuerier registry, HashSet installedIdents, bool hideEpochs, bool hideV) - => registry.CheckUpgradeable(versionCriteria, + => registry.CheckUpgradeable(inst, ModuleLabels.HeldIdentifiers(inst) .ToHashSet()) .SelectMany(kvp => kvp.Value - .Where(mod => !registry.IsAutodetected(mod.identifier)) - .Select(mod => new GUIMod(registry.InstalledModule(mod.identifier), - repoData, registry, - versionCriteria, null, - hideEpochs, hideV) - { - HasUpdate = kvp.Key, - })) + .Select(mod => registry.IsAutodetected(mod.identifier) + ? new GUIMod(mod, repoData, registry, + versionCriteria, null, + hideEpochs, hideV) + { + HasUpdate = kvp.Key, + } + : new GUIMod(registry.InstalledModule(mod.identifier), + repoData, registry, + versionCriteria, null, + hideEpochs, hideV) + { + HasUpdate = kvp.Key, + })) .Concat(registry.CompatibleModules(versionCriteria) .Where(m => !installedIdents.Contains(m.identifier)) .AsParallel() diff --git a/Tests/Core/Registry/Registry.cs b/Tests/Core/Registry/Registry.cs index 3c7e9043a..939d57d98 100644 --- a/Tests/Core/Registry/Registry.cs +++ b/Tests/Core/Registry/Registry.cs @@ -256,6 +256,7 @@ public void HasUpdate_WithUpgradeableManuallyInstalledMod_ReturnsTrue() var mod = registry.GetModuleByVersion("AutoDetectedMod", "1.0"); GameInstance gameInst = gameInstWrapper.KSP; + gameInst.SetCompatibleVersions(new List { mod.ksp_version }); registry.SetDlls(new Dictionary() { { @@ -264,10 +265,9 @@ public void HasUpdate_WithUpgradeableManuallyInstalledMod_ReturnsTrue() "GameData", $"{mod.identifier}.dll")) } }); - GameVersionCriteria crit = new GameVersionCriteria(mod.ksp_version); // Act - bool has = registry.HasUpdate(mod.identifier, crit, out _); + bool has = registry.HasUpdate(mod.identifier, gameInst, out _); // Assert Assert.IsTrue(has, "Can't upgrade manually installed DLL"); @@ -320,7 +320,7 @@ public void HasUpdate_OtherModDependsOnCurrent_ReturnsFalse() GameVersionCriteria crit = new GameVersionCriteria(olderDepMod.ksp_version); // Act - bool has = registry.HasUpdate(olderDepMod.identifier, crit, out _, + bool has = registry.HasUpdate(olderDepMod.identifier, gameInst, out _, registry.InstalledModules .Select(im => im.Module) .ToList()); diff --git a/Tests/GUI/Model/GUIMod.cs b/Tests/GUI/Model/GUIMod.cs index 448c57747..7544fb597 100644 --- a/Tests/GUI/Model/GUIMod.cs +++ b/Tests/GUI/Model/GUIMod.cs @@ -64,7 +64,7 @@ public void HasUpdate_UpdateAvailable_ReturnsTrue() var registry = new Registry(repoData.Manager, repo.repo); registry.RegisterModule(old_version, new List(), null, false); - var upgradeableGroups = registry.CheckUpgradeable(tidy.KSP.VersionCriteria(), + var upgradeableGroups = registry.CheckUpgradeable(tidy.KSP, new HashSet()); var mod = new GUIMod(old_version, repoData.Manager, registry, tidy.KSP.VersionCriteria(), diff --git a/Tests/GUI/Model/ModList.cs b/Tests/GUI/Model/ModList.cs index 2d727b876..ade5510b9 100644 --- a/Tests/GUI/Model/ModList.cs +++ b/Tests/GUI/Model/ModList.cs @@ -27,7 +27,7 @@ public class ModListTests public void ComputeFullChangeSetFromUserChangeSet_WithEmptyList_HasEmptyChangeSet() { var item = new ModList(); - Assert.That(item.ComputeUserChangeSet(null, null, null), Is.Empty); + Assert.That(item.ComputeUserChangeSet(null, null, null, null, null), Is.Empty); } [Test] @@ -210,7 +210,7 @@ public void InstallAndSortByCompat_WithAnyCompat_NoCrash() { // Install the "other" module installer.InstallList( - modList.ComputeUserChangeSet(null, null, null).Select(change => change.Mod).ToList(), + modList.ComputeUserChangeSet(null, null, null, null, null).Select(change => change.Mod).ToList(), new RelationshipResolverOptions(), registryManager, ref possibleConfigOnlyDirs, diff --git a/build.cake b/build.cake index c1ffd67ce..2eba791d1 100644 --- a/build.cake +++ b/build.cake @@ -227,6 +227,7 @@ Task("Build") // Use Mono to build for net48 since dotnet can't use WinForms on Linux MSBuild(solution, settings => settings.SetConfiguration(configuration) + .SetMaxCpuCount(0) .WithProperty("TargetFramework", buildNetFramework)); // Use dotnet to build the stuff Mono can't build