/// <summary> /// The actual functionality to download with optional hash verification /// subclasses that wish to return the contents of the downloaded file /// or do something else with it can override this instead of RunWithReturn. /// </summary> /// <param name="success"></param> /// <returns></returns> protected virtual NPath RunDownload(bool success) { Exception exception = null; var attempts = 0; bool result = false; var partialFile = TargetDirectory.Combine(Filename + ".partial"); TargetDirectory.EnsureDirectoryExists(); do { exception = null; if (Token.IsCancellationRequested) { break; } try { Logger.Trace($"Download of {Url} to {Destination} Attempt {attempts + 1} of {RetryCount + 1}"); using (var destinationStream = fileSystem.OpenWrite(partialFile, FileMode.Append)) { result = Downloader.Download(Logger, Url, destinationStream, (value, total) => { UpdateProgress(value, total); return(!Token.IsCancellationRequested); }); } if (result) { partialFile.Move(Destination); } } catch (Exception ex) { exception = ex; result = false; } } while (!result && attempts++ < RetryCount); if (!result) { Token.ThrowIfCancellationRequested(); throw new DownloadException("Error downloading file", exception); } return(Destination); }
async Task DownloadAsync(HttpClient httpClient, CancellationToken cancellationToken) { var response = (await httpClient.GetAsync( SourceUri, HttpCompletionOption.ResponseHeadersRead, cancellationToken)).EnsureSuccessStatusCode(); cancellationToken.ThrowIfCancellationRequested(); ActualSourceUri = response.Content.Headers.ContentLocation ?? SourceUri; TotalBytes = (ulong)response.Content.Headers.ContentLength.GetValueOrDefault(); var _targetFile = TargetDirectory.Combine(Path.GetFileName(ActualSourceUri.LocalPath)); using (var hash = MD5.Create()) using (var sourceStream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false)) using (var outputStream = CreateOutputStream(ref _targetFile)) { cancellationToken.ThrowIfCancellationRequested(); TargetFile = _targetFile; var buffer = new byte [bufferSize]; int read; while ((read = await sourceStream .ReadAsync(buffer, 0, buffer.Length, cancellationToken) .ConfigureAwait(false)) != 0) { cancellationToken.ThrowIfCancellationRequested(); await outputStream .WriteAsync(buffer, 0, read, cancellationToken) .ConfigureAwait(false); hash.TransformBlock(buffer, 0, read, buffer, 0); ProgressBytes += (ulong)read; Progress = TotalBytes == 0 ? 0.0 : ProgressBytes / (double)TotalBytes; } hash.TransformFinalBlock(buffer, 0, 0); await outputStream .FlushAsync(cancellationToken) .ConfigureAwait(false); var fileLength = new FileInfo(TargetFile).Length; if (fileLength != (long)TotalBytes) { throw new DamagedDownloadException( $"expected {TotalBytes} bytes for {TargetFile} " + $"but size on disk is {fileLength} bytes"); } if (Md5Hash != null) { var actualHash = hash.Hash.ToHexString(); if (!String.Equals(Md5Hash, actualHash, StringComparison.OrdinalIgnoreCase)) { throw new DamagedDownloadException( $"checksum ({actualHash}) for {TargetFile} does " + $"not match expected checksum {Md5Hash}"); } } } }