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); }
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)); }