Skip to content

Commit

Permalink
Merge #4253 Allow installs and removals to be cancelled
Browse files Browse the repository at this point in the history
  • Loading branch information
HebaruSan committed Nov 7, 2024
2 parents bd2f866 + 3ceb445 commit f776f02
Show file tree
Hide file tree
Showing 8 changed files with 65 additions and 59 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ All notable changes to this project will be documented in this file.
- [Multiple] Cache migration and other fixes (#4240 by: HebaruSan)
- [Multiple] Start installing mods while downloads are still in progress (#4249 by: HebaruSan)
- [Multiple] Sort dependencies first in modpacks (#4252 by: HebaruSan)
- [Multiple] Allow installs and removals to be cancelled (#4253 by: HebaruSan)

### Bugfixes

Expand Down
26 changes: 21 additions & 5 deletions Core/ModuleInstaller.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
using CKAN.Versioning;
using CKAN.Configuration;
using CKAN.Games;
using System.Threading;

namespace CKAN
{
Expand All @@ -34,17 +35,23 @@ public class ModuleInstaller

private static readonly ILog log = LogManager.GetLogger(typeof(ModuleInstaller));

private readonly GameInstance instance;
private readonly NetModuleCache Cache;
private readonly string? userAgent;
private readonly GameInstance instance;
private readonly NetModuleCache Cache;
private readonly string? userAgent;
private readonly CancellationToken cancelToken;

// Constructor
public ModuleInstaller(GameInstance inst, NetModuleCache cache, IUser user, string? userAgent = null)
public ModuleInstaller(GameInstance inst,
NetModuleCache cache,
IUser user,
string? userAgent = null,
CancellationToken cancelToken = default)
{
User = user;
Cache = cache;
instance = inst;
this.userAgent = userAgent;
this.cancelToken = cancelToken;
log.DebugFormat("Creating ModuleInstaller for {0}", instance.GameDir());
}

Expand Down Expand Up @@ -182,7 +189,7 @@ public void InstallList(ICollection<CkanModule> modules,
long installedBytes = 0;
if (downloads.Count > 0)
{
downloader ??= new NetAsyncModulesDownloader(User, Cache, userAgent);
downloader ??= new NetAsyncModulesDownloader(User, Cache, userAgent, cancelToken);
downloader.OverallDownloadProgress += brc =>
{
downloadedBytes = downloadBytes - brc.BytesLeft;
Expand Down Expand Up @@ -464,6 +471,10 @@ private List<string> InstallModule(CkanModule module,
var fileProgress = new ProgressImmediate<long>(bytes => moduleProgress?.Report(installedBytes + bytes));
foreach (InstallableFile file in files)
{
if (cancelToken.IsCancellationRequested)
{
throw new CancelledActionKraken();
}
log.DebugFormat("Copying {0}", file.source.Name);
var path = CopyZipEntry(zipfile, file.source, file.destination, file.makedir,
fileProgress);
Expand Down Expand Up @@ -947,6 +958,11 @@ private void Uninstall(string identifier,
long bytesDeleted = 0;
foreach (string relPath in modFiles)
{
if (cancelToken.IsCancellationRequested)
{
throw new CancelledActionKraken();
}

string absPath = instance.ToAbsoluteGameDir(relPath);

try
Expand Down
5 changes: 0 additions & 5 deletions Core/Net/IDownloader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,5 @@ public interface IDownloader

IEnumerable<CkanModule> ModulesAsTheyFinish(ICollection<CkanModule> cached,
ICollection<CkanModule> toDownload);

/// <summary>
/// Cancel any running downloads.
/// </summary>
void CancelDownload();
}
}
26 changes: 12 additions & 14 deletions Core/Net/NetAsyncDownloader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ public partial class NetAsyncDownloader
// For inter-thread communication
private volatile bool download_canceled;
private readonly ManualResetEvent complete_or_canceled;
private readonly CancellationToken cancelToken;

/// <summary>
/// Invoked when a download completes or fails.
Expand All @@ -51,17 +52,19 @@ public partial class NetAsyncDownloader
/// </summary>
public NetAsyncDownloader(IUser user,
Func<HashAlgorithm?> getHashAlgo,
string? userAgent = null)
string? userAgent = null,
CancellationToken cancelToken = default)
{
User = user;
this.userAgent = userAgent ?? Net.UserAgentString;
this.getHashAlgo = getHashAlgo;
this.cancelToken = cancelToken;
complete_or_canceled = new ManualResetEvent(false);
}

public static void DownloadWithProgress(IList<DownloadTarget> downloadTargets,
string? userAgent,
IUser? user = null)
IUser? user = null)
{
var downloader = new NetAsyncDownloader(user ?? new NullUser(), () => null, userAgent);
downloader.onOneCompleted += (target, error, etag, hash) =>
Expand Down Expand Up @@ -123,7 +126,7 @@ public void DownloadAndWait(ICollection<DownloadTarget> targets)
log.Debug("Completion signal reset");

// If the user cancelled our progress, then signal that.
if (old_download_canceled)
if (old_download_canceled || cancelToken.IsCancellationRequested)
{
log.DebugFormat("User clicked cancel, discarding {0} queued downloads: {1}", queuedDownloads.Count, string.Join(", ", queuedDownloads.SelectMany(dl => dl.target.urls)));
// Ditch anything we haven't started
Expand Down Expand Up @@ -185,17 +188,6 @@ public void DownloadAndWait(ICollection<DownloadTarget> targets)
log.Debug("Done downloading");
}

/// <summary>
/// <see cref="IDownloader.CancelDownload()"/>
/// This will also call onCompleted with all null arguments.
/// </summary>
public void CancelDownload()
{
log.Info("Cancelling download");
download_canceled = true;
triggerCompleted();
}

/// <summary>
/// Downloads our files.
/// </summary>
Expand Down Expand Up @@ -297,6 +289,12 @@ private void FileProgressReport(DownloadPart download, long bytesDownloaded, lon
}

OverallProgress?.Invoke(rateCounter);

if (cancelToken.IsCancellationRequested)
{
download_canceled = true;
triggerCompleted();
}
}

private void PopFromQueue(string host)
Expand Down
24 changes: 8 additions & 16 deletions Core/Net/NetAsyncModulesDownloader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,13 @@ public class NetAsyncModulesDownloader : IDownloader
/// <summary>
/// Returns a perfectly boring NetAsyncModulesDownloader.
/// </summary>
public NetAsyncModulesDownloader(IUser user, NetModuleCache cache, string? userAgent = null)
public NetAsyncModulesDownloader(IUser user,
NetModuleCache cache,
string? userAgent = null,
CancellationToken cancelToken = default)
{
modules = new List<CkanModule>();
downloader = new NetAsyncDownloader(user, SHA256.Create, userAgent);
downloader = new NetAsyncDownloader(user, SHA256.Create, userAgent, cancelToken);
// Schedule us to process each module on completion.
downloader.onOneCompleted += ModuleDownloadComplete;
downloader.TargetProgress += (target, remaining, total) =>
Expand All @@ -44,6 +47,7 @@ public NetAsyncModulesDownloader(IUser user, NetModuleCache cache, string? userA
};
downloader.OverallProgress += brc => OverallDownloadProgress?.Invoke(brc);
this.cache = cache;
this.cancelToken = cancelToken;
}

internal NetAsyncDownloader.DownloadTarget TargetFromModuleGroup(
Expand Down Expand Up @@ -94,7 +98,6 @@ public void DownloadModules(IEnumerable<CkanModule> modules)
grp => grp.ToArray());
try
{
cancelTokenSrc = new CancellationTokenSource();
// Start the downloads!
downloader.DownloadAndWait(targetModules.Keys);
this.modules.Clear();
Expand All @@ -117,17 +120,6 @@ public void DownloadModules(IEnumerable<CkanModule> modules)
}
}

/// <summary>
/// <see cref="IDownloader.CancelDownload()"/>
/// </summary>
public void CancelDownload()
{
// Cancel downloads
downloader.CancelDownload();
// Cancel validation/store
cancelTokenSrc?.Cancel();
}

public IEnumerable<CkanModule> ModulesAsTheyFinish(ICollection<CkanModule> cached,
ICollection<CkanModule> toDownload)
{
Expand Down Expand Up @@ -214,7 +206,7 @@ private void ModuleDownloadComplete(NetAsyncDownloader.DownloadTarget target,
fileSize)),
module.StandardName(),
false,
cancelTokenSrc?.Token);
cancelToken);
File.Delete(filename);
foreach (var m in completedMods)
{
Expand Down Expand Up @@ -266,6 +258,6 @@ private void ModuleDownloadComplete(NetAsyncDownloader.DownloadTarget target,
private readonly NetAsyncDownloader downloader;
private IUser User => downloader.User;
private readonly NetModuleCache cache;
private CancellationTokenSource? cancelTokenSrc;
private CancellationToken cancelToken;
}
}
10 changes: 6 additions & 4 deletions GUI/Main/MainDownload.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System.Linq;
using System.Collections.Generic;
using System.ComponentModel;
using System.Threading;
using System.Threading.Tasks;

using CKAN.GUI.Attributes;
Expand Down Expand Up @@ -46,11 +47,13 @@ private void CacheMod(object? sender, DoWorkEventArgs? e)
&& e.Argument is GUIMod gm
&& Manager?.Cache != null)
{
downloader = new NetAsyncModulesDownloader(currentUser, Manager.Cache, userAgent);
var cancelTokenSrc = new CancellationTokenSource();
Wait.OnCancel += cancelTokenSrc.Cancel;
downloader = new NetAsyncModulesDownloader(currentUser, Manager.Cache, userAgent,
cancelTokenSrc.Token);
downloader.DownloadProgress += OnModDownloading;
downloader.StoreProgress += OnModValidating;
downloader.OverallDownloadProgress += currentUser.RaiseProgress;
Wait.OnCancel += downloader.CancelDownload;
downloader.DownloadModules(new List<CkanModule> { gm.ToCkanModule() });
e.Result = e.Argument;
}
Expand All @@ -60,7 +63,6 @@ public void PostModCaching(object? sender, RunWorkerCompletedEventArgs? e)
{
if (downloader != null)
{
Wait.OnCancel -= downloader.CancelDownload;
downloader = null;
}
// Can't access e.Result if there's an error
Expand All @@ -69,7 +71,7 @@ public void PostModCaching(object? sender, RunWorkerCompletedEventArgs? e)
switch (e.Error)
{

case CancelledActionKraken exc:
case CancelledActionKraken:
// User already knows they cancelled, get out
HideWaitDialog();
EnableMainWindow();
Expand Down
20 changes: 12 additions & 8 deletions GUI/Main/MainInstall.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System.ComponentModel;
using System.Linq;
using System.Transactions;
using System.Threading;
#if NET5_0_OR_GREATER
using System.Runtime.Versioning;
#endif
Expand Down Expand Up @@ -83,9 +84,17 @@ private void InstallMods(object? sender, DoWorkEventArgs? e)
&& Manager.Cache != null
&& e?.Argument is (List<ModChange> changes, RelationshipResolverOptions options))
{
var cancelTokenSrc = new CancellationTokenSource();
Wait.OnCancel += () =>
{
canceled = true;
cancelTokenSrc.Cancel();
};

var registry_manager = RegistryManager.Instance(CurrentInstance, repoData);
var registry = registry_manager.registry;
var installer = new ModuleInstaller(CurrentInstance, Manager.Cache, currentUser, userAgent);
var installer = new ModuleInstaller(CurrentInstance, Manager.Cache, currentUser, userAgent,
cancelTokenSrc.Token);
// Avoid accumulating multiple event handlers
installer.OneComplete -= OnModInstalled;
installer.InstallProgress -= OnModInstalling;
Expand Down Expand Up @@ -195,16 +204,11 @@ private void InstallMods(object? sender, DoWorkEventArgs? e)
});
tabController.SetTabLock(true);

var downloader = new NetAsyncModulesDownloader(currentUser, Manager.Cache, userAgent);
var downloader = new NetAsyncModulesDownloader(currentUser, Manager.Cache, userAgent,
cancelTokenSrc.Token);
downloader.DownloadProgress += OnModDownloading;
downloader.StoreProgress += OnModValidating;

Wait.OnCancel += () =>
{
canceled = true;
downloader.CancelDownload();
};

HashSet<string>? possibleConfigOnlyDirs = null;

// Treat whole changeset as atomic
Expand Down
12 changes: 5 additions & 7 deletions GUI/Main/MainRepo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.ComponentModel;
using System.Timers;
using System.Threading;
using System.Linq;
using System.Windows.Forms;
using System.Transactions;
Expand Down Expand Up @@ -66,6 +67,9 @@ private void UpdateRepo(object? sender, DoWorkEventArgs? e)
// Note the current mods' compatibility for the NewlyCompatible filter
var registry = regMgr.registry;

var cancelTokenSrc = new CancellationTokenSource();
Wait.OnCancel += cancelTokenSrc.Cancel;

// Load cached data with progress bars instead of without if not already loaded
// (which happens if auto-update is enabled, otherwise this is a no-op).
// We need the old data to alert the user of newly compatible modules after update.
Expand All @@ -89,7 +93,6 @@ private void UpdateRepo(object? sender, DoWorkEventArgs? e)
var repos = registry.Repositories.Values.ToArray();
try
{
bool canceled = false;
var downloader = new NetAsyncDownloader(currentUser, () => null, userAgent);
downloader.TargetProgress += (target, remaining, total) =>
{
Expand All @@ -100,18 +103,13 @@ private void UpdateRepo(object? sender, DoWorkEventArgs? e)
Wait.SetProgress(repo.name, remaining, total);
}
};
Wait.OnCancel += () =>
{
canceled = true;
downloader.CancelDownload();
};

currentUser.RaiseMessage(Properties.Resources.MainRepoUpdating);

var updateResult = repoData.Update(repos, CurrentInstance.game,
forceFullRefresh, downloader, currentUser, userAgent);

if (canceled)
if (cancelTokenSrc.Token.IsCancellationRequested)
{
throw new CancelledActionKraken();
}
Expand Down

0 comments on commit f776f02

Please sign in to comment.