/// <summary>
        /// Starts downloading an update.
        /// </summary>
        /// <param name="buildPath">The local build path.</param>
        /// <param name="downloadDirectory">The download directory.</param>
        /// <param name="filesToDownload">A list of files to download.</param>
        /// <param name="updateMirror">The update mirror to use.</param>
        public UpdateDownloadResult DownloadUpdates(string buildPath, string downloadDirectory,
                                                    List <RemoteFileInfo> filesToDownload, UpdateMirror updateMirror)
        {
            if (filesToDownload.Count == 0)
            {
                return(new UpdateDownloadResult(UpdateDownloadResultType.COMPLETED));
            }

            cancelled = false;

            this.filesToDownload   = filesToDownload;
            this.downloadDirectory = downloadDirectory;
            this.updateMirror      = updateMirror;

            foreach (var fileInfo in filesToDownload)
            {
                totalUpdateSize += fileInfo.GetDownloadSize();
            }

            verifierWaitHandle = new EventWaitHandle(false, EventResetMode.AutoReset);

            verifier = new Verifier(downloadDirectory);
            verifier.VerificationFailed += Verifier_VerificationFailed;
            verifier.Completed          += Verifier_Completed;

            webClient = new WebClient()
            {
                CachePolicy = new RequestCachePolicy(RequestCacheLevel.NoCacheNoStore),
                Encoding    = Encoding.GetEncoding(1252)
            };
            webClient.DownloadProgressChanged += WebClient_DownloadProgressChanged;

            fileIndexesToDownload = new List <int>();
            for (int i = 0; i < filesToDownload.Count; i++)
            {
                fileIndexesToDownload.Add(i);
            }
            fileIndexesToDownload.Reverse();
            fileIndexErrorCounts = new int[filesToDownload.Count];

            while (true)
            {
                int fileIndex;

                lock (fileListLocker)
                {
                    // If we have downloaded all files and the verifier has
                    // succesfully verified them, the list of file indexes to
                    // download has no entries -> we don't have any work left
                    if (fileIndexesToDownload.Count == 0)
                    {
                        break;
                    }

                    fileIndex = fileIndexesToDownload[fileIndexesToDownload.Count - 1];
                    currentlyDownloadedFile = filesToDownload[fileIndex];

                    if (fileIndexErrorCounts[fileIndex] > MAX_ERROR_COUNT)
                    {
                        return(CleanUpAndReturnResult(UpdateDownloadResultType.FAILED,
                                                      "Failed to download file " + currentlyDownloadedFile.FilePath));
                    }
                }

                Task downloadTask = null;

                try
                {
                    // By both checking for cancellation and starting a new download
                    // task in the same lock block that's also used in Cancel() we're
                    // making sure that a call to Cancel() will take effect right
                    // away - either we're executing the code above meaning we'll
                    // check for cancellation soon, or we're waiting for a download to
                    // finish - and Cancel() cancels the download operation in case
                    // one is going on
                    lock (cancellationLocker)
                    {
                        if (cancelled)
                        {
                            return(CleanUpAndReturnResult(UpdateDownloadResultType.CANCELLED));
                        }

                        string fileSavePath = downloadDirectory + currentlyDownloadedFile.GetFilePathWithCompression();

                        Directory.CreateDirectory(Path.GetDirectoryName(fileSavePath));

                        downloadTask = webClient.DownloadFileTaskAsync(
                            new Uri(updateMirror.URL + currentlyDownloadedFile.GetFilePathWithCompression().Replace('\\', '/')),
                            fileSavePath);
                    }

                    downloadTask.Wait();
                    downloadTask.Dispose();

                    lock (downloadedBytesLocker)
                        downloadedBytes += currentlyDownloadedFile.GetDownloadSize();

                    verifier.VerifyFile(new IndexedRemoteFileInfo(currentlyDownloadedFile, fileIndex));
                }
                catch (AggregateException ex)
                {
                    downloadTask.Dispose();

                    if (cancelled)
                    {
                        return(CleanUpAndReturnResult(UpdateDownloadResultType.CANCELLED));
                    }

                    UpdaterLogger.Log("Exception while downloading file " +
                                      currentlyDownloadedFile.FilePath);

                    LogInnerExceptionRecursive(ex);

                    lock (fileListLocker)
                    {
                        fileIndexErrorCounts[fileIndex]++;
                    }

                    continue;
                }

                bool waitingForVerifier = false;

                lock (fileListLocker)
                {
                    // Remove the downloaded file from the download queue
                    fileIndexesToDownload.Remove(fileIndex);

                    waitingForVerifier = fileIndexesToDownload.Count == 0;
                }

                if (waitingForVerifier)
                {
                    // We have downloaded all the files, wait for the verifier
                    // to finish verifying them
                    verifier.SetQueueReady();
                    verifierWaitHandle.WaitOne();
                }
            }

            return(CleanUpAndReturnResult(UpdateDownloadResultType.COMPLETED));
        }
        /// <summary>
        /// Raises an event for download progress.
        /// </summary>
        private void WebClient_DownloadProgressChanged(object sender, DownloadProgressChangedEventArgs e)
        {
            DownloadProgressEventArgs args = null;

            lock (downloadedBytesLocker)
            {
                args = new DownloadProgressEventArgs(downloadedBytes + e.BytesReceived,
                                                     totalUpdateSize, e.BytesReceived, currentlyDownloadedFile.GetDownloadSize(),
                                                     currentlyDownloadedFile.FilePath);
            }

            DownloadProgress?.Invoke(this, args);
        }