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

Caching and changeset fixes #3881

Merged
merged 1 commit into from
Aug 16, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
33 changes: 1 addition & 32 deletions Core/Net/NetAsyncModulesDownloader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,37 +42,6 @@ public NetAsyncModulesDownloader(IUser user, NetModuleCache cache)
this.cache = cache;
}

internal static List<HashSet<CkanModule>> GroupByDownloads(IEnumerable<CkanModule> modules)
{
// Each module is a vertex, each download URL is an edge
// We want to group the vertices by transitive connectedness
// We can go breadth first or depth first
// Once we encounter a mod, we never have to look at it again
var unsearched = modules.ToHashSet();
var groups = new List<HashSet<CkanModule>>();
while (unsearched.Count > 0)
{
// Find one group, remove it from unsearched, add it to groups
var searching = new List<CkanModule> { unsearched.First() };
unsearched.ExceptWith(searching);
var found = searching.ToHashSet();
// Breadth first search to find all modules any URLs in common, transitively
while (searching.Count > 0)
{
var origin = searching.First();
searching.Remove(origin);
var neighbors = origin.download
.SelectMany(dlUri => unsearched.Where(other => other.download.Contains(dlUri)))
.ToHashSet();
unsearched.ExceptWith(neighbors);
searching.AddRange(neighbors);
found.UnionWith(neighbors);
}
groups.Add(found);
}
return groups;
}

internal Net.DownloadTarget TargetFromModuleGroup(HashSet<CkanModule> group,
string[] preferredHosts)
=> TargetFromModuleGroup(group, group.OrderBy(m => m.identifier).First(), preferredHosts);
Expand Down Expand Up @@ -102,7 +71,7 @@ public void DownloadModules(IEnumerable<CkanModule> modules)
{
var activeURLs = this.modules.SelectMany(m => m.download)
.ToHashSet();
var moduleGroups = GroupByDownloads(modules);
var moduleGroups = CkanModule.GroupByDownloads(modules);
// Make sure we have enough space to download and cache
cache.CheckFreeSpace(moduleGroups.Select(grp => grp.First().download_size)
.Sum());
Expand Down
11 changes: 3 additions & 8 deletions Core/Registry/Registry.cs
Original file line number Diff line number Diff line change
Expand Up @@ -487,10 +487,7 @@ public void SetAllAvailable(IEnumerable<CkanModule> newAvail)
/// <returns>
/// True if we have at least one available mod, false otherwise.
/// </returns>
public bool HasAnyAvailable()
{
return available_modules.Count > 0;
}
public bool HasAnyAvailable() => available_modules.Count > 0;

/// <summary>
/// Mark a given module as available.
Expand Down Expand Up @@ -643,11 +640,9 @@ public string GetAvailableMetadata(string identifier)
/// </summary>
/// <param name="identifier">Name of mod to check</param>
public GameVersion LatestCompatibleKSP(string identifier)
{
return available_modules.ContainsKey(identifier)
? available_modules[identifier].LatestCompatibleKSP()
=> available_modules.TryGetValue(identifier, out AvailableModule availMod)
? availMod.LatestCompatibleKSP()
: null;
}

/// <summary>
/// Find the minimum and maximum mod versions and compatible game versions
Expand Down
41 changes: 41 additions & 0 deletions Core/Types/CkanModule.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,13 @@
using System.Runtime.Serialization;
using System.Text;
using System.Text.RegularExpressions;

using Autofac;
using log4net;
using Newtonsoft.Json;

using CKAN.Versioning;
using CKAN.Extensions;
using CKAN.Games;

namespace CKAN
Expand Down Expand Up @@ -806,6 +809,44 @@ public static string FmtSize(long bytes)
: bytes < K*K*K*K ? $"{bytes /K/K/K :N1} GiB"
: $"{bytes /K/K/K/K :N1} TiB";

public HashSet<CkanModule> GetDownloadsGroup(IEnumerable<CkanModule> modules)
=> OneDownloadGroupingPass(modules.ToHashSet(), this);

public static List<HashSet<CkanModule>> GroupByDownloads(IEnumerable<CkanModule> modules)
{
// Each module is a vertex, each download URL is an edge
// We want to group the vertices by transitive connectedness
// We can go breadth first or depth first
// Once we encounter a mod, we never have to look at it again
var unsearched = modules.ToHashSet();
var groups = new List<HashSet<CkanModule>>();
while (unsearched.Count > 0)
{
groups.Add(OneDownloadGroupingPass(unsearched, unsearched.First()));
}
return groups;
}

private static HashSet<CkanModule> OneDownloadGroupingPass(HashSet<CkanModule> unsearched,
CkanModule firstModule)
{
var searching = new List<CkanModule> { firstModule };
unsearched.ExceptWith(searching);
var found = searching.ToHashSet();
// Breadth first search to find all modules with any URLs in common, transitively
while (searching.Count > 0)
{
var origin = searching.First();
searching.Remove(origin);
var neighbors = origin.download
.SelectMany(dlUri => unsearched.Where(other => other.download.Contains(dlUri)))
.ToHashSet();
unsearched.ExceptWith(neighbors);
searching.AddRange(neighbors);
found.UnionWith(neighbors);
}
return found;
}
}

public class InvalidModuleAttributesException : Exception
Expand Down
44 changes: 26 additions & 18 deletions GUI/Controls/Changeset.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,29 +20,19 @@ public Changeset()
List<ModuleLabel> AlertLabels,
Dictionary<CkanModule, string> conflicts)
{
changeset = changes;
alertLabels = AlertLabels;
ChangesListView.Items.Clear();
if (changes != null)
{
// Changeset sorting is handled upstream in the resolver
ChangesListView.Items.AddRange(changes
.Where(ch => ch.ChangeType != GUIModChangeType.None)
.Select(ch => makeItem(ch, conflicts))
.ToArray());
ChangesListView.AutoResizeColumns(ColumnHeaderAutoResizeStyle.ColumnContent);
ChangesListView.AutoResizeColumns(ColumnHeaderAutoResizeStyle.HeaderSize);
}
changeset = changes;
alertLabels = AlertLabels;
this.conflicts = conflicts;
ConfirmChangesButton.Enabled = conflicts == null || !conflicts.Any();
}

protected override void OnVisibleChanged(EventArgs e)
{
base.OnVisibleChanged(e);
if (Visible && Platform.IsMono)
if (Visible)
{
// Workaround: make sure the ListView headers are drawn
Util.Invoke(ChangesListView, () => ChangesListView.EndUpdate());
// Update list on each refresh in case caching changed
UpdateList();
}
}

Expand All @@ -54,6 +44,23 @@ public ListView.SelectedListViewItemCollection SelectedItems
public event Action<List<ModChange>> OnConfirmChanges;
public event Action<bool> OnCancelChanges;

private void UpdateList()
{
ChangesListView.BeginUpdate();
ChangesListView.Items.Clear();
if (changeset != null)
{
// Changeset sorting is handled upstream in the resolver
ChangesListView.Items.AddRange(changeset
.Where(ch => ch.ChangeType != GUIModChangeType.None)
.Select(ch => makeItem(ch, conflicts))
.ToArray());
ChangesListView.AutoResizeColumns(ColumnHeaderAutoResizeStyle.ColumnContent);
ChangesListView.AutoResizeColumns(ColumnHeaderAutoResizeStyle.HeaderSize);
}
ChangesListView.EndUpdate();
}

private void ChangesListView_SelectedIndexChanged(object sender, EventArgs e)
{
OnSelectedItemsChanged?.Invoke(ChangesListView.SelectedItems);
Expand Down Expand Up @@ -101,7 +108,8 @@ private ListViewItem makeItem(ModChange change, Dictionary<CkanModule, string> c
};
}

private List<ModChange> changeset;
private List<ModuleLabel> alertLabels;
private List<ModChange> changeset;
private List<ModuleLabel> alertLabels;
private Dictionary<CkanModule, string> conflicts;
}
}
44 changes: 30 additions & 14 deletions GUI/Controls/ManageMods.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,9 @@ public ManageMods()
FilterNotInstalledButton.ToolTipText = Properties.Resources.FilterLinkToolTip;
FilterIncompatibleButton.ToolTipText = Properties.Resources.FilterLinkToolTip;

mainModList = new ModList(source => UpdateFilters());
mainModList = new ModList();
mainModList.ModFiltersUpdated += UpdateFilters;
UpdateFilters();
FilterToolButton.MouseHover += (sender, args) => FilterToolButton.ShowDropDown();
launchGameToolStripMenuItem.MouseHover += (sender, args) => launchGameToolStripMenuItem.ShowDropDown();
ApplyToolButton.MouseHover += (sender, args) => ApplyToolButton.ShowDropDown();
Expand Down Expand Up @@ -929,8 +931,9 @@ public void FocusMod(string key, bool exactMatch, bool showAsFirst = false)
});

ModGrid.ClearSelection();
var rows = ModGrid.Rows.Cast<DataGridViewRow>().Where(row => row.Visible);
DataGridViewRow match = rows.FirstOrDefault(does_name_begin_with_key);
DataGridViewRow match = ModGrid.Rows.Cast<DataGridViewRow>()
.Where(row => row.Visible)
.FirstOrDefault(does_name_begin_with_key);
if (match == null && first_match != null)
{
// If there were no matches after the first match, cycle over to the beginning.
Expand Down Expand Up @@ -1031,6 +1034,13 @@ private void reinstallToolStripMenuItem_Click(object sender, EventArgs e)
.ToList());
}

public Dictionary<string, GUIMod> AllGUIMods()
=> ModGrid.Rows.Cast<DataGridViewRow>()
.Select(row => row.Tag as GUIMod)
.Where(guiMod => guiMod != null)
.ToDictionary(guiMod => guiMod.Identifier,
guiMod => guiMod);

private void purgeContentsToolStripMenuItem_Click(object sender, EventArgs e)
{
// Purge other versions as well since the user is likely to want that
Expand All @@ -1044,7 +1054,18 @@ private void purgeContentsToolStripMenuItem_Click(object sender, EventArgs e)
{
Main.Instance.Manager.Cache.Purge(mod);
}
selected.UpdateIsCached();

// Update all mods that share the same ZIP
var allGuiMods = AllGUIMods();
foreach (var otherMod in selected.ToModule().GetDownloadsGroup(
allGuiMods.Values.Select(guiMod => guiMod.ToModule())))
{
allGuiMods[otherMod.identifier].UpdateIsCached();
}

// Reapply searches in case is:cached or not:cached is active
UpdateFilters();

Main.Instance.RefreshModContentsTree();
}
}
Expand All @@ -1067,7 +1088,7 @@ private void EditModSearches_SurrenderFocus()
Util.Invoke(this, () => ModGrid.Focus());
}

private void UpdateFilters()
public void UpdateFilters()
{
Util.Invoke(this, _UpdateFilters);
}
Expand Down Expand Up @@ -1503,15 +1524,10 @@ public void ResetFilterAndSelectModOnList(string key)
FocusMod(key, true);
}

public GUIMod SelectedModule
{
get
{
return ModGrid.SelectedRows.Count == 0
? null
: ModGrid.SelectedRows[0]?.Tag as GUIMod;
}
}
public GUIMod SelectedModule =>
ModGrid.SelectedRows.Count == 0
? null
: ModGrid.SelectedRows[0]?.Tag as GUIMod;

#region Navigation History

Expand Down
26 changes: 22 additions & 4 deletions GUI/Main/MainDownload.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Linq;
using System.Collections.Generic;
using System.ComponentModel;
using System.Threading.Tasks;
Expand Down Expand Up @@ -26,7 +27,7 @@ public void StartDownload(GUIMod module)
{
// Just pass to the existing worker
downloader.DownloadModules(new List<CkanModule> { module.ToCkanModule() });
module.UpdateIsCached();
UpdateCachedByDownloads(module);
});
}
else
Expand Down Expand Up @@ -82,13 +83,30 @@ public void PostModCaching(object sender, RunWorkerCompletedEventArgs e)
}
}

private void UpdateCachedByDownloads(GUIMod module)
{
// Update all mods that share the same ZIP
var allGuiMods = ManageMods.AllGUIMods();
foreach (var otherMod in module.ToModule().GetDownloadsGroup(
allGuiMods.Values.Select(guiMod => guiMod.ToModule())))
{
allGuiMods[otherMod.identifier].UpdateIsCached();
}
}

private void _PostModCaching(GUIMod module)
{
module.UpdateIsCached();
// Update mod list in case is:cached or not:cached filters are active
RefreshModList();
UpdateCachedByDownloads(module);

// Reapply searches in case is:cached or not:cached is active
ManageMods.UpdateFilters();

// User might have selected another row. Show current in tree.
RefreshModContentsTree();

// Close progress tab and switch back to mod list
HideWaitDialog();
EnableMainWindow();
}
}
}
23 changes: 5 additions & 18 deletions GUI/Model/GUIMod.cs
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ private void OnPropertyChanged([CallerMemberName] string name = null)
public bool IsUpgradeChecked { get; private set; }
public bool IsReplaceChecked { get; private set; }
public bool IsNew { get; set; }
public bool IsCKAN { get; private set; }
public bool IsCKAN => Mod != null;
public string Abbrevation { get; private set; }

public string SearchableName { get; private set; }
Expand Down Expand Up @@ -156,7 +156,6 @@ public GUIMod(CkanModule mod, IRegistryQuerier registry, GameVersionCriteria cur
: this(mod.identifier, registry, current_game_version, incompatible, hideEpochs, hideV)
{
Mod = mod;
IsCKAN = mod is CkanModule;

Name = mod.name.Trim();
Abstract = mod.@abstract.Trim();
Expand Down Expand Up @@ -288,10 +287,7 @@ public CkanModule ToCkanModule()
/// Get the CkanModule associated with this GUIMod.
/// </summary>
/// <returns>The CkanModule associated with this GUIMod or null if there is none</returns>
public CkanModule ToModule()
{
return Mod;
}
public CkanModule ToModule() => Mod;

public IEnumerable<ModChange> GetModChanges()
{
Expand Down Expand Up @@ -431,10 +427,7 @@ public void SetAutoInstallChecked(DataGridViewRow row, DataGridViewColumn col, b
}
}

private bool Equals(GUIMod other)
{
return Equals(Identifier, other.Identifier);
}
private bool Equals(GUIMod other) => Equals(Identifier, other.Identifier);

public override bool Equals(object obj)
{
Expand All @@ -444,14 +437,8 @@ public override bool Equals(object obj)
return Equals((GUIMod) obj);
}

public override int GetHashCode()
{
return Identifier?.GetHashCode() ?? 0;
}
public override int GetHashCode() => Identifier?.GetHashCode() ?? 0;

public override string ToString()
{
return $"{ToModule()}";
}
public override string ToString() => Mod.ToString();
}
}