Пример #1
0
        /// <summary>
        /// Download content
        /// </summary>
        /// <param name="files">The files to download</param>
        public void Add(IEnumerable <UpdateFile> files)
        {
            var contentDownloader = new ContentDownloader();

            contentDownloader.OnDownloadProgress += ContentDownloader_OnDownloadProgress;

            var hashChecker = new ContentHash();

            hashChecker.OnHashingProgress += HashChecker_OnHashingProgress;

            var cancellationSource = new CancellationTokenSource();

            var progressData = new ContentOperationProgress();

            foreach (var file in files)
            {
                progressData.CurrentOperation = OperationType.DownloadFileStart;
                progressData.File             = file;
                Progress?.Invoke(this, progressData);

                if (Contains(file))
                {
                    progressData.CurrentOperation = OperationType.DownloadFileEnd;
                    Progress?.Invoke(this, progressData);
                }
                else
                {
                    // Create the directory structure where the file will be downloaded
                    var contentFilePath      = GetUpdateFilePath(file);
                    var contentFileDirectory = Path.GetDirectoryName(contentFilePath);
                    if (!Directory.Exists(contentFileDirectory))
                    {
                        Directory.CreateDirectory(contentFileDirectory);
                    }

                    // Download the file (or resume and interrupted download)
                    contentDownloader.DownloadToFile(GetUpdateFilePath(file), file, cancellationSource.Token);

                    progressData.CurrentOperation = OperationType.DownloadFileEnd;
                    Progress?.Invoke(this, progressData);

                    progressData.CurrentOperation = OperationType.HashFileStart;
                    Progress?.Invoke(this, progressData);

                    // Check the hash; must match the strongest hash specified in the update metadata
                    if (hashChecker.Check(file, contentFilePath))
                    {
                        var markerFile = File.Create(GetUpdateFileMarkerPath(file));
                        markerFile.Dispose();
                    }

                    progressData.CurrentOperation = OperationType.HashFileEnd;
                    Progress?.Invoke(this, progressData);
                }
            }
        }
        /// <summary>
        /// Downloads the specified URL to the destination file stream
        /// </summary>
        /// <param name="destination">The file stream to write content to</param>
        /// <param name="updateFile">The update to download</param>
        /// <param name="startOffset">Offset to resume download at</param>
        /// <param name="cancellationToken">Cancellation token</param>
        public void DownloadToStream(
            Stream destination,
            UpdateFile updateFile,
            long startOffset,
            CancellationToken cancellationToken)
        {
            var progress = new ContentOperationProgress()
            {
                File             = updateFile,
                Current          = startOffset,
                Maximum          = (long)updateFile.Size,
                CurrentOperation = OperationType.DownloadFileProgress
            };

            // Validate starting offset
            if (startOffset >= (long)updateFile.Size)
            {
                throw new Exception($"Start offset {startOffset} cannot be greater than expected file size {updateFile.Size}");
            }

            var url = updateFile.DownloadUrl;

            using (var client = new HttpClient())
            {
                var fileSizeOnServer = GetFileSizeOnServer(client, url, cancellationToken);

                // Make sure our size matches the server's size
                if (fileSizeOnServer != (long)updateFile.Size)
                {
                    throw new Exception($"File size mismatch. Expected {updateFile.Size}, server advertised {fileSizeOnServer}");
                }

                // Build the range request for the download
                using (var updateRequest = new HttpRequestMessage {
                    RequestUri = new Uri(url), Method = HttpMethod.Get
                })
                {
                    updateRequest.Headers.Range = new RangeHeaderValue((long)startOffset, (long)fileSizeOnServer - 1);

                    // Stream the file to disk
                    using (HttpResponseMessage response = client
                                                          .SendAsync(updateRequest, HttpCompletionOption.ResponseHeadersRead, cancellationToken)
                                                          .GetAwaiter()
                                                          .GetResult())
                    {
                        if (response.IsSuccessStatusCode)
                        {
                            using (Stream streamToReadFrom = response.Content.ReadAsStreamAsync().GetAwaiter().GetResult())
                            {
                                // Read in chunks while not at the end and cancellation was not requested
                                byte[] readBuffer     = new byte[2097152 * 5];
                                var    readBytesCount = streamToReadFrom.Read(readBuffer, 0, readBuffer.Length);
                                while (!cancellationToken.IsCancellationRequested && readBytesCount > 0)
                                {
                                    destination.Write(readBuffer, 0, readBytesCount);

                                    progress.Current += readBytesCount;
                                    OnDownloadProgress?.Invoke(this, progress);

                                    readBytesCount = streamToReadFrom.Read(readBuffer, 0, readBuffer.Length);
                                }
                            }
                        }
                        else
                        {
                            throw new Exception($"Failed to get content of update from {url}: {response.ReasonPhrase}");
                        }
                    }
                }
            }
        }
        /// <summary>
        /// Checks that the hash of a file matches the value specified in the update file metadata
        /// </summary>
        /// <param name="updateFile">The update file object that contains the expected checksums</param>
        /// <param name="filePath">The path to the file to checksum</param>
        /// <returns>The string representatin of the hash</returns>
        public bool Check(UpdateFile updateFile, string filePath)
        {
            byte[] readAheadBuffer, buffer;
            int    readAheadBytesRead, bytesRead;
            int    bufferSize = 512 * 1024;

            // Pick the stronges hash algorithm available
            HashAlgorithm    hashAlgorithm;
            UpdateFileDigest targetDigest;

            if ((targetDigest = updateFile.Digests.Find(d => d.Algorithm.Equals("SHA512"))) != null)
            {
                hashAlgorithm = new SHA512Managed();
            }
            else if ((targetDigest = updateFile.Digests.Find(d => d.Algorithm.Equals("SHA256"))) != null)
            {
                hashAlgorithm = new SHA256Managed();
            }
            else if ((targetDigest = updateFile.Digests.Find(d => d.Algorithm.Equals("SHA1"))) != null)
            {
                hashAlgorithm = new SHA1Managed();
            }
            else
            {
                throw new Exception($"No supported hashing algorithms found for update file {updateFile.FileName}");
            }

            readAheadBuffer = new byte[bufferSize];
            buffer          = new byte[bufferSize];

            // Hash the file contents
            using (var fileStream = File.Open(filePath, FileMode.Open, FileAccess.Read))
            {
                var progress = new ContentOperationProgress()
                {
                    File             = updateFile,
                    Current          = 0,
                    Maximum          = (long)updateFile.Size,
                    CurrentOperation = OperationType.HashFileProgress
                };

                readAheadBytesRead = fileStream.Read(readAheadBuffer, 0, readAheadBuffer.Length);

                do
                {
                    byte[] tempBuffer;
                    bytesRead       = readAheadBytesRead;
                    tempBuffer      = buffer;
                    buffer          = readAheadBuffer;
                    readAheadBuffer = tempBuffer;

                    readAheadBytesRead = fileStream.Read(readAheadBuffer, 0, readAheadBuffer.Length);

                    if (readAheadBytesRead == 0)
                    {
                        hashAlgorithm.TransformFinalBlock(buffer, 0, bytesRead);
                        progress.Current += bytesRead;
                    }
                    else
                    {
                        hashAlgorithm.TransformBlock(buffer, 0, bytesRead, buffer, 0);
                        progress.Current += bytesRead;
                    }

                    OnHashingProgress?.Invoke(this, progress);
                } while (readAheadBytesRead != 0);

                // Check that actual hash matches the expected value
                var actualHash   = hashAlgorithm.Hash;
                var expectedHash = Convert.FromBase64String(targetDigest.DigestBase64);

                return(actualHash.SequenceEqual(expectedHash));
            }
        }