private void LogInnerExceptionRecursive(Exception ex)
 {
     if (ex.InnerException != null)
     {
         UpdaterLogger.Log("Message: " + ex.InnerException.Message);
         LogInnerExceptionRecursive(ex.InnerException);
     }
 }
 /// <summary>
 /// Reads local build information.
 /// </summary>
 public void ReadLocalBuildInfo()
 {
     try
     {
         if (File.Exists(localBuildInfo.BuildPath + LOCAL_BUILD_INFO_FILE))
         {
             localBuildInfo.Parse(localBuildInfo.BuildPath + LOCAL_BUILD_INFO_FILE);
         }
     }
     catch (ParseException ex)
     {
         UpdaterLogger.Log("Failed to parse local build information. Message: " + ex.Message);
     }
 }
        /// <summary>
        /// Gathers a list of files to download for the update.
        /// </summary>
        private List <RemoteFileInfo> GatherFilesToDownload(string buildPath,
                                                            string downloadDirectory)
        {
            List <RemoteFileInfo> filesToDownload = new List <RemoteFileInfo>();

            // This could be multithreaded later on for faster processing,
            // calculating the SHA1 hashes can take a lot of time

            for (int i = 0; i < remoteBuildInfo.FileInfos.Count; i++)
            {
                var remoteFileInfo = remoteBuildInfo.FileInfos[i];

                UpdateProgressChanged?.Invoke(this, new UpdateProgressEventArgs(
                                                  UpdateProgressState.PREPARING, 0,
                                                  (int)(i / (double)remoteBuildInfo.FileInfos.Count * 100.0), string.Empty));

                if (!File.Exists(buildPath + remoteFileInfo.FilePath))
                {
                    if (!File.Exists(downloadDirectory + remoteFileInfo.FilePath))
                    {
                        UpdaterLogger.Log("File " + remoteFileInfo.FilePath + " doesn't exist, adding it to the download queue.");
                        filesToDownload.Add(remoteFileInfo);
                    }
                    else if (!HashHelper.FileHashMatches(downloadDirectory + remoteFileInfo.FilePath,
                                                         remoteFileInfo.UncompressedHash))
                    {
                        UpdaterLogger.Log("File " + remoteFileInfo.FilePath + " exists in the " +
                                          "temporary directory, but it's different from the remote version. Adding it to the download queue.");
                        filesToDownload.Add(remoteFileInfo);
                    }
                }
                else
                {
                    if (!HashHelper.FileHashMatches(buildPath + remoteFileInfo.FilePath, remoteFileInfo.UncompressedHash))
                    {
                        UpdaterLogger.Log("File " + remoteFileInfo.FilePath + " is different from the " +
                                          "remote version, adding it to the download queue.");
                        filesToDownload.Add(remoteFileInfo);
                    }
                    else
                    {
                        UpdaterLogger.Log("File " + remoteFileInfo.FilePath + " is up to date.");
                    }
                }
            }

            return(filesToDownload);
        }
        /// <summary>
        /// Cleans up the temporary download directory. Deletes files that don't
        /// exist in the download queue.
        /// </summary>
        /// <param name="filesToDownload">A list of files to download.</param>
        private void CleanUpDownloadDirectory(List <RemoteFileInfo> filesToDownload, string downloadDirectory)
        {
            string[] files = Directory.GetFiles(downloadDirectory);

            foreach (string filePath in files)
            {
                // Remove the download directory from the file path
                string subPath = filePath.Substring(downloadDirectory.Length);

                if (filesToDownload.Find(fi => fi.GetFilePathWithCompression() == subPath) == null)
                {
                    UpdaterLogger.Log("Deleting file " + subPath + " from the download " +
                                      "directory as part of the clean-up process.");
                    File.Delete(filePath);
                }
            }
        }
        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;
            }
        }
        private void CheckForUpdatesInternal()
        {
            UpdaterLogger.Log("Checking for updates.");

            updateMirrors = updateMirrors.OrderBy(um => um.Rating).ToList();

            using (WebClient webClient = CreateWebClient())
            {
                webClient.CachePolicy = new RequestCachePolicy(RequestCacheLevel.NoCacheNoStore);
                webClient.Encoding    = Encoding.GetEncoding(1252);

                CreateTemporaryDirectory();

                int i = 0;

                UpdateMirror updateMirror;

                string downloadDirectory = localBuildInfo.BuildPath + TEMPORARY_UPDATER_DIRECTORY + Path.DirectorySeparatorChar;

                while (i < updateMirrors.Count)
                {
                    updateMirror = updateMirrors[i];
                    UpdaterLogger.Log("Attempting to download version information from " +
                                      updateMirror.UIName + " (" + updateMirror.URL + ")");

                    try
                    {
                        webClient.DownloadFile(updateMirror.URL + REMOTE_BUILD_INFO_FILE, downloadDirectory + REMOTE_BUILD_INFO_FILE);

                        UpdaterLogger.Log("Version information downloaded, proceeding to parsing it.");

                        remoteBuildInfo = new RemoteBuildInfo();
                        remoteBuildInfo.Parse(downloadDirectory + REMOTE_BUILD_INFO_FILE);

                        lastUpdateMirrorId = i;

                        lock (locker)
                        {
                            updateCheckInProgress = false;
                        }

                        if (remoteBuildInfo.ProductVersionInfo.VersionNumber == localBuildInfo.ProductVersionInfo.VersionNumber)
                        {
                            BuildState = BuildState.UPTODATE;
                            BuildUpToDate?.Invoke(this, EventArgs.Empty);
                            return;
                        }
                        else
                        {
                            BuildState = BuildState.OUTDATED;
                            BuildOutdated?.Invoke(this, new BuildOutdatedEventArgs(
                                                      remoteBuildInfo.ProductVersionInfo.DisplayString, GetUpdateSize()));
                            return;
                        }
                    }
                    catch (WebException ex)
                    {
                        UpdaterLogger.Log("WebException when downloading version information: " + ex.Message);
                        i++;
                    }
                    catch (ParseException ex)
                    {
                        UpdaterLogger.Log("ParseException when parsing version information: " + ex.Message);
                        i++;
                    }
                    catch (FormatException ex)
                    {
                        UpdaterLogger.Log("FormatException when parsing version information: " + ex.Message);
                        i++;
                    }
                }
            }

            UpdaterLogger.Log("Failed to download version information from all update mirrors. Aborting.");

            lock (locker)
            {
                updateCheckInProgress = false;
            }

            UpdateCheckFailed?.Invoke(this, EventArgs.Empty);
        }
Example #7
0
        private void VerifyFiles()
        {
            while (true)
            {
                IndexedRemoteFileInfo indexedFileInfo;

                if (stopped)
                {
                    break;
                }

                lock (locker)
                {
                    indexedFileInfo = filesToCheck[0];
                }

                RemoteFileInfo fileInfo = indexedFileInfo.FileInfo;

                bool checkFileHash = true;

                if (fileInfo.Compressed)
                {
                    try
                    {
                        CompressionHelper.DecompressFile(downloadDirectory + fileInfo.GetFilePathWithCompression(),
                                                         downloadDirectory + fileInfo.FilePath);
                        File.Delete(downloadDirectory + fileInfo.GetFilePathWithCompression());
                    }
                    catch (Exception ex)
                    {
                        // The SevenZip compressor doesn't define what exceptions
                        // it might throw, so we'll just catch them all

                        UpdaterLogger.Log("Decompressing file " + fileInfo.FilePath + " failed! Message: " + ex.Message);
                        VerificationFailed?.Invoke(this, new IndexEventArgs(indexedFileInfo.Index));
                        queueReady    = false;
                        checkFileHash = false;
                    }
                }

                if (checkFileHash)
                {
                    if (!HashHelper.FileHashMatches(downloadDirectory + fileInfo.FilePath, fileInfo.UncompressedHash))
                    {
                        UpdaterLogger.Log("File " + fileInfo.FilePath + " failed verification!");
                        VerificationFailed?.Invoke(this, new IndexEventArgs(indexedFileInfo.Index));
                        queueReady = false;
                    }
                    else
                    {
                        UpdaterLogger.Log("File " + fileInfo.FilePath + " passed verification.");
                    }
                }

                bool waitingForWork = false;

                lock (locker)
                {
                    filesToCheck.RemoveAt(0);

                    waitingForWork = filesToCheck.Count == 0;

                    waitHandle.Reset();

                    if (queueReady && waitingForWork)
                    {
                        Completed?.Invoke(this, EventArgs.Empty);
                        break;
                    }

                    //if (stopped)
                    //{
                    //    filesToCheck.Clear();
                    //    break;
                    //}
                }

                if (waitingForWork)
                {
                    waitHandle.WaitOne();
                }
            }

            lock (locker)
            {
                waitHandle.Dispose();
                waitHandle = null;
            }

            // We could also dispose of verifierTask, but it sounds like we don't need to bother
            // https://blogs.msdn.microsoft.com/pfxteam/2012/03/25/do-i-need-to-dispose-of-tasks/
            // In case we'd still want to do it, it'd be safest for this class to have a function
            // for disposing the task (maybe this class could implement IDisposable), and the
            // user of this class would then call it
        }
        /// <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));
        }