From bf5d092f43ca01fdb7acd1a55516fa23e14f0939 Mon Sep 17 00:00:00 2001 From: Timothy Baldridge Date: Mon, 27 Dec 2021 08:37:20 -0700 Subject: [PATCH] Implement mod list maintainer --- .../View Models/Gallery/ModListMetadataVM.cs | 26 ++++--- Wabbajack.RateLimiter/Job.cs | 4 + .../ServiceExtensions.cs | 3 + .../Services/ModListDownloadMaintainer.cs | 78 +++++++++++++++++++ 4 files changed, 102 insertions(+), 9 deletions(-) create mode 100644 Wabbajack.Services.OSIntegrated/Services/ModListDownloadMaintainer.cs diff --git a/Wabbajack.App.Wpf/View Models/Gallery/ModListMetadataVM.cs b/Wabbajack.App.Wpf/View Models/Gallery/ModListMetadataVM.cs index fe0e70b5..3327acf3 100644 --- a/Wabbajack.App.Wpf/View Models/Gallery/ModListMetadataVM.cs +++ b/Wabbajack.App.Wpf/View Models/Gallery/ModListMetadataVM.cs @@ -13,6 +13,7 @@ using System.Windows.Input; using System.Windows.Media.Imaging; using Alphaleonis.Win32.Filesystem; using DynamicData; +using Microsoft.Extensions.Logging; using ReactiveUI; using ReactiveUI.Fody.Helpers; using Wabbajack.Common; @@ -23,7 +24,9 @@ using Wabbajack.Lib.Downloaders; using Wabbajack.Lib.Extensions; using Wabbajack.Lib.ModListRegistry; using Wabbajack.Paths; +using Wabbajack.Paths.IO; using Wabbajack.RateLimiter; +using Wabbajack.Services.OSIntegrated.Services; namespace Wabbajack { @@ -84,12 +87,17 @@ namespace Wabbajack public bool LoadingImage => _LoadingImage.Value; private Subject IsLoadingIdle; + private readonly ILogger _logger; + private readonly ModListDownloadMaintainer _maintainer; - public ModListMetadataVM(ModListGalleryVM parent, ModlistMetadata metadata) + public ModListMetadataVM(ILogger logger, ModListGalleryVM parent, ModlistMetadata metadata, + ModListDownloadMaintainer maintainer) { + _logger = logger; _parent = parent; + _maintainer = maintainer; Metadata = metadata; - Location = LauncherUpdater.CommonFolder.Value.Combine("downloaded_mod_lists", Metadata.Links.MachineURL + (string)Consts.ModListExtension); + Location = LauncherUpdater.CommonFolder.Value.Combine("downloaded_mod_lists", Metadata.Links.MachineURL).WithExtension(Ext.Wabbajack); ModListTagList = new List(); Metadata.tags.ForEach(tag => @@ -103,7 +111,7 @@ namespace Wabbajack VersionText = "Modlist version : " + Metadata.Version; IsBroken = metadata.ValidationSummary.HasFailures || metadata.ForceDown; //https://www.wabbajack.org/#/modlists/info?machineURL=eldersouls - OpenWebsiteCommand = ReactiveCommand.Create(() => Utils.OpenWebsite(new Uri($"https://www.wabbajack.org/#/modlists/info?machineURL={Metadata.Links.MachineURL}"))); + OpenWebsiteCommand = ReactiveCommand.Create(() => UIUtils.OpenWebsite(new Uri($"https://www.wabbajack.org/#/modlists/info?machineURL={Metadata.Links.MachineURL}"))); IsLoadingIdle = new Subject(); @@ -152,7 +160,7 @@ namespace Wabbajack return false; } // Return an updated check on exists - return Location.Exists; + return Location.FileExists(); } return exists; }) @@ -175,7 +183,7 @@ namespace Wabbajack } catch (Exception ex) { - Utils.Error(ex); + _logger.LogError(ex, "While opening modlist README"); } }); }) @@ -190,7 +198,7 @@ namespace Wabbajack { try { - return !IsDownloading && !(await metadata.NeedsDownload(Location)); + return !IsDownloading && !(await maintainer.HaveModList(metadata)); } catch (Exception) { @@ -200,7 +208,7 @@ namespace Wabbajack .ToGuiProperty(this, nameof(Exists)); var imageObs = Observable.Return(Metadata.Links.ImageUri) - .DownloadBitmapImage((ex) => Utils.Log($"Error downloading modlist image {Metadata.Title}")); + .DownloadBitmapImage((ex) => _logger.LogError("Error downloading modlist image {Title}", Metadata.Title)); _Image = imageObs .ToGuiProperty(this, nameof(Image)); @@ -227,8 +235,8 @@ namespace Wabbajack try { IsDownloading = true; - Utils.Log($"Starting Download of {Metadata.Links.MachineURL}"); - var downloader = DownloadDispatcher.ResolveArchive(Metadata.Links.Download); + _logger.LogInformation("Starting Download of {MachineUrl}", Metadata.Links.MachineURL); + var downloader = await DownloadDispatcher.ResolveArchive(Metadata.Links.Download); var result = await downloader.Download( new Archive(state: null!) { diff --git a/Wabbajack.RateLimiter/Job.cs b/Wabbajack.RateLimiter/Job.cs index bef581f9..55437a41 100644 --- a/Wabbajack.RateLimiter/Job.cs +++ b/Wabbajack.RateLimiter/Job.cs @@ -27,10 +27,14 @@ public class Job : IJob, IDisposable { await Resource.Report(this, processedSize, token); Current += processedSize; + OnUpdate?.Invoke(this, (Percent.FactoryPutInRange(Current, Size ?? 1), Current)); } public void ReportNoWait(int processedSize) { Resource.ReportNoWait(this, processedSize); + OnUpdate?.Invoke(this, (Percent.FactoryPutInRange(Current, Size ?? 1), Current)); } + + public event EventHandler<(Percent Progress, long Processed)> OnUpdate; } \ No newline at end of file diff --git a/Wabbajack.Services.OSIntegrated/ServiceExtensions.cs b/Wabbajack.Services.OSIntegrated/ServiceExtensions.cs index 6f3e03dc..c7a50f77 100644 --- a/Wabbajack.Services.OSIntegrated/ServiceExtensions.cs +++ b/Wabbajack.Services.OSIntegrated/ServiceExtensions.cs @@ -19,6 +19,7 @@ using Wabbajack.Networking.WabbajackClientApi; using Wabbajack.Paths; using Wabbajack.Paths.IO; using Wabbajack.RateLimiter; +using Wabbajack.Services.OSIntegrated.Services; using Wabbajack.Services.OSIntegrated.TokenProviders; using Wabbajack.VFS; @@ -99,6 +100,8 @@ public static class ServiceExtensions service.AddScoped(); service.AddSingleton(); + service.AddSingleton(); + // Networking service.AddSingleton(); service.AddAllSingleton(); diff --git a/Wabbajack.Services.OSIntegrated/Services/ModListDownloadMaintainer.cs b/Wabbajack.Services.OSIntegrated/Services/ModListDownloadMaintainer.cs new file mode 100644 index 00000000..9e84f2f2 --- /dev/null +++ b/Wabbajack.Services.OSIntegrated/Services/ModListDownloadMaintainer.cs @@ -0,0 +1,78 @@ +using System; +using System.Reactive.Subjects; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +using Wabbajack.Common; +using Wabbajack.Downloaders; +using Wabbajack.DTOs; +using Wabbajack.Paths; +using Wabbajack.Paths.IO; +using Wabbajack.RateLimiter; +using Wabbajack.VFS; + +namespace Wabbajack.Services.OSIntegrated.Services; + +public class ModListDownloadMaintainer +{ + private readonly ILogger _logger; + private readonly Configuration _configuration; + private readonly DownloadDispatcher _dispatcher; + private readonly FileHashCache _hashCache; + private readonly IResource _rateLimiter; + + public ModListDownloadMaintainer(ILogger logger, Configuration configuration, + DownloadDispatcher dispatcher, FileHashCache hashCache, IResource rateLimiter) + { + _logger = logger; + _configuration = configuration; + _dispatcher = dispatcher; + _hashCache = hashCache; + _rateLimiter = rateLimiter; + } + + public AbsolutePath ModListPath(ModlistMetadata metadata) + { + return _configuration.ModListsDownloadLocation.Combine(metadata.Links.MachineURL).WithExtension(Ext.Wabbajack); + } + + public async Task HaveModList(ModlistMetadata metadata, CancellationToken? token = null) + { + token ??= CancellationToken.None; + var path = ModListPath(metadata); + if (!path.FileExists()) return false; + + return await _hashCache.FileHashCachedAsync(path, token.Value) == metadata.DownloadMetadata!.Hash; + } + + public (IObservable Progress, Task Task) DownloadModlist(ModlistMetadata metadata, CancellationToken? token = null) + { + var path = ModListPath(metadata); + + token ??= CancellationToken.None; + + var progress = new Subject(); + progress.OnNext(Percent.Zero); + + var tsk = Task.Run(async () => + { + var job = await _rateLimiter.Begin($"Downloading {metadata.Title}", metadata.DownloadMetadata!.Size, token.Value); + + job.OnUpdate += (_, pr) => + { + progress.OnNext(pr.Progress); + }; + + var hash = await _dispatcher.Download(new Archive() + { + State = _dispatcher.Parse(new Uri(metadata.Links.Download))!, + Size = metadata.DownloadMetadata.Size, + Hash = metadata.DownloadMetadata.Hash + }, path, job, token.Value); + + _hashCache.FileHashWriteCache(path, hash); + }); + + return (progress, tsk); + } +} \ No newline at end of file