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 YouTubeDownloader(), new WabbajackCDNDownloader(), 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) { var success = await Download(archive, destination); if (success) { 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; } var upgrade = (IUpgradingState)archive.State; Utils.Log($"Trying to find solution to broken download for {archive.Name}"); var result = await upgrade.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}"); var patchResult = await ClientAPI.GetModUpgrade(archive, result.Archive!); Utils.Log($"Downloading patch for {archive.Name}"); var tempFile = new TempFile(); using var response = await (new Common.Http.Client()).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 = 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; } 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; } } } }