using System; using System.Collections.Generic; using System.Linq; 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 AFKModsDownloader(), new TESAllianceDownloader(), new YouTubeDownloader(), new HTTPDownloader(), new ManualDownloader(), }; public static readonly List Inferencers = new List() { new BethesdaNetInferencer(), new YoutubeInferencer() }; 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)]; inst.Prepare(); return inst; } public static async Task ResolveArchive(dynamic ini) { var states = await Task.WhenAll(Downloaders.Select(d => (Task)d.GetDownloaderState(ini))); 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 void PrepareAll(IEnumerable states) { states.Select(s => s.GetDownloader().GetType()) .Distinct() .Do(t => Downloaders.First(d => d.GetType() == t).Prepare()); } public static async Task DownloadWithPossibleUpgrade(Archive archive, string destination) { var success = await Download(archive, destination); if (success) { await destination.FileHashCachedAsync(); return true; } Utils.Log($"Download failed, looking for upgrade"); var upgrade = await ClientAPI.GetModUpgrade(archive.Hash); if (upgrade == null) { Utils.Log($"No upgrade found for {archive.Hash}"); return false; } Utils.Log($"Upgrading {archive.Hash}"); var upgradePath = Path.Combine(Path.GetDirectoryName(destination), "_Upgrade_" + archive.Name); var upgradeResult = await Download(upgrade, upgradePath); if (!upgradeResult) return false; var patchName = $"{archive.Hash.FromBase64().ToHex()}_{upgrade.Hash.FromBase64().ToHex()}"; var patchPath = Path.Combine(Path.GetDirectoryName(destination), "_Patch_" + patchName); var patchState = new Archive { Name = patchName, State = new HTTPDownloader.State { Url = $"https://wabbajackcdn.b-cdn.net/updates/{patchName}" } }; var patchResult = await Download(patchState, patchPath); if (!patchResult) return false; Utils.Status($"Applying Upgrade to {archive.Hash}"); await using (var patchStream = File.OpenRead(patchPath)) await using (var srcStream = File.OpenRead(upgradePath)) await using (var destStream = File.Create(destination)) { OctoDiff.Apply(srcStream, patchStream, destStream); } await destination.FileHashCachedAsync(); return true; } private static async Task Download(Archive archive, string destination) { try { var result = await archive.State.Download(archive, destination); if (!result) return false; if (archive.Hash == null) return true; var hash = await destination.FileHashCachedAsync(); if (hash == archive.Hash) return true; Utils.Log($"Hashed download is incorrect"); return false; } catch (Exception ex) { return false; } } } }