diff --git a/StabilityMatrix.Avalonia/ViewModels/DownloadProgressItemViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/DownloadProgressItemViewModel.cs index 7e11d069..ed359bbb 100644 --- a/StabilityMatrix.Avalonia/ViewModels/DownloadProgressItemViewModel.cs +++ b/StabilityMatrix.Avalonia/ViewModels/DownloadProgressItemViewModel.cs @@ -13,6 +13,7 @@ public class DownloadProgressItemViewModel : PausableProgressItemViewModelBase { this.download = download; + Id = download.Id; Name = download.FileName; State = download.ProgressState; diff --git a/StabilityMatrix.Avalonia/ViewModels/MainWindowViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/MainWindowViewModel.cs index 7ab02651..d5b1fad9 100644 --- a/StabilityMatrix.Avalonia/ViewModels/MainWindowViewModel.cs +++ b/StabilityMatrix.Avalonia/ViewModels/MainWindowViewModel.cs @@ -24,6 +24,7 @@ public partial class MainWindowViewModel : ViewModelBase { private readonly ISettingsManager settingsManager; private readonly ServiceManager dialogFactory; + private readonly ITrackedDownloadService trackedDownloadService; private readonly IDiscordRichPresenceService discordRichPresenceService; public string Greeting => "Welcome to Avalonia!"; @@ -45,11 +46,13 @@ public partial class MainWindowViewModel : ViewModelBase public MainWindowViewModel( ISettingsManager settingsManager, IDiscordRichPresenceService discordRichPresenceService, + ITrackedDownloadService trackedDownloadService, ServiceManager dialogFactory) { this.settingsManager = settingsManager; this.dialogFactory = dialogFactory; this.discordRichPresenceService = discordRichPresenceService; + this.trackedDownloadService = trackedDownloadService; ProgressManagerViewModel = dialogFactory.Get(); UpdateViewModel = dialogFactory.Get(); @@ -81,6 +84,9 @@ public partial class MainWindowViewModel : ViewModelBase // Initialize Discord Rich Presence (this needs LibraryDir so is set here) discordRichPresenceService.UpdateState(); + // Load in-progress downloads + ProgressManagerViewModel.AddDownloads(trackedDownloadService.Downloads); + // Index checkpoints if we dont have Task.Run(() => settingsManager.IndexCheckpoints()).SafeFireAndForget(); diff --git a/StabilityMatrix.Avalonia/ViewModels/ProgressManagerViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/ProgressManagerViewModel.cs index 9024965a..32de83a2 100644 --- a/StabilityMatrix.Avalonia/ViewModels/ProgressManagerViewModel.cs +++ b/StabilityMatrix.Avalonia/ViewModels/ProgressManagerViewModel.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; using Avalonia.Collections; @@ -37,6 +38,19 @@ public partial class ProgressManagerViewModel : PageViewModelBase var vm = new DownloadProgressItemViewModel(e); ProgressItems.Add(vm); } + + public void AddDownloads(IEnumerable downloads) + { + foreach (var download in downloads) + { + if (ProgressItems.Any(vm => vm.Id == download.Id)) + { + continue; + } + var vm = new DownloadProgressItemViewModel(download); + ProgressItems.Add(vm); + } + } public void StartEventListener() { diff --git a/StabilityMatrix.Core/Models/TrackedDownload.cs b/StabilityMatrix.Core/Models/TrackedDownload.cs index 17fc753b..e381ab1e 100644 --- a/StabilityMatrix.Core/Models/TrackedDownload.cs +++ b/StabilityMatrix.Core/Models/TrackedDownload.cs @@ -1,6 +1,7 @@ using System.Diagnostics.CodeAnalysis; using System.Text.Json.Serialization; using AsyncAwaitBestPractices; +using NLog; using StabilityMatrix.Core.Helper; using StabilityMatrix.Core.Models.FileInterfaces; using StabilityMatrix.Core.Models.Progress; @@ -16,6 +17,8 @@ public class TrackedDownloadProgressEventArgs : EventArgs public class TrackedDownload { + private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); + [JsonIgnore] private IDownloadService? downloadService; @@ -128,6 +131,7 @@ public class TrackedDownload { throw new InvalidOperationException($"Download state must be inactive to start, not {ProgressState}"); } + Logger.Debug("Starting download {Download}", FileName); EnsureDownloadService(); @@ -138,34 +142,59 @@ public class TrackedDownload .ContinueWith(OnDownloadTaskCompleted); ProgressState = ProgressState.Working; + OnProgressStateChanged(ProgressState); } public void Resume() { - if (ProgressState != ProgressState.Inactive) return; - + if (ProgressState != ProgressState.Inactive) + { + Logger.Warn("Attempted to resume download {Download} but it is not paused ({State})", FileName, ProgressState); + } + Logger.Debug("Resuming download {Download}", FileName); + + // Read the temp file to get the current size + var tempSize = 0L; + + var tempFile = DownloadDirectory.JoinFile(TempFileName); + if (tempFile.Exists) + { + tempSize = tempFile.Info.Length; + } + EnsureDownloadService(); downloadCancellationTokenSource = new CancellationTokenSource(); downloadPauseTokenSource = new CancellationTokenSource(); - downloadTask = StartDownloadTask(0, AggregateCancellationTokenSource.Token) + downloadTask = StartDownloadTask(tempSize, AggregateCancellationTokenSource.Token) .ContinueWith(OnDownloadTaskCompleted); ProgressState = ProgressState.Working; + OnProgressStateChanged(ProgressState); } public void Pause() { - if (ProgressState != ProgressState.Working) return; + if (ProgressState != ProgressState.Working) + { + Logger.Warn("Attempted to pause download {Download} but it is not in progress ({State})", FileName, ProgressState); + return; + } + Logger.Debug("Pausing download {Download}", FileName); downloadPauseTokenSource?.Cancel(); } public void Cancel() { - if (ProgressState is not (ProgressState.Working or ProgressState.Inactive)) return; + if (ProgressState is not (ProgressState.Working or ProgressState.Inactive)) + { + Logger.Warn("Attempted to cancel download {Download} but it is not in progress ({State})", FileName, ProgressState); + return; + } + Logger.Debug("Cancelling download {Download}", FileName); downloadCancellationTokenSource?.Cancel(); } diff --git a/StabilityMatrix.Core/Services/ITrackedDownloadService.cs b/StabilityMatrix.Core/Services/ITrackedDownloadService.cs index ca8fcab4..f6baadbc 100644 --- a/StabilityMatrix.Core/Services/ITrackedDownloadService.cs +++ b/StabilityMatrix.Core/Services/ITrackedDownloadService.cs @@ -5,6 +5,8 @@ namespace StabilityMatrix.Core.Services; public interface ITrackedDownloadService { + IEnumerable Downloads { get; } + event EventHandler? DownloadAdded; TrackedDownload NewDownload(Uri downloadUrl, FilePath downloadPath); diff --git a/StabilityMatrix.Core/Services/TrackedDownloadService.cs b/StabilityMatrix.Core/Services/TrackedDownloadService.cs index 8d2f3688..b7f6ec50 100644 --- a/StabilityMatrix.Core/Services/TrackedDownloadService.cs +++ b/StabilityMatrix.Core/Services/TrackedDownloadService.cs @@ -17,6 +17,8 @@ public class TrackedDownloadService : ITrackedDownloadService, IDisposable private readonly ConcurrentDictionary downloads = new(); + public IEnumerable Downloads => downloads.Values.Select(x => x.Item1); + /// public event EventHandler? DownloadAdded; @@ -136,6 +138,10 @@ public class TrackedDownloadService : ITrackedDownloadService, IDisposable download.SetDownloadService(downloadService); downloads.TryAdd(download.Id, (download, fileStream)); + + // Connect to state changed event to update json file + download.ProgressStateChanged += TrackedDownload_OnProgressStateChanged; + OnDownloadAdded(download); logger.LogDebug("Loaded in-progress download {Download}", download.FileName);