private async Task<string> DownloadClientAsync([NotNull] CookieAwareWebClient client, [NotNull] FlexibleLoaderGetPreferredDestinationCallback getPreferredDestination, [CanBeNull] FlexibleLoaderReportDestinationCallback reportDestination, [CanBeNull] Func<bool> checkIfPaused, CancellationToken cancellation) { cancellation.Register(client.CancelAsync); var information = FlexibleLoaderMetaInformation.FromLoader(this); var destination = getPreferredDestination(Url, information); var filename = FileUtils.EnsureUnique(true, destination.Filename); reportDestination?.Invoke(filename); await client.DownloadFileTaskAsync(Url, filename).ConfigureAwait(false); return filename; }
public async Task <string> DownloadAsync(CookieAwareWebClient client, FlexibleLoaderGetPreferredDestinationCallback getPreferredDestination, FlexibleLoaderReportDestinationCallback reportDestination, Func <bool> checkIfPaused, IProgress <long> progress, CancellationToken cancellation) { try { // TODO: Resume download? var d = getPreferredDestination(_uri.OriginalString, FlexibleLoaderMetaInformation.FromLoader(this)); if (File.Exists(d.Filename)) { if (d.CanResumeDownload && new FileInfo(d.Filename).Length == TotalSize) { return(d.Filename); } File.Delete(d.Filename); } await _client.DownloadFileAsync(_uri, d.Filename, new Progress <double>(x => progress?.Report((long)((TotalSize ?? 0d) * x / 100))), cancellation); return(d.Filename); } catch (Exception e) when(IsBandwidthLimitExceeded(e)) { throw new InformativeException("Bandwidth limit exceeded", "That’s Mega.nz for you.", e); } catch (ApiException e) { WindowsHelper.ViewInBrowser(_uri); throw new InformativeException("Unsupported link", "Please download it manually.", e); } bool IsBandwidthLimitExceeded(Exception e) { if (e is AggregateException ae) { return(IsBandwidthLimitExceeded(ae.InnerException) || ae.InnerExceptions.Any(IsBandwidthLimitExceeded)); } return(e is HttpRequestException && e.Message.Contains(@"509")); } }
public static async Task <string> LoadAsyncTo(string argument, FlexibleLoaderGetPreferredDestinationCallback getPreferredDestination, [CanBeNull] FlexibleLoaderReportDestinationCallback reportDestination, Action <FlexibleLoaderMetaInformation> reportMetaInformation = null, Func <bool> checkIfPaused = null, IProgress <AsyncProgressEntry> progress = null, CancellationToken cancellation = default) { progress?.Report(AsyncProgressEntry.FromStringIndetermitate("Finding fitting loader…")); var loader = await CreateLoaderAsync(argument, cancellation) ?? throw new OperationCanceledException(); try { using (var order = KillerOrder.Create(new CookieAwareWebClient(), TimeSpan.FromMinutes(10))) { var client = order.Victim; if (_proxy != null) { client.Proxy = _proxy; } progress?.Report(AsyncProgressEntry.Indetermitate); cancellation.ThrowIfCancellationRequested(); cancellation.Register(client.CancelAsync); if (!await loader.PrepareAsync(client, cancellation)) { throw new InformativeException("Can’t load file", "Loader preparation failed."); } cancellation.ThrowIfCancellationRequested(); reportMetaInformation?.Invoke(FlexibleLoaderMetaInformation.FromLoader(loader)); var initialProgressCallback = true; var reportStopwatch = Stopwatch.StartNew(); var progressStopwatch = new AsyncProgressBytesStopwatch(); if (loader.UsesClientToDownload) { client.DownloadProgressChanged += (sender, args) => { if (initialProgressCallback) { reportMetaInformation?.Invoke(FlexibleLoaderMetaInformation.FromLoader(loader)); initialProgressCallback = false; } if (reportStopwatch.Elapsed.TotalMilliseconds < 20) { return; } order.Delay(); reportStopwatch.Restart(); progress?.Report(AsyncProgressEntry.CreateDownloading(args.BytesReceived, args.TotalBytesToReceive == -1 && loader.TotalSize.HasValue ? Math.Max(loader.TotalSize.Value, args.BytesReceived) : args.TotalBytesToReceive, progressStopwatch)); }; } var loaded = await loader.DownloadAsync(client, getPreferredDestination, reportDestination, checkIfPaused, loader.UsesClientToDownload?null : new Progress <long>(p => { if (initialProgressCallback) { reportMetaInformation?.Invoke(FlexibleLoaderMetaInformation.FromLoader(loader)); initialProgressCallback = false; } if (reportStopwatch.Elapsed.TotalMilliseconds < 20) { return; } order.Delay(); reportStopwatch.Restart(); progress?.Report(loader.TotalSize.HasValue ? AsyncProgressEntry.CreateDownloading(p, loader.TotalSize.Value, progressStopwatch) : new AsyncProgressEntry(string.Format(UiStrings.Progress_Downloading, p.ToReadableSize(1)), null)); }), cancellation); cancellation.ThrowIfCancellationRequested(); Logging.Write("Loaded: " + loaded); return(loaded); } } catch (Exception e) when(cancellation.IsCancellationRequested || e.IsCancelled()) { Logging.Warning("Cancelled"); throw new OperationCanceledException(); } catch (Exception e) { Logging.Warning(e); throw; } }
private async Task <string> DownloadResumeSupportAsync([NotNull] CookieAwareWebClient client, [NotNull] FlexibleLoaderGetPreferredDestinationCallback getPreferredDestination, [CanBeNull] FlexibleLoaderReportDestinationCallback reportDestination, [CanBeNull] Func <bool> checkIfPaused, IProgress <long> progress, CancellationToken cancellation) { // Common variables string filename = null, selectedDestination = null, actualFootprint = null; Stream remoteData = null; var resumeSupported = ResumeSupported; try { // Read resume-related data and remove it to avoid conflicts var resumeDestination = CacheStorage.Get <string>(_keyDestination); var resumePartiallyLoadedFilename = CacheStorage.Get <string>(_keyPartiallyLoadedFilename); var resumeLastWriteDate = CacheStorage.Get <DateTime?>(_keyLastWriteDate); var resumePreviousFootprint = CacheStorage.Get <string>(_keyFootprint); ClearResumeData(); // Collect known information for destination callback var information = FlexibleLoaderMetaInformation.FromLoader(this); // Opening stream to read… var headRequest = HeadRequestSupported && resumeDestination != null; using (headRequest ? client.SetMethod("HEAD") : null) { Logging.Warning($"Initial request: {(headRequest ? "HEAD" : "GET")}"); remoteData = await client.OpenReadTaskAsync(Url); } cancellation.ThrowIfCancellationRequested(); // Maybe we’ll be lucky enough to load the most accurate data if (client.ResponseHeaders != null) { if (long.TryParse(client.ResponseHeaders[HttpResponseHeader.ContentLength] ?? "", NumberStyles.Any, CultureInfo.InvariantCulture, out var length)) { TotalSize = information.TotalSize = length; } if (TryGetFileName(client.ResponseHeaders, out var fileName)) { FileName = information.FileName = fileName; } // For example, Google Drive responds with “none” and yet allows to download file partially, // so this header will only be checked if value is not defined. if (resumeSupported == null) { var accept = client.ResponseHeaders[HttpResponseHeader.AcceptRanges] ?? ""; if (accept.Contains("bytes")) { resumeSupported = true; } else if (accept.Contains("none")) { resumeSupported = false; } } client.LogResponseHeaders(); } // Was the file partially loaded before? var partiallyLoaded = ResumeSupported != false && resumePartiallyLoadedFilename != null ? new FileInfo(FileUtils.EnsureFilenameIsValid(resumePartiallyLoadedFilename)) : null; if (partiallyLoaded != null) { Logging.Warning("Not finished: " + partiallyLoaded); } // Does it still exist if (partiallyLoaded?.Exists != true) { Logging.Warning($"Partially downloaded file “{partiallyLoaded?.FullName}” does not exist"); partiallyLoaded = null; } // If so, wasn’t it changed since the last time? if (partiallyLoaded?.LastWriteTime > resumeLastWriteDate + TimeSpan.FromMinutes(5)) { Logging.Warning($"Partially downloaded file is newer that it should be: {partiallyLoaded.LastWriteTime}, expected: {resumeLastWriteDate}"); partiallyLoaded = null; } // Looks like file is partially downloaded, but let’s ensure link still leads to the same content actualFootprint = GetFootprint(information, client.ResponseHeaders); if (partiallyLoaded != null && resumePreviousFootprint != actualFootprint) { Logging.Warning($"Footprints don’t match: {resumePreviousFootprint}≠{actualFootprint}"); partiallyLoaded = null; } // Let’s check where to load data, which is potentially the most actual data at this point var destination = getPreferredDestination(Url, information); selectedDestination = destination.Filename; if (partiallyLoaded != null && (!destination.CanResumeDownload || !FileUtils.ArePathsEqual(selectedDestination, resumeDestination))) { Logging.Warning($"Different destination chosen: {selectedDestination} (before: {resumeDestination})"); partiallyLoaded = null; } // TODO: Check that header? // Where to write? // ReSharper disable once MergeConditionalExpression filename = partiallyLoaded != null ? partiallyLoaded.FullName : FileUtils.EnsureUnique(true, destination.Filename); reportDestination?.Invoke(filename); // Set cancellation token cancellation.Register(o => client.CancelAsync(), null); // Open write stream if (partiallyLoaded != null) { var rangeFrom = partiallyLoaded.Length; using (client.SetRange(new Tuple <long, long>(rangeFrom, -1))) { Logging.Warning($"Trying to resume download from {rangeFrom} bytes…"); remoteData.Dispose(); remoteData = await client.OpenReadTaskAsync(Url); cancellation.ThrowIfCancellationRequested(); client.LogResponseHeaders(); // It’s unknown if resume is supported or not at this point if (resumeSupported == null) { var bytes = new byte[16]; var firstBytes = await remoteData.ReadAsync(bytes, 0, bytes.Length); cancellation.ThrowIfCancellationRequested(); if (CouldBeBeginningOfAFile(bytes)) { using (var file = File.Create(filename)) { Logging.Warning("File beginning found, restart download"); file.Write(bytes, 0, firstBytes); await CopyToAsync(remoteData, file, checkIfPaused, progress, cancellation); cancellation.ThrowIfCancellationRequested(); } Logging.Write("Download finished"); return(filename); } rangeFrom += firstBytes; } using (var file = new FileStream(filename, FileMode.Append, FileAccess.Write)) { await CopyToAsync(remoteData, file, checkIfPaused, new Progress <long>(v => { progress?.Report(v + rangeFrom); }), cancellation); cancellation.ThrowIfCancellationRequested(); } } } else { if (headRequest) { Logging.Warning("Re-open request to be GET"); remoteData.Dispose(); remoteData = await client.OpenReadTaskAsync(Url); } using (var file = File.Create(filename)) { Logging.Debug("Downloading the whole file…"); await CopyToAsync(remoteData, file, checkIfPaused, progress, cancellation); cancellation.ThrowIfCancellationRequested(); } } Logging.Write("Download finished"); return(filename); } catch (Exception e) when(e is WebException || e.IsCancelled()) { Logging.Write("Download is interrupted! Saving details to resume later…"); var download = filename == null ? null : new FileInfo(filename); if (download?.Exists == true && filename.Length > 0) { CacheStorage.Set(_keyDestination, selectedDestination); CacheStorage.Set(_keyPartiallyLoadedFilename, filename); CacheStorage.Set(_keyFootprint, actualFootprint); CacheStorage.Set(_keyLastWriteDate, download.LastWriteTime); } else { ClearResumeData(); } throw; } finally { remoteData?.Dispose(); } }