Skip to content

Commit

Permalink
Fix skipping failed dependency downloads
Browse files Browse the repository at this point in the history
  • Loading branch information
HebaruSan committed Oct 18, 2024
1 parent 631cc43 commit 01b93f7
Show file tree
Hide file tree
Showing 5 changed files with 94 additions and 91 deletions.
2 changes: 1 addition & 1 deletion Core/Net/NetFileCache.cs
Original file line number Diff line number Diff line change
Expand Up @@ -339,7 +339,7 @@ public void EnforceSizeLimit(long bytes, Registry registry)
}
}

private static int compareFiles(Dictionary<string, List<CkanModule>> hashMap, FileInfo a, FileInfo b)
private static int compareFiles(IReadOnlyDictionary<string, List<CkanModule>> hashMap, FileInfo a, FileInfo b)
{
// Compatible modules for file A
hashMap.TryGetValue(a.Name[..8], out List<CkanModule>? modulesA);
Expand Down
57 changes: 30 additions & 27 deletions Core/Registry/Registry.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1099,50 +1099,55 @@ public void CheckSanity()
/// <param name="dlc">Installed DLCs</param>
/// <returns>List of modules whose dependencies are about to be or already removed.</returns>
public static IEnumerable<string> FindReverseDependencies(
List<string> modulesToRemove,
List<CkanModule>? modulesToInstall,
HashSet<CkanModule> origInstalled,
ICollection<string> modulesToRemove,
ICollection<CkanModule>? modulesToInstall,
ICollection<CkanModule> origInstalled,
ICollection<string> dlls,
IDictionary<string, ModuleVersion>? dlc,
IDictionary<string, ModuleVersion> dlc,
Func<RelationshipDescriptor, bool>? satisfiedFilter = null)
{
log.DebugFormat("Finding reverse dependencies of: {0}", string.Join(", ", modulesToRemove));
log.DebugFormat("From installed mods: {0}", string.Join(", ", origInstalled));
log.DebugFormat("Installing mods: {0}", string.Join(", ", modulesToInstall ?? new List<CkanModule>()));
if (modulesToInstall != null)
{
log.DebugFormat("Installing mods: {0}", string.Join(", ", modulesToInstall));
}

// The empty list has no reverse dependencies
// (Don't remove broken modules if we're only installing)
if (modulesToRemove.Count != 0)
{
// All modules in the input are included in the output
foreach (string starter in modulesToRemove)
foreach (var starter in modulesToRemove)
{
yield return starter;
}
while (true)
{
// Make our hypothetical install, and remove the listed modules from it.
// Clone because we alter hypothetical.
var hypothetical = new HashSet<CkanModule>(origInstalled);
var hypothetical = origInstalled.ToHashSet();
if (modulesToInstall != null)
{
// Pretend the mods we are going to install are already installed, so that dependencies that will be
// satisfied by a mod that is going to be installed count as satisfied.
hypothetical = hypothetical.Concat(modulesToInstall).ToHashSet();
hypothetical.UnionWith(modulesToInstall);
}
hypothetical.RemoveWhere(mod => modulesToRemove.Contains(mod.identifier));

log.DebugFormat("Removing: {0}", string.Join(", ", modulesToRemove));
log.DebugFormat("Keeping: {0}", string.Join(", ", hypothetical));

// Find what would break with this configuration
var brokenDeps = SanityChecker.FindUnsatisfiedDepends(hypothetical, dlls, dlc)
var brokenDeps = SanityChecker.FindUnsatisfiedDepends(hypothetical,
dlls, dlc)
.ToList();
if (satisfiedFilter != null)
{
brokenDeps.RemoveAll(kvp => satisfiedFilter(kvp.Item2));
brokenDeps.RemoveAll(tuple => satisfiedFilter(tuple.Item2));
}
var brokenIdents = brokenDeps.Select(x => x.Item1.identifier).ToHashSet();
var brokenIdents = brokenDeps.Select(tuple => tuple.Item1.identifier)
.ToHashSet();

if (modulesToInstall != null)
{
Expand All @@ -1153,24 +1158,23 @@ public static IEnumerable<string> FindReverseDependencies(
}
log.DebugFormat("Broken: {0}", string.Join(", ", brokenIdents));
// Lazily return each newly found rev dep
foreach (string newFound in brokenIdents.Except(modulesToRemove))
foreach (var newFound in brokenIdents.Except(modulesToRemove))
{
yield return newFound;
}

// If nothing else would break, it's just the list of modules we're removing
var to_remove = new HashSet<string>(modulesToRemove);

if (to_remove.IsSupersetOf(brokenIdents))
var toRemove = modulesToRemove.ToHashSet();
if (toRemove.IsSupersetOf(brokenIdents))
{
log.DebugFormat("{0} is a superset of {1}, work done",
string.Join(", ", to_remove),
string.Join(", ", toRemove),
string.Join(", ", brokenIdents));
break;
}

// Otherwise, remove our broken modules as well, and recurse
modulesToRemove = brokenIdents.Union(to_remove).ToList();
modulesToRemove = brokenIdents.Union(toRemove).ToList();
}
}
}
Expand All @@ -1179,14 +1183,13 @@ public static IEnumerable<string> FindReverseDependencies(
/// Return modules which are dependent on the modules passed in or modules in the return list
/// </summary>
public IEnumerable<string> FindReverseDependencies(
List<string> modulesToRemove,
List<CkanModule>? modulesToInstall = null,
Func<RelationshipDescriptor, bool>? satisfiedFilter = null)
=> FindReverseDependencies(modulesToRemove,
modulesToInstall,
new HashSet<CkanModule>(installed_modules.Values.Select(x => x.Module)),
installed_dlls.Keys,
InstalledDlc,
List<string> modulesToRemove,
List<CkanModule>? modulesToInstall = null,
Func<RelationshipDescriptor, bool>? satisfiedFilter = null)
=> FindReverseDependencies(modulesToRemove, modulesToInstall,
installed_modules.Values.Select(im => im.Module)
.ToHashSet(),
InstalledDlls, InstalledDlc,
satisfiedFilter);

/// <summary>
Expand All @@ -1196,7 +1199,7 @@ public IEnumerable<string> FindReverseDependencies(
/// <returns>
/// dictionary[sha256 or sha1] = {mod1, mod2, mod3};
/// </returns>
public Dictionary<string, List<CkanModule>> GetDownloadHashesIndex()
public IReadOnlyDictionary<string, List<CkanModule>> GetDownloadHashesIndex()
=> downloadHashesIndex ??=
(repoDataMgr?.GetAllAvailableModules(Repositories.Values)
.SelectMany(availMod => availMod.module_version.Values)
Expand Down Expand Up @@ -1229,7 +1232,7 @@ private IEnumerable<Tuple<string, CkanModule>> ModWithDownloadHashes(CkanModule
/// <returns>
/// dictionary[urlHash] = {mod1, mod2, mod3};
/// </returns>
public Dictionary<string, List<CkanModule>> GetDownloadUrlHashIndex()
public IReadOnlyDictionary<string, List<CkanModule>> GetDownloadUrlHashIndex()
=> downloadUrlHashIndex ??=
(repoDataMgr?.GetAllAvailableModules(Repositories.Values)
?? Enumerable.Empty<AvailableModule>())
Expand Down
46 changes: 21 additions & 25 deletions Core/Relationships/SanityChecker.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@ public static class SanityChecker
/// Throws a BadRelationshipsKraken describing the problems otherwise.
/// Does nothing if the modules can happily co-exist.
/// </summary>
public static void EnforceConsistency(IEnumerable<CkanModule> modules,
ICollection<string> dlls,
IDictionary<string, ModuleVersion>? dlc = null)
public static void EnforceConsistency(IEnumerable<CkanModule> modules,
ICollection<string> dlls,
IDictionary<string, ModuleVersion> dlc)
{
if (!CheckConsistency(modules, dlls, dlc,
out List<Tuple<CkanModule, RelationshipDescriptor>> unmetDepends,
Expand All @@ -35,15 +35,15 @@ public static void EnforceConsistency(IEnumerable<CkanModule> module
/// Returns true if the mods supplied can co-exist. This checks depends/pre-depends/conflicts only.
/// This is only used by tests!
/// </summary>
public static bool IsConsistent(IEnumerable<CkanModule> modules,
ICollection<string>? dlls = null,
IDictionary<string, ModuleVersion>? dlc = null)
public static bool IsConsistent(IEnumerable<CkanModule> modules,
ICollection<string> dlls,
IDictionary<string, ModuleVersion> dlc)
=> CheckConsistency(modules, dlls, dlc,
out var _, out var _);

private static bool CheckConsistency(IEnumerable<CkanModule> modules,
ICollection<string>? dlls,
IDictionary<string, ModuleVersion>? dlc,
ICollection<string> dlls,
IDictionary<string, ModuleVersion> dlc,
out List<Tuple<CkanModule, RelationshipDescriptor>> UnmetDepends,
out modRelList Conflicts)
{
Expand All @@ -64,16 +64,12 @@ private static bool CheckConsistency(IEnumerable<CkanModule>
/// Each Key is the depending module, and each Value is the relationship.
/// </returns>
public static IEnumerable<Tuple<CkanModule, RelationshipDescriptor>> FindUnsatisfiedDepends(
ICollection<CkanModule> modules,
ICollection<string>? dlls,
IDictionary<string, ModuleVersion>? dlc)
=> (modules?.Where(m => m.depends != null)
.SelectMany(m => (m.depends ?? Enumerable.Empty<RelationshipDescriptor>())
.Select(dep =>
new Tuple<CkanModule,
RelationshipDescriptor>(m, dep)))
.Where(kvp => !kvp.Item2.MatchesAny(modules, dlls, dlc))
?? Enumerable.Empty<Tuple<CkanModule, RelationshipDescriptor>>());
ICollection<CkanModule> modules,
ICollection<string>? dlls,
IDictionary<string, ModuleVersion> dlc)
=> modules.SelectMany(m => (m.depends ?? Enumerable.Empty<RelationshipDescriptor>())
.Where(dep => !dep.MatchesAny(modules, dlls, dlc))
.Select(dep => Tuple.Create(m, dep)));

/// <summary>
/// Find conflicts among the given modules and DLLs.
Expand All @@ -85,9 +81,9 @@ public static IEnumerable<Tuple<CkanModule, RelationshipDescriptor>> FindUnsatis
/// List of conflicts represented as pairs.
/// Each Key is the depending module, and each Value is the relationship.
/// </returns>
private static modRelList FindConflicting(List<CkanModule> modules,
ICollection<string>? dlls,
IDictionary<string, ModuleVersion>? dlc)
private static modRelList FindConflicting(List<CkanModule> modules,
ICollection<string> dlls,
IDictionary<string, ModuleVersion> dlc)
=> modules.Where(m => m.conflicts != null)
.SelectMany(m => FindConflictingWith(
m,
Expand All @@ -96,10 +92,10 @@ private static modRelList FindConflicting(List<CkanModule> mo
dlls, dlc))
.ToList();

private static IEnumerable<modRelPair> FindConflictingWith(CkanModule module,
List<CkanModule> otherMods,
ICollection<string>? dlls,
IDictionary<string, ModuleVersion>? dlc)
private static IEnumerable<modRelPair> FindConflictingWith(CkanModule module,
List<CkanModule> otherMods,
ICollection<string> dlls,
IDictionary<string, ModuleVersion> dlc)
=> module.conflicts?.Select(rel => rel.MatchesAny(otherMods, dlls, dlc, out CkanModule? other)
? new modRelPair(module, rel, other)
: null)
Expand Down
22 changes: 11 additions & 11 deletions GUI/Main/MainInstall.cs
Original file line number Diff line number Diff line change
Expand Up @@ -260,27 +260,27 @@ private void InstallMods(object? sender, DoWorkEventArgs? e)
(m1, m2) => (m1 as CkanModule)?.download == (m2 as CkanModule)?.download);
dfd.ShowDialog(this);
});
var skip = dfd?.Wait()?.Select(m => m as CkanModule)
.OfType<CkanModule>()
.ToArray();
var abort = dfd?.Abort;
var skip = (dfd?.Wait()?.OfType<CkanModule>() ?? Enumerable.Empty<CkanModule>())
.ToArray();
var abort = dfd?.Abort ?? false;
dfd?.Dispose();
if (abort ?? false)
if (abort)
{
canceled = true;
e.Result = new InstallResult(false, changes);
throw new CancelledActionKraken();
}

if (skip != null && skip.Length > 0)
if (skip.Length > 0)
{
// Remove mods from changeset that user chose to skip
// and any mods depending on them
var dependers = registry.FindReverseDependencies(
skip.Select(s => s.identifier).ToList(),
fullChangeset,
// Consider virtual dependencies satisfied so user can make a new choice if they skip
rel => rel.LatestAvailableWithProvides(registry, crit).Count > 1)
var dependers = Registry.FindReverseDependencies(
skip.Select(s => s.identifier).ToList(),
fullChangeset, fullChangeset,
registry.InstalledDlls, registry.InstalledDlc,
// Consider virtual dependencies satisfied so user can make a new choice if they skip
rel => rel.LatestAvailableWithProvides(registry, crit).Count > 1)
.ToHashSet();
toInstall.RemoveAll(m => dependers.Contains(m.identifier));
}
Expand Down
Loading

0 comments on commit 01b93f7

Please sign in to comment.