Skip to content

Commit

Permalink
Merge #4213 Improvements for missing file checks
Browse files Browse the repository at this point in the history
  • Loading branch information
HebaruSan committed Sep 30, 2024
2 parents aa22570 + b883868 commit b5736f4
Show file tree
Hide file tree
Showing 19 changed files with 254 additions and 96 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ All notable changes to this project will be documented in this file.

## v1.35.1

### Features

- [Multiple] Improvements for missing file checks (#4213 by: HebaruSan)

### Bugfixes

- [Core] Skip corrupted .acf files in Steam library (#4200 by: HebaruSan)
Expand Down
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

0 comments on commit b5736f4

Please sign in to comment.