/// <summary>
        /// Reads update mirrors from an INI section.
        /// </summary>
        /// <param name="section">The INI section.</param>
        public void ParseUpdateMirrors(IniSection section)
        {
            if (section == null)
            {
                throw new ArgumentNullException("section");
            }

            var keys = section.GetKeys();

            foreach (string key in keys)
            {
                string value = section.GetStringValue(key, string.Empty);

                updateMirrors.Add(UpdateMirror.FromString(value));
            }
        }
        private void PerformUpdateInternal()
        {
            UpdaterLogger.Log("Performing update.");

            CreateTemporaryDirectory();

            UpdateMirror updateMirror = updateMirrors[lastUpdateMirrorId];

            char dsc = Path.DirectorySeparatorChar;

            string buildPath         = localBuildInfo.BuildPath;
            string downloadDirectory = buildPath + TEMPORARY_UPDATER_DIRECTORY + dsc;

            List <RemoteFileInfo> filesToDownload = GatherFilesToDownload(buildPath, downloadDirectory);

            CleanUpDownloadDirectory(filesToDownload, downloadDirectory);

            UpdaterLogger.Log("Creating downloader.");

            UpdateDownloader downloader = new UpdateDownloader();

            downloader.DownloadProgress += Downloader_DownloadProgress;
            UpdateDownloadResult result = downloader.DownloadUpdates(buildPath, downloadDirectory, filesToDownload, updateMirror);

            downloader.DownloadProgress -= Downloader_DownloadProgress;

            lock (locker)
            {
                updateInProgress = false;
            }

            switch (result.UpdateDownloadResultState)
            {
            case UpdateDownloadResultType.CANCELLED:
                UpdateCancelled?.Invoke(this, EventArgs.Empty);
                return;

            case UpdateDownloadResultType.COMPLETED:
                // If a new second-stage updater was downloaded, update it
                // first before launching it

                string originalSecondStageUpdaterPath = localBuildInfo.BuildPath + SecondStageUpdaterPath;

                string updatedSecondStageUpdaterPath = localBuildInfo.BuildPath +
                                                       TEMPORARY_UPDATER_DIRECTORY + dsc +
                                                       SecondStageUpdaterPath;

                if (File.Exists(updatedSecondStageUpdaterPath))
                {
                    File.Delete(originalSecondStageUpdaterPath);
                    File.Move(updatedSecondStageUpdaterPath, originalSecondStageUpdaterPath);
                }

                // Also update the second-stage updater's config file

                string originalSecondStageConfigPath = Path.GetDirectoryName(originalSecondStageUpdaterPath)
                                                       + dsc + SECOND_STAGE_UPDATER_CONFIGURATION_FILE;

                string updatedSecondStageConfigPath = Path.GetDirectoryName(updatedSecondStageUpdaterPath)
                                                      + dsc + SECOND_STAGE_UPDATER_CONFIGURATION_FILE;

                if (File.Exists(updatedSecondStageConfigPath))
                {
                    File.Delete(originalSecondStageConfigPath);
                    File.Move(updatedSecondStageConfigPath, originalSecondStageConfigPath);
                }

                // Generate local build information file
                LocalBuildInfo newBuildInfo = LocalBuildInfoFromRemoteBuildInfo();
                newBuildInfo.Write(localBuildInfo.BuildPath + TEMPORARY_UPDATER_DIRECTORY + dsc + LOCAL_BUILD_INFO_FILE);

                Process.Start(originalSecondStageUpdaterPath);

                // No null checking necessary here, it's actually better to
                // crash the application in case this is not subscribed to
                DownloadCompleted.Invoke(this, EventArgs.Empty);
                return;

            case UpdateDownloadResultType.FAILED:
                UpdateFailed?.Invoke(this, new UpdateFailureEventArgs(result.ErrorDescription));
                return;
            }
        }
        /// <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));
        }