using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Net.Http; using System.Threading.Tasks; using Alphaleonis.Win32.Filesystem; using Wabbajack.Common; using Wabbajack.Lib.Downloaders.UrlDownloaders; namespace Wabbajack.Lib.Downloaders { public static class DownloadDispatcher { public static readonly List Downloaders = new List() { new GameFileSourceDownloader(), new MegaDownloader(), new DropboxDownloader(), new GoogleDriveDownloader(), new ModDBDownloader(), new NexusDownloader(), new MediaFireDownloader(), new LoversLabDownloader(), new VectorPlexusDownloader(), new DeadlyStreamDownloader(), new BethesdaNetDownloader(), new TESAllianceDownloader(), new TESAllDownloader(), new YouTubeDownloader(), new WabbajackCDNDownloader(), new YandexDownloader(), new HTTPDownloader(), new ManualDownloader(), }; public static readonly List Inferencers = new List() { new BethesdaNetInferencer(), new YoutubeInferencer(), new WabbajackCDNInfluencer() }; private static readonly Dictionary IndexedDownloaders; static DownloadDispatcher() { IndexedDownloaders = Downloaders.ToDictionary(d => d.GetType()); } public static async Task Infer(Uri uri) { foreach (var inf in Inferencers) { var state = await inf.Infer(uri); if (state != null) return state; } return null; } public static T GetInstance() where T : IDownloader { var inst = (T)IndexedDownloaders[typeof(T)]; return inst; } public static async Task ResolveArchive(dynamic ini, bool quickMode = false) { var states = await Task.WhenAll(Downloaders.Select(d => (Task)d.GetDownloaderState(ini, quickMode))); return states.FirstOrDefault(result => result != null); } /// /// Reduced version of Resolve archive that requires less information, but only works /// with a single URL string /// /// /// public static AbstractDownloadState? ResolveArchive(string url) { return Downloaders.OfType().Select(d => d.GetDownloaderState(url)).FirstOrDefault(result => result != null); } public static async Task PrepareAll(IEnumerable states) { await Task.WhenAll(states.Select(s => s.GetDownloader().GetType()) .Distinct() .Select(t => Downloaders.First(d => d.GetType() == t).Prepare())); } public static async Task DownloadWithPossibleUpgrade(Archive archive, AbsolutePath destination) { if (await Download(archive, destination)) { await destination.FileHashCachedAsync(); return true; } if (await DownloadFromMirror(archive, destination)) { await destination.FileHashCachedAsync(); return true; } if (!(archive.State is IUpgradingState)) { Utils.Log($"Download failed for {archive.Name} and no upgrade from this download source is possible"); return false; } Utils.Log($"Trying to find solution to broken download for {archive.Name}"); var result = await FindUpgrade(archive); if (result == default) { Utils.Log( $"No solution for broken download {archive.Name} {archive.State.PrimaryKeyString} could be found"); return false; } Utils.Log($"Looking for patch for {archive.Name} ({(long)archive.Hash} {archive.Hash.ToHex()} -> {(long)result.Archive!.Hash} {result.Archive!.Hash.ToHex()})"); var patchResult = await ClientAPI.GetModUpgrade(archive, result.Archive!); Utils.Log($"Downloading patch for {archive.Name} from {patchResult}"); var tempFile = new TempFile(); using var response = await (await ClientAPI.GetClient()).GetAsync(patchResult); await tempFile.Path.WriteAllAsync(await response.Content.ReadAsStreamAsync()); response.Dispose(); Utils.Log($"Applying patch to {archive.Name}"); await using(var src = await result.NewFile.Path.OpenShared()) await using (var final = await destination.Create()) { Utils.ApplyPatch(src, () => tempFile.Path.OpenShared().Result, final); } var hash = await destination.FileHashCachedAsync(); if (hash != archive.Hash && archive.Hash != default) { Utils.Log("Archive hash didn't match after patching"); return false; } return true; } public static async Task<(Archive? Archive, TempFile NewFile)> FindUpgrade(Archive a, Func>? downloadResolver = null) { downloadResolver ??= async a => default; return await a.State.FindUpgrade(a, downloadResolver); } private static async Task DownloadFromMirror(Archive archive, AbsolutePath destination) { try { var url = await ClientAPI.GetMirrorUrl(archive.Hash); if (url == null) return false; var newArchive = new Archive( new WabbajackCDNDownloader.State(url)) { Hash = archive.Hash, Size = archive.Size, Name = archive.Name }; return await Download(newArchive, destination); } catch (Exception ex) { return false; } } private static async Task Download(Archive archive, AbsolutePath destination) { try { var result = await archive.State.Download(archive, destination); if (!result) return false; if (!archive.Hash.IsValid) return true; var hash = await destination.FileHashCachedAsync(); if (hash == archive.Hash) return true; Utils.Log($"Hashed download is incorrect"); return false; } catch (Exception) { return false; } } } }