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

Improvements for missing file checks #4213

Merged
merged 9 commits into from
Sep 30, 2024
3 changes: 2 additions & 1 deletion Core/Extensions/EnumerableExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -170,8 +170,9 @@ public static IEnumerable<T> DistinctBy<T, U>(this IEnumerable<T> seq, Func<T, U
/// <param name="getNext">Function to go from one node to the next</param>
/// <returns>All the nodes in the list as a sequence</returns>
public static IEnumerable<T> TraverseNodes<T>(this T start, Func<T, T?> getNext)
where T : class
{
for (T? t = start; t != null; t = getNext(t))
for (T? t = start; t != null; t = Utilities.DefaultIfThrows(() => getNext(t)))
{
yield return t;
}
Expand Down
57 changes: 48 additions & 9 deletions Core/ModuleInstaller.cs
Original file line number Diff line number Diff line change
Expand Up @@ -582,23 +582,62 @@ public static List<InstallableFile> FindInstallableFiles(CkanModule module, stri
}
}

/// <summary>
/// Returns contents of an installed module
/// </summary>
public static IEnumerable<(string path, bool dir, bool exists)> GetModuleContents(
GameInstance instance,
IReadOnlyCollection<string> installed,
HashSet<string> filters)
=> GetModuleContents(instance, installed,
installed.SelectMany(f => f.TraverseNodes(Path.GetDirectoryName)
.Skip(1)
.Where(s => s.Length > 0)
.Select(CKANPathUtils.NormalizePath))
.ToHashSet(),
filters);

private static IEnumerable<(string path, bool dir, bool exists)> GetModuleContents(
GameInstance instance,
IReadOnlyCollection<string> installed,
HashSet<string> parents,
HashSet<string> filters)
=> installed.Where(f => !filters.Any(filt => f.Contains(filt)))
.GroupBy(parents.Contains)
.SelectMany(grp =>
grp.Select(p => (path: p,
dir: grp.Key,
exists: grp.Key ? Directory.Exists(instance.ToAbsoluteGameDir(p))
: File.Exists(instance.ToAbsoluteGameDir(p)))));

/// <summary>
/// Returns the module contents if and only if we have it
/// available in our cache, empty sequence otherwise.
///
/// Intended for previews.
/// </summary>
public static IEnumerable<InstallableFile> GetModuleContents(NetModuleCache Cache,
GameInstance instance,
CkanModule module,
HashSet<string> filters)
public static IEnumerable<(string path, bool dir, bool exists)> GetModuleContents(
NetModuleCache Cache,
GameInstance instance,
CkanModule module,
HashSet<string> filters)
=> (Cache.GetCachedFilename(module) is string filename
? Utilities.DefaultIfThrows(() => FindInstallableFiles(module, filename, instance)
.Where(instF => !filters.Any(filt =>
instF.destination != null
&& instF.destination.Contains(filt))))
? GetModuleContents(instance,
Utilities.DefaultIfThrows(
() => FindInstallableFiles(module, filename, instance)),
filters)
: null)
?? Enumerable.Empty<InstallableFile>();
?? Enumerable.Empty<(string path, bool dir, bool exists)>();

private static IEnumerable<(string path, bool dir, bool exists)>? GetModuleContents(
GameInstance instance,
IEnumerable<InstallableFile>? installable,
HashSet<string> filters)
=> installable?.Where(instF => !filters.Any(filt => instF.destination != null
&& instF.destination.Contains(filt)))
.Select(f => (path: instance.ToRelativeGameDir(f.destination),
dir: f.source.IsDirectory,
exists: true));

#endregion

Expand Down
37 changes: 27 additions & 10 deletions Core/Registry/IRegistryQuerier.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
using System;
using System.IO;
using System.Linq;
using System.Collections.Generic;
using System.Collections.ObjectModel;

using Autofac;
using log4net;

using CKAN.Configuration;
using CKAN.Versioning;
using CKAN.Games;
#if NETSTANDARD2_0
Expand Down Expand Up @@ -174,6 +175,8 @@ public static bool IsAutodetected(this IRegistryQuerier querier, string identifi
public static bool HasUpdate(this IRegistryQuerier querier,
string identifier,
GameInstance? instance,
HashSet<string> filters,
bool checkMissingFiles,
out CkanModule? latestMod,
ICollection<CkanModule>? installed = null)
{
Expand Down Expand Up @@ -202,11 +205,10 @@ public static bool HasUpdate(this IRegistryQuerier querier,
if (comp == -1
|| (comp == 0 && !querier.MetadataChanged(identifier)
// Check if any of the files or directories are missing
&& (instance == null
&& (!checkMissingFiles
|| instance == null
|| (querier.InstalledModule(identifier)
?.Files
.Select(instance.ToAbsoluteGameDir)
.All(p => Directory.Exists(p) || File.Exists(p))
?.AllFilesExist(instance, filters)
// Manually installed, consider up to date
?? true))))
{
Expand All @@ -222,36 +224,51 @@ public static bool HasUpdate(this IRegistryQuerier querier,

public static Dictionary<bool, List<CkanModule>> CheckUpgradeable(this IRegistryQuerier querier,
GameInstance? instance,
HashSet<string> heldIdents)
HashSet<string> heldIdents,
HashSet<string>? ignoreMissingIdents = null)
{
var filters = ServiceLocator.Container.Resolve<IConfiguration>()
.GlobalInstallFilters
.Concat(instance?.InstallFilters
?? Enumerable.Empty<string>())
.ToHashSet();
// 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, instance,
&& querier.HasUpdate(ident, instance, filters,
!ignoreMissingIdents?.Contains(ident) ?? true,
out CkanModule? latest)
&& latest is not null
&& !latest.IsDLC
? latest
: querier.GetInstalledVersion(ident))
.OfType<CkanModule>()
.ToList();
return querier.CheckUpgradeable(instance, heldIdents, unlimited);
return querier.CheckUpgradeable(instance, heldIdents, unlimited, filters, ignoreMissingIdents);
}

public static Dictionary<bool, List<CkanModule>> CheckUpgradeable(this IRegistryQuerier querier,
GameInstance? instance,
HashSet<string> heldIdents,
List<CkanModule> initial)
List<CkanModule> initial,
HashSet<string>? filters = null,
HashSet<string>? ignoreMissingIdents = null)
{
filters ??= ServiceLocator.Container.Resolve<IConfiguration>()
.GlobalInstallFilters
.Concat(instance?.InstallFilters
?? Enumerable.Empty<string>())
.ToHashSet();
// Use those as the installed modules
var upgradeable = new List<CkanModule>();
var notUpgradeable = new List<CkanModule>();
foreach (var ident in initial.Select(module => module.identifier))
{
if (!heldIdents.Contains(ident)
&& querier.HasUpdate(ident, instance,
&& querier.HasUpdate(ident, instance, filters,
!ignoreMissingIdents?.Contains(ident) ?? true,
out CkanModule? latest, initial)
&& latest is not null
&& !latest.IsDLC)
Expand Down
16 changes: 12 additions & 4 deletions Core/Registry/InstalledModule.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Linq;
using System.ComponentModel;
using System.Collections.Generic;
using System.IO;
Expand Down Expand Up @@ -45,10 +46,10 @@ public class InstalledModule
[JsonProperty]
private Dictionary<string, InstalledModuleFile> installed_files;

public IEnumerable<string> Files => installed_files.Keys;
public string identifier => source_module.identifier;
public CkanModule Module => source_module;
public DateTime InstallTime => install_time;
public IReadOnlyCollection<string> Files => installed_files.Keys;
public string identifier => source_module.identifier;
public CkanModule Module => source_module;
public DateTime InstallTime => install_time;

public bool AutoInstalled
{
Expand Down Expand Up @@ -132,6 +133,13 @@ public void Renormalise(GameInstance ksp)

#endregion

public bool AllFilesExist(GameInstance instance,
HashSet<string> filters)
// Don't make them reinstall files they've filtered out since installing
=> Files.Where(f => !filters.Any(filt => f.Contains(filt)))
.Select(instance.ToAbsoluteGameDir)
.All(p => Directory.Exists(p) || File.Exists(p));

public override string ToString()
=> string.Format(AutoInstalled ? Properties.Resources.InstalledModuleToStringAutoInstalled
: Properties.Resources.InstalledModuleToString,
Expand Down
40 changes: 21 additions & 19 deletions GUI/Controls/ManageMods.cs
Original file line number Diff line number Diff line change
Expand Up @@ -335,24 +335,27 @@ private void LabelsContextMenuStrip_Opening(object? sender, CancelEventArgs? e)

private void labelMenuItem_Click(object? sender, EventArgs? e)
{
if (user != null && manager != null && currentInstance != null && SelectedModule != null)
if (user != null
&& manager != null
&& currentInstance != null
&& SelectedModule != null
&& sender is ToolStripMenuItem item
&& item.Tag is ModuleLabel mlbl)
{
var item = sender as ToolStripMenuItem;
var mlbl = item?.Tag as ModuleLabel;
if (item?.Checked ?? false)
if (item.Checked)
{
mlbl?.Add(currentInstance.game, SelectedModule.Identifier);
mlbl.Add(currentInstance.game, SelectedModule.Identifier);
}
else
{
mlbl?.Remove(currentInstance.game, SelectedModule.Identifier);
mlbl.Remove(currentInstance.game, SelectedModule.Identifier);
}
var registry = RegistryManager.Instance(currentInstance, repoData).registry;
mainModList.ReapplyLabels(SelectedModule, Conflicts?.ContainsKey(SelectedModule) ?? false,
currentInstance.Name, currentInstance.game, registry);
ModuleLabelList.ModuleLabels.Save(ModuleLabelList.DefaultPath);
UpdateHiddenTagsAndLabels();
if (mlbl?.HoldVersion ?? false)
if (mlbl.HoldVersion || mlbl.IgnoreMissingFiles)
{
UpdateCol.Visible = UpdateAllToolButton.Enabled =
mainModList.ResetHasUpdate(currentInstance, registry, ChangeSet, ModGrid.Rows);
Expand Down Expand Up @@ -538,19 +541,18 @@ public void MarkAllUpdates()
{
WithFrozenChangeset(() =>
{
foreach (var gmod in mainModList.full_list_of_mod_rows
.Values
.Select(row => row.Tag)
.OfType<GUIMod>())
var checkboxes = mainModList.full_list_of_mod_rows
.Values
.Where(row => row.Tag is GUIMod {Identifier: string ident}
&& (!Main.Instance?.LabelsHeld(ident) ?? false))
.SelectWithCatch(row => row.Cells[UpdateCol.Index],
(row, exc) => null)
.OfType<DataGridViewCheckBoxCell>();
foreach (var checkbox in checkboxes)
{
if (gmod?.HasUpdate ?? false)
{
if (!Main.Instance?.LabelsHeld(gmod.Identifier) ?? false)
{
gmod.SelectedMod = gmod.LatestCompatibleMod;
}
}
checkbox.Value = true;
}
ModGrid.CommitEdit(DataGridViewDataErrorContexts.Commit);

// only sort by Update column if checkbox in settings checked
if (guiConfig?.AutoSortByUpdate ?? false)
Expand Down Expand Up @@ -935,7 +937,7 @@ private void ModGrid_CellValueChanged(object? sender, DataGridViewCellEventArgs?
: gmod.LatestCompatibleMod
: gmod.InstalledMod?.Module;

if (nowChecked && gmod.SelectedMod == gmod.LatestCompatibleMod)
if (gmod.SelectedMod == gmod.LatestCompatibleMod)
{
// Reinstall, force update without change
UpdateChangeSetAndConflicts(currentInstance,
Expand Down
14 changes: 14 additions & 0 deletions GUI/Controls/ModInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

using Autofac;

using CKAN.Configuration;
using CKAN.Versioning;
using CKAN.GUI.Attributes;

Expand Down Expand Up @@ -138,6 +139,19 @@ private void UpdateHeaderInfo(GUIMod gmod, GameVersionCriteria crit)
var pageToAlert = module.IsCompatible(crit) ? RelationshipTabPage : VersionsTabPage;
pageToAlert.ImageKey = "Stop";
}
if (manager?.CurrentInstance is GameInstance inst)
{
var filters = ServiceLocator.Container.Resolve<IConfiguration>()
.GlobalInstallFilters
.Concat(inst.InstallFilters)
.ToHashSet();
ContentTabPage.ImageKey = ModuleLabels.IgnoreMissingIdentifiers(inst)
.Contains(gmod.Identifier)
|| (gmod.InstalledMod?.AllFilesExist(inst, filters)
?? true)
? ""
: "Stop";
}

ModInfoTabControl.ResumeLayout();
});
Expand Down
Loading