using System; using System.Reactive.Subjects; using System.Text.Json; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Wabbajack.Common; using Wabbajack.Downloaders; using Wabbajack.DTOs; using Wabbajack.DTOs.JsonConverters; 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; private int _downloadingCount; private readonly DTOSerializer _dtos; public ModListDownloadMaintainer(ILogger logger, Configuration configuration, DownloadDispatcher dispatcher, FileHashCache hashCache, DTOSerializer dtos, IResource rateLimiter) { _logger = logger; _configuration = configuration; _dispatcher = dispatcher; _hashCache = hashCache; _rateLimiter = rateLimiter; _downloadingCount = 0; _dtos = dtos; } public AbsolutePath ModListPath(ModlistMetadata metadata) { return _configuration.ModListsDownloadLocation.Combine(metadata.NamespacedName.Replace("/", "_@@_")).WithExtension(Ext.Wabbajack); } public async Task HaveModList(ModlistMetadata metadata, CancellationToken? token = null) { token ??= CancellationToken.None; var path = ModListPath(metadata); if (!path.FileExists()) return false; if (await _hashCache.TryGetHashCache(path) == metadata.DownloadMetadata!.Hash) return true; if (_downloadingCount > 0) return false; return await _hashCache.FileHashCachedAsync(path, token.Value) == metadata.DownloadMetadata!.Hash; } public (IObservable Progress, Task Task) DownloadModlist(ModlistMetadata metadata, CancellationToken token) { var path = ModListPath(metadata); var progress = new Subject(); progress.OnNext(Percent.Zero); var tsk = Task.Run(async () => { try { Interlocked.Increment(ref _downloadingCount); using var job = await _rateLimiter.Begin($"Downloading {metadata.Title}", metadata.DownloadMetadata!.Size, token); 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); if (token.IsCancellationRequested) { return; } await _hashCache.FileHashWriteCache(path, hash); await path.WithExtension(Ext.MetaData).WriteAllTextAsync(JsonSerializer.Serialize(metadata), token); } finally { progress.OnCompleted(); Interlocked.Decrement(ref _downloadingCount); } }, token); return (progress, tsk); } }