private bool CheckLocalFilesUpToDate(bool checkObsoleteFiles, bool searchSelfPatchFiles) { comms.Log(Localization.Get(StringId.CheckingIfFilesAreUpToDate)); List <VersionItem> versionInfoFiles = comms.VersionInfo.Files; for (int i = 0; i < versionInfoFiles.Count; i++) { VersionItem item = versionInfoFiles[i]; FileInfo localFile = new FileInfo(comms.RootPath + item.Path); if (!localFile.Exists || !localFile.MatchesSignature(item.FileSize, item.Md5Hash)) { if (searchSelfPatchFiles) { FileInfo decompressedFile = new FileInfo(comms.DecompressedFilesPath + item.Path); if (decompressedFile.Exists && decompressedFile.MatchesSignature(item.FileSize, item.Md5Hash)) { continue; } } return(false); } } // Check if there are any obsolete files return(!checkObsoleteFiles || FindFilesToDelete(comms.RootPath).Count == 0); }
public List <VersionItem> FindFilesToUpdate() { List <VersionItem> versionInfoFiles = VersionInfo.Files; List <VersionItem> result = new List <VersionItem>(); for (int i = 0; i < versionInfoFiles.Count; i++) { if (Cancel) { return(null); } VersionItem item = versionInfoFiles[i]; FileInfo localFile = new FileInfo(RootPath + item.Path); if (localFile.Exists && localFile.MatchesSignature(item.FileSize, item.Md5Hash)) { continue; } if (SelfPatching) { FileInfo decompressedFile = new FileInfo(DecompressedFilesPath + item.Path); if (decompressedFile.Exists && decompressedFile.MatchesSignature(item.FileSize, item.Md5Hash)) { continue; } } result.Add(item); } return(result); }
private void CompressRepairItemsToDestination() { Stopwatch timer = Stopwatch.StartNew(); for (int i = 0; i < patch.Files.Count; i++) { if (cancel) { return; } VersionItem patchItem = patch.Files[i]; string fromAbsolutePath = rootPath + patchItem.Path; string toAbsolutePath = repairPatchOutputPath + patchItem.Path + PatchParameters.COMPRESSED_FILE_EXTENSION; Log(Localization.Get(StringId.CompressingXToY, fromAbsolutePath, toAbsolutePath)); timer.Reset(); timer.Start(); ZipUtils.CompressFileLZMA(fromAbsolutePath, toAbsolutePath); Log(Localization.Get(StringId.CompressionFinishedInXSeconds, timer.ElapsedSeconds())); patchItem.OnCompressed(new FileInfo(toAbsolutePath)); } }
private PatchResult CreateRepairPatch() { if (cancel) { return(PatchResult.Failed); } Directory.CreateDirectory(repairPatchOutputPath); Log(Localization.Get(StringId.CreatingRepairPatch)); Stopwatch timer = Stopwatch.StartNew(); // Compress repair patch files and move them to the destination Log(Localization.Get(StringId.CompressingFilesToDestination)); Stopwatch compressTimer = Stopwatch.StartNew(); for (int i = 0; i < versionInfo.Files.Count; i++) { if (cancel) { return(PatchResult.Failed); } VersionItem patchItem = versionInfo.Files[i]; string fromAbsolutePath = rootPath + patchItem.Path; string toAbsolutePath = repairPatchOutputPath + patchItem.Path + PatchParameters.REPAIR_PATCH_FILE_EXTENSION; Log(Localization.Get(StringId.CompressingXToY, fromAbsolutePath, toAbsolutePath)); compressTimer.Reset(); compressTimer.Start(); ZipUtils.CompressFile(fromAbsolutePath, toAbsolutePath, compressionFormatRepairPatch); Log(Localization.Get(StringId.CompressionFinishedInXSeconds, compressTimer.ElapsedSeconds())); patchItem.OnCompressed(new FileInfo(toAbsolutePath)); } if (cancel) { return(PatchResult.Failed); } Log(Localization.Get(StringId.PatchCreatedInXSeconds, timer.ElapsedSeconds())); // Calculate compression ratio long uncompressedTotal = 0L, compressedTotal = 0L; for (int i = 0; i < versionInfo.Files.Count; i++) { uncompressedTotal += versionInfo.Files[i].FileSize; compressedTotal += versionInfo.Files[i].CompressedFileSize; } Log(Localization.Get(StringId.CompressionRatioIsX, ((double)compressedTotal * 100 / uncompressedTotal).ToString("F2"))); return(PatchResult.Success); }
public string GetDownloadURLFor(VersionItem item) { if (!string.IsNullOrEmpty(item.DownloadURL)) { return(item.DownloadURL); } if (!string.IsNullOrEmpty(BaseDownloadURL)) { return(BaseDownloadURL + PatchParameters.REPAIR_PATCH_DIRECTORY + '/' + item.Path.Replace('\\', '/') + PatchParameters.COMPRESSED_FILE_EXTENSION); } return(null); }
private List <VersionItem> FindFilesToDownload(List <VersionItem> filesToUpdate) { List <VersionItem> result = new List <VersionItem>(); for (int i = 0; i < filesToUpdate.Count; i++) { if (comms.Cancel) { return(null); } VersionItem item = filesToUpdate[i]; FileInfo downloadedFile = new FileInfo(comms.DownloadsPath + item.Path); if (!downloadedFile.Exists || !downloadedFile.MatchesSignature(item.CompressedFileSize, item.CompressedMd5Hash)) { result.Add(item); } } return(result); }
public static VersionInfo DeserializeXMLToVersionInfo(string xmlContent) { var serializer = new XmlSerializer(typeof(VersionInfo)); using (var stream = new StringReader(xmlContent)) { VersionInfo result = serializer.Deserialize(stream) as VersionInfo; if (result != null) { result.IncrementalPatches.RemoveAll((patch) => !patch.FromVersion.IsValid || !patch.ToVersion.IsValid || patch.ToVersion <= patch.FromVersion); result.IncrementalPatches.Sort(IncrementalPatchComparison); // BaseDownloadURL uses '/' as path separator char, be consistent if (result.BaseDownloadURL.StartsWith("file://")) { result.BaseDownloadURL = "file://" + result.BaseDownloadURL.Substring(7).Replace('\\', '/'); } // Always use Path.DirectorySeparatorChar for (int i = 0; i < result.Files.Count; i++) { VersionItem item = result.Files[i]; item.Path = item.Path.Replace(AltDirectorySeparatorChar, Path.DirectorySeparatorChar); } result.IgnoredPathsRegex = new List <Regex>(result.IgnoredPaths.Count + 2); for (int i = 0; i < result.IgnoredPaths.Count; i++) { result.AddIgnoredPath(result.IgnoredPaths[i]); } result.AddIgnoredPath("*" + PatchParameters.VERSION_HOLDER_FILENAME_POSTFIX); result.AddIgnoredPath("*" + PatchParameters.LOG_FILE_NAME); } return(result); } }
private bool DownloadAndUpdateFiles(List <VersionItem> filesToDownload, List <VersionItem> filesToUpdate) { string rootPath = comms.SelfPatching ? comms.DecompressedFilesPath : comms.RootPath; for (int i = 0, j = 0; i < filesToUpdate.Count; i++) { if (comms.Cancel) { return(false); } VersionItem item = filesToUpdate[i]; string downloadAbsolutePath = comms.DownloadsPath + item.Path; if (filesToDownload.Count > 0 && filesToDownload[j] == item) { comms.Stage = PatchStage.DownloadingFiles; // Download the file Directory.CreateDirectory(Path.GetDirectoryName(downloadAbsolutePath)); comms.Log(Localization.Get(StringId.DownloadingXthFile, j + 1, filesToDownload.Count, item.Path, item.CompressedFileSize.ToMegabytes())); Stopwatch downloadTimer = Stopwatch.StartNew(); FileInfo downloadedFile = comms.DownloadManager.DownloadFileFromURLToPath(comms.VersionInfo.GetDownloadURLFor(item), downloadAbsolutePath, item.CompressedFileSize); if (downloadedFile == null) { comms.FailReason = PatchFailReason.DownloadError; comms.FailDetails = Localization.Get(StringId.E_XCouldNotBeDownloaded, item.Path); return(false); } else if (!downloadedFile.MatchesSignature(item.CompressedFileSize, item.CompressedMd5Hash)) { comms.FailReason = PatchFailReason.CorruptDownloadError; comms.FailDetails = Localization.Get(StringId.E_DownloadedFileXIsCorrupt, item.Path); return(false); } else { comms.Log(Localization.Get(StringId.XDownloadedInYSeconds, item.Path, downloadTimer.ElapsedSeconds())); } j++; } if (comms.Cancel) { return(false); } comms.Stage = PatchStage.UpdatingFiles; comms.Log(Localization.Get(StringId.UpdatingXthFile, i + 1, filesToUpdate.Count, item.Path)); string targetAbsolutePath = rootPath + item.Path; Directory.CreateDirectory(Path.GetDirectoryName(targetAbsolutePath)); ZipUtils.DecompressFile(downloadAbsolutePath, targetAbsolutePath, comms.VersionInfo.CompressionFormat); File.Delete(downloadAbsolutePath); ReportProgress(1, 0L); } return(true); }
protected override PatchResult Execute() { if (comms.Cancel) { return(PatchResult.Failed); } if (comms.IsUnderMaintenance()) { return(PatchResult.Failed); } Stopwatch timer = Stopwatch.StartNew(); comms.Stage = PatchStage.CalculatingFilesToUpdate; comms.Log(Localization.Get(StringId.CalculatingNewOrChangedFiles)); List <VersionItem> filesToUpdate = comms.FindFilesToUpdate(); if (filesToUpdate.Count == 0) { return(PatchResult.AlreadyUpToDate); } if (comms.Cancel) { return(PatchResult.Failed); } comms.Log(Localization.Get(StringId.CalculatingFilesToDownload)); List <VersionItem> filesToDownload = FindFilesToDownload(filesToUpdate); if (comms.Cancel) { return(PatchResult.Failed); } long estimatedDownloadSize = 0L; for (int i = 0; i < filesToDownload.Count; i++) { estimatedDownloadSize += filesToDownload[i].CompressedFileSize; } InitializeProgress(filesToUpdate.Count, estimatedDownloadSize); if (filesToDownload.Count > 0 && comms.VerifyFiles) { comms.Stage = PatchStage.VerifyingFilesOnServer; for (int i = 0; i < filesToDownload.Count; i++) { if (comms.Cancel) { return(PatchResult.Failed); } VersionItem item = filesToDownload[i]; long fileSize; if (!comms.DownloadManager.FileExistsAtUrl(comms.VersionInfo.GetDownloadURLFor(item), out fileSize)) { comms.FailReason = PatchFailReason.FileDoesNotExistOnServer; comms.FailDetails = Localization.Get(StringId.E_FileXDoesNotExistOnServer, item.Path); return(PatchResult.Failed); } else if (fileSize > 0L && fileSize != item.CompressedFileSize) { comms.FailReason = PatchFailReason.FileIsNotValidOnServer; comms.FailDetails = Localization.Get(StringId.E_FileXIsNotValidOnServer, item.Path); return(PatchResult.Failed); } } } if (filesToDownload.Count > 0) { comms.Log(Localization.Get(StringId.DownloadingXFiles, filesToDownload.Count)); } if (filesToUpdate.Count > 0) { comms.Log(Localization.Get(StringId.UpdatingXFiles, filesToUpdate.Count)); } Stopwatch downloadTimer = Stopwatch.StartNew(); if (!DownloadAndUpdateFiles(filesToDownload, filesToUpdate)) { return(PatchResult.Failed); } comms.Log(Localization.Get(StringId.AllFilesAreDownloadedInXSeconds, downloadTimer.ElapsedSeconds())); PatchUtils.DeleteDirectory(comms.DownloadsPath); comms.Log(Localization.Get(StringId.PatchAppliedInXSeconds, timer.ElapsedSeconds())); return(PatchResult.Success); }
public bool UpdateDownloadLinks(Dictionary <string, string> downloadLinks) { // Replace all AltDirectorySeparatorChar's with DirectorySeparatorChar's in the dictionary keys List <string> keys = new List <string>(downloadLinks.Keys); for (int i = 0; i < keys.Count; i++) { string key = keys[i]; if (key.IndexOf(PatchUtils.AltDirectorySeparatorChar) >= 0) { downloadLinks[key.Replace(PatchUtils.AltDirectorySeparatorChar, Path.DirectorySeparatorChar)] = downloadLinks[key]; downloadLinks.Remove(key); } } int updateCount = 0; int totalCount = VersionInfo.Files.Count + VersionInfo.IncrementalPatches.Count; for (int i = 0; i < VersionInfo.Files.Count; i++) { VersionItem item = VersionInfo.Files[i]; string downloadLink; string relativePath = item.Path.Replace(PatchUtils.AltDirectorySeparatorChar, Path.DirectorySeparatorChar); if (downloadLinks.TryGetValue(relativePath, out downloadLink) || downloadLinks.TryGetValue(relativePath + PatchParameters.REPAIR_PATCH_FILE_EXTENSION, out downloadLink) || downloadLinks.TryGetValue(PatchParameters.REPAIR_PATCH_DIRECTORY + Path.DirectorySeparatorChar + relativePath, out downloadLink) || downloadLinks.TryGetValue(PatchParameters.REPAIR_PATCH_DIRECTORY + Path.DirectorySeparatorChar + relativePath + PatchParameters.REPAIR_PATCH_FILE_EXTENSION, out downloadLink)) { item.DownloadURL = downloadLink; updateCount++; } } for (int i = 0; i < VersionInfo.IncrementalPatches.Count; i++) { IncrementalPatch patch = VersionInfo.IncrementalPatches[i]; string downloadLink; string relativePath = patch.PatchVersion() + PatchParameters.INCREMENTAL_PATCH_FILE_EXTENSION; if (downloadLinks.TryGetValue(relativePath, out downloadLink) || downloadLinks.TryGetValue(PatchParameters.INCREMENTAL_PATCH_DIRECTORY + Path.DirectorySeparatorChar + relativePath, out downloadLink)) { patch.DownloadURL = downloadLink; updateCount++; } relativePath = patch.PatchVersion() + PatchParameters.INCREMENTAL_PATCH_INFO_EXTENSION; if (downloadLinks.TryGetValue(relativePath, out downloadLink) || downloadLinks.TryGetValue(PatchParameters.INCREMENTAL_PATCH_DIRECTORY + Path.DirectorySeparatorChar + relativePath, out downloadLink)) { patch.InfoURL = downloadLink; updateCount++; } if (patch.Files > 0) { totalCount++; } } if (VersionInfo.InstallerPatch.PatchSize > 0L || !string.IsNullOrEmpty(VersionInfo.InstallerPatch.PatchMd5Hash)) { string downloadLink; string relativePath = PatchParameters.INSTALLER_PATCH_FILENAME; if (downloadLinks.TryGetValue(relativePath, out downloadLink) || downloadLinks.TryGetValue(PatchParameters.INSTALLER_PATCH_DIRECTORY + Path.DirectorySeparatorChar + relativePath, out downloadLink)) { VersionInfo.InstallerPatch.DownloadURL = downloadLink; updateCount++; } totalCount++; } if (Logger != null) { Logger(Localization.Get(StringId.XDownloadLinksAreUpdatedSuccessfully, updateCount, totalCount)); } return(true); }
private PatchResult Patch() { PatchStage = PatchStage.CheckingUpdates; Stopwatch timer = Stopwatch.StartNew(); comms.Log(Localization.Get(StringId.RetrievingVersionInfo)); if (!FetchVersionInfo()) { return(PatchResult.Failed); } if (comms.IsUnderMaintenance()) { return(PatchResult.Failed); } if (!currentVersion.IsValid) { currentVersion = new VersionCode(0); } VersionCode rootVersion = currentVersion; if (comms.SelfPatching) { VersionCode patchedVersion = PatchUtils.GetVersion(comms.DecompressedFilesPath, comms.VersionInfo.Name); if (patchedVersion > currentVersion) { currentVersion = patchedVersion; } } PatchStage = PatchStage.CheckingFileIntegrity; if (CheckLocalFilesUpToDate(true, false)) { return(PatchResult.AlreadyUpToDate); } if (!PatchUtils.CheckWriteAccessToFolder(comms.RootPath)) { FailReason = PatchFailReason.RequiresAdminPriviledges; FailDetails = Localization.Get(StringId.E_AccessToXIsForbiddenRunInAdminMode, comms.RootPath); return(PatchResult.Failed); } if (!PatchUtils.CheckWriteAccessToFolder(comms.CachePath)) { FailReason = PatchFailReason.RequiresAdminPriviledges; FailDetails = Localization.Get(StringId.E_AccessToXIsForbiddenRunInAdminMode, comms.CachePath); return(PatchResult.Failed); } if (checkForMultipleRunningInstances) { string currentExecutablePath = PatchUtils.GetCurrentExecutablePath(); if (PatchUtils.GetNumberOfRunningProcesses(currentExecutablePath) > 1) { FailReason = PatchFailReason.MultipleRunningInstances; FailDetails = Localization.Get(StringId.E_AnotherInstanceOfXIsRunning, Path.GetFileName(currentExecutablePath)); return(PatchResult.Failed); } } if (comms.Cancel) { return(PatchResult.Failed); } // Add a date holder file to the cache to save the last access time reliably DateTime dateTimeNow = DateTime.UtcNow; File.WriteAllText(comms.CachePath + PatchParameters.CACHE_DATE_HOLDER_FILENAME, dateTimeNow.ToString("O")); // Check if there are any leftover files from other SimplePatchTool integrated apps in cache DirectoryInfo[] patcherCaches = new DirectoryInfo(comms.CachePath).Parent.GetDirectories(); for (int i = 0; i < patcherCaches.Length; i++) { DirectoryInfo cacheDir = patcherCaches[i]; if (cacheDir.Name.Equals(comms.VersionInfo.Name, StringComparison.OrdinalIgnoreCase)) { continue; } FileInfo dateHolder = new FileInfo(PatchUtils.GetPathWithTrailingSeparatorChar(cacheDir.FullName) + PatchParameters.CACHE_DATE_HOLDER_FILENAME); if (dateHolder.Exists && dateHolder.Length > 0L) { DateTime lastAccessTime; if (DateTime.TryParseExact(File.ReadAllText(dateHolder.FullName), "O", CultureInfo.InvariantCulture, DateTimeStyles.RoundtripKind, out lastAccessTime)) { if ((dateTimeNow - lastAccessTime).TotalDays <= PatchParameters.CACHE_DATE_EXPIRE_DAYS) { continue; } } } // This cache directory doesn't have a date holder file or is older than CACHE_DATE_EXPIRE_DAYS, delete it cacheDir.Delete(true); } bool canRepairPatch = this.canRepairPatch; bool canIncrementalPatch = this.canIncrementalPatch; bool canInstallerPatch = this.canInstallerPatch; List <PatchMethodHolder> preferredPatchMethods = new List <PatchMethodHolder>(3); List <VersionItem> versionInfoFiles = comms.VersionInfo.Files; if (canRepairPatch) { for (int i = 0; i < versionInfoFiles.Count; i++) { VersionItem item = versionInfoFiles[i]; if (item.CompressedFileSize == 0L && string.IsNullOrEmpty(item.CompressedMd5Hash)) { canRepairPatch = false; break; } } if (canRepairPatch) { long repairPatchSize = 0L; for (int i = 0; i < versionInfoFiles.Count; i++) { VersionItem item = versionInfoFiles[i]; FileInfo localFile = new FileInfo(comms.RootPath + item.Path); if (localFile.Exists && localFile.MatchesSignature(item.FileSize, item.Md5Hash)) { continue; } FileInfo downloadedFile = new FileInfo(comms.DownloadsPath + item.Path); if (downloadedFile.Exists && downloadedFile.MatchesSignature(item.CompressedFileSize, item.CompressedMd5Hash)) { continue; } if (comms.SelfPatching) { FileInfo decompressedFile = new FileInfo(comms.DecompressedFilesPath + item.Path); if (decompressedFile.Exists && decompressedFile.MatchesSignature(item.FileSize, item.Md5Hash)) { continue; } } repairPatchSize += item.CompressedFileSize; } preferredPatchMethods.Add(new PatchMethodHolder(PatchMethod.RepairPatch, repairPatchSize)); } } if (canIncrementalPatch) { // Find incremental patches to apply VersionCode thisVersion = rootVersion; List <IncrementalPatch> versionInfoPatches = comms.VersionInfo.IncrementalPatches; for (int i = 0; i < versionInfoPatches.Count; i++) { if (thisVersion == comms.VersionInfo.Version) { break; } IncrementalPatch patch = versionInfoPatches[i]; if (thisVersion == patch.FromVersion) { thisVersion = patch.ToVersion; incrementalPatches.Add(patch); } } if (thisVersion != comms.VersionInfo.Version) { incrementalPatches.Clear(); } if (incrementalPatches.Count == 0) { canIncrementalPatch = false; } else { long incrementalPatchSize = 0L; for (int i = 0; i < incrementalPatches.Count; i++) { IncrementalPatch incrementalPatch = incrementalPatches[i]; if (currentVersion > incrementalPatch.FromVersion) { continue; } FileInfo patchFile = new FileInfo(comms.GetDownloadPathForPatch(incrementalPatch.PatchVersion())); if (patchFile.Exists && patchFile.MatchesSignature(incrementalPatch.PatchSize, incrementalPatch.PatchMd5Hash)) { continue; } incrementalPatchSize += incrementalPatch.PatchSize; } preferredPatchMethods.Add(new PatchMethodHolder(PatchMethod.IncrementalPatch, incrementalPatchSize)); } } if (canInstallerPatch) { InstallerPatch installerPatch = comms.VersionInfo.InstallerPatch; if (installerPatch.PatchSize == 0L && string.IsNullOrEmpty(installerPatch.PatchMd5Hash)) { canInstallerPatch = false; } else { preferredPatchMethods.Add(new PatchMethodHolder(PatchMethod.InstallerPatch, installerPatch.PatchSize)); } } preferredPatchMethods.Sort((p1, p2) => p1.size.CompareTo(p2.size)); if (preferredPatchMethods.Count == 0) { FailReason = PatchFailReason.NoSuitablePatchMethodFound; FailDetails = Localization.Get(StringId.E_NoSuitablePatchMethodFound); return(PatchResult.Failed); } // Check if there is enough free disk space long requiredFreeSpaceInCache = preferredPatchMethods[0].size, requiredFreeSpaceInRoot = 0L; for (int i = 0; i < versionInfoFiles.Count; i++) { VersionItem item = versionInfoFiles[i]; FileInfo localFile = new FileInfo(comms.RootPath + item.Path); if (!localFile.Exists) { requiredFreeSpaceInCache += item.FileSize; requiredFreeSpaceInRoot += item.FileSize; } else if (!localFile.MatchesSignature(item.FileSize, item.Md5Hash)) { requiredFreeSpaceInCache += item.FileSize; long deltaSize = item.FileSize - localFile.Length; if (deltaSize > 0L) { requiredFreeSpaceInRoot += deltaSize; } } } requiredFreeSpaceInCache += requiredFreeSpaceInCache / 3; // Require additional 33% free space (might be needed by compressed files and/or incremental patches) requiredFreeSpaceInCache += 1024 * 1024 * 1024L; // Require additional 1 GB of free space, just in case string rootDrive = new DirectoryInfo(comms.RootPath).Root.FullName; string cacheDrive = new DirectoryInfo(comms.CachePath).Root.FullName; if (rootDrive.Equals(cacheDrive, StringComparison.OrdinalIgnoreCase)) { if (!CheckFreeSpace(rootDrive, requiredFreeSpaceInCache + requiredFreeSpaceInRoot)) { return(PatchResult.Failed); } } else { if (!CheckFreeSpace(rootDrive, requiredFreeSpaceInRoot)) { return(PatchResult.Failed); } if (!CheckFreeSpace(cacheDrive, requiredFreeSpaceInCache)) { return(PatchResult.Failed); } } for (int i = 0; i < preferredPatchMethods.Count; i++) { comms.LogToFile(Localization.Get(StringId.PatchMethodXSizeY, preferredPatchMethods[i].method, preferredPatchMethods[i].size.ToMegabytes() + "MB")); } // Start patching for (int i = 0; i < preferredPatchMethods.Count; i++) { PatchMethod patchMethod = preferredPatchMethods[i].method; bool success; if (patchMethod == PatchMethod.RepairPatch) { PatchMethod = PatchMethod.RepairPatch; comms.ListenerCallPatchMethodChanged(PatchMethod); success = PatchUsingRepairPatch(); } else if (patchMethod == PatchMethod.IncrementalPatch) { PatchMethod = PatchMethod.IncrementalPatch; comms.ListenerCallPatchMethodChanged(PatchMethod); success = PatchUsingIncrementalPatches(); } else { PatchMethod = PatchMethod.InstallerPatch; comms.ListenerCallPatchMethodChanged(PatchMethod); success = PatchUsingInstallerPatch(); } if (comms.Cancel) { return(PatchResult.Failed); } if (success) { break; } else { comms.LogToFile(string.Concat(comms.FailReason, ": ", comms.FailDetails)); if (i == preferredPatchMethods.Count - 1) { return(PatchResult.Failed); } } } PatchStage = PatchStage.CheckingFileIntegrity; if (!CheckLocalFilesUpToDate(false, comms.SelfPatching)) { comms.Log(Localization.Get(StringId.SomeFilesAreStillNotUpToDate)); if (canRepairPatch) { if (!PatchUsingRepairPatch()) { return(PatchResult.Failed); } } else { FailReason = PatchFailReason.FilesAreNotUpToDateAfterPatch; FailDetails = Localization.Get(StringId.E_FilesAreNotUpToDateAfterPatch); return(PatchResult.Failed); } } comms.UpdateVersion(comms.VersionInfo.Version); PatchStage = PatchStage.DeletingObsoleteFiles; comms.Log(Localization.Get(StringId.CalculatingObsoleteFiles)); List <string> obsoleteFiles = FindFilesToDelete(comms.RootPath); if (!comms.SelfPatching) { if (obsoleteFiles.Count > 0) { comms.Log(Localization.Get(StringId.DeletingXObsoleteFiles, obsoleteFiles.Count)); for (int i = 0; i < obsoleteFiles.Count; i++) { comms.Log(Localization.Get(StringId.DeletingX, obsoleteFiles[i])); File.Delete(comms.RootPath + obsoleteFiles[i]); } } else { comms.Log(Localization.Get(StringId.NoObsoleteFiles)); } PatchUtils.DeleteDirectory(comms.CachePath); } else { // Delete obsolete self patching files List <string> obsoleteSelfPatchingFiles = FindFilesToDelete(comms.DecompressedFilesPath); if (obsoleteSelfPatchingFiles.Count > 0) { comms.Log(Localization.Get(StringId.DeletingXObsoleteFiles, obsoleteSelfPatchingFiles.Count)); for (int i = 0; i < obsoleteSelfPatchingFiles.Count; i++) { comms.Log(Localization.Get(StringId.DeletingX, obsoleteSelfPatchingFiles[i])); File.Delete(comms.DecompressedFilesPath + obsoleteSelfPatchingFiles[i]); } } else { comms.Log(Localization.Get(StringId.NoObsoleteFiles)); } // Self patcher executable, if exists, can't self patch itself, so patch it manually here // This assumes that self patcher and any related files are located at SELF_PATCHER_DIRECTORY string selfPatcherFiles = comms.DecompressedFilesPath + PatchParameters.SELF_PATCHER_DIRECTORY; if (Directory.Exists(selfPatcherFiles)) { PatchUtils.MoveDirectory(selfPatcherFiles, comms.RootPath + PatchParameters.SELF_PATCHER_DIRECTORY); } string separator = PatchParameters.SELF_PATCH_OP_SEPARATOR; StringBuilder sb = new StringBuilder(500); // Append current version to the beginning of the file sb.Append(rootVersion); // 1. Rename files if (incrementalPatchesInfo.Count > 0) { sb.Append(separator).Append(PatchParameters.SELF_PATCH_MOVE_OP); for (int i = 0; i < incrementalPatchesInfo.Count; i++) { IncrementalPatchInfo incrementalPatch = incrementalPatchesInfo[i]; for (int j = 0; j < incrementalPatch.RenamedFiles.Count; j++) { PatchRenamedItem renamedItem = incrementalPatch.RenamedFiles[j]; sb.Append(separator).Append(comms.RootPath + renamedItem.BeforePath).Append(separator).Append(comms.RootPath + renamedItem.AfterPath); } } } // 2. Update files sb.Append(separator).Append(PatchParameters.SELF_PATCH_MOVE_OP); DirectoryInfo updatedFilesDir = new DirectoryInfo(comms.DecompressedFilesPath); DirectoryInfo[] updatedSubDirectories = updatedFilesDir.GetDirectories(); for (int i = 0; i < updatedSubDirectories.Length; i++) { sb.Append(separator).Append(comms.DecompressedFilesPath).Append(updatedSubDirectories[i].Name).Append(Path.DirectorySeparatorChar).Append(separator).Append(comms.RootPath).Append(updatedSubDirectories[i].Name).Append(Path.DirectorySeparatorChar); } string versionHolderFilename = comms.VersionInfo.Name + PatchParameters.VERSION_HOLDER_FILENAME_POSTFIX; FileInfo[] updatedFiles = updatedFilesDir.GetFiles(); for (int i = 0; i < updatedFiles.Length; i++) { // Don't update the version holder file until everything else is updated properly if (updatedFiles[i].Name != versionHolderFilename) { sb.Append(separator).Append(comms.DecompressedFilesPath).Append(updatedFiles[i].Name).Append(separator).Append(comms.RootPath).Append(updatedFiles[i].Name); } } // Update the version holder now sb.Append(separator).Append(comms.DecompressedFilesPath).Append(versionHolderFilename).Append(separator).Append(comms.RootPath).Append(versionHolderFilename); // 3. Delete obsolete files if (obsoleteFiles.Count > 0) { string selfPatcherDirectory = PatchParameters.SELF_PATCHER_DIRECTORY + Path.DirectorySeparatorChar; sb.Append(separator).Append(PatchParameters.SELF_PATCH_DELETE_OP); comms.Log(Localization.Get(StringId.DeletingXObsoleteFiles, obsoleteFiles.Count)); for (int i = 0; i < obsoleteFiles.Count; i++) { // Delete the obsolete files inside SELF_PATCHER_DIRECTORY manually string absolutePath = comms.RootPath + obsoleteFiles[i]; if (obsoleteFiles[i].StartsWith(selfPatcherDirectory, StringComparison.OrdinalIgnoreCase)) { comms.Log(Localization.Get(StringId.DeletingX, obsoleteFiles[i])); if (File.Exists(absolutePath)) { File.Delete(absolutePath); } else if (Directory.Exists(absolutePath)) { PatchUtils.DeleteDirectory(absolutePath); } } else { // '-->' indicates that the file will be deleted by the self patcher executable comms.LogToFile(Localization.Get(StringId.DeletingX, "--> " + obsoleteFiles[i])); sb.Append(separator).Append(absolutePath); } } } else { comms.Log(Localization.Get(StringId.NoObsoleteFiles)); } sb.Append(separator).Append(comms.CachePath); File.Delete(comms.CachePath + PatchParameters.SELF_PATCH_COMPLETED_INSTRUCTIONS_FILENAME); File.WriteAllText(comms.CachePath + PatchParameters.SELF_PATCH_INSTRUCTIONS_FILENAME, sb.Append(separator).ToString()); } comms.Log(Localization.Get(StringId.PatchCompletedInXSeconds, timer.ElapsedSeconds())); return(PatchResult.Success); }
private PatchResult CreateRepairPatch() { if (cancel) { return(PatchResult.Failed); } Directory.CreateDirectory(repairPatchOutputPath); Log(Localization.Get(StringId.CreatingRepairPatch)); Stopwatch timer = Stopwatch.StartNew(); // Compress repair patch files and move them to the destination Log(Localization.Get(StringId.CompressingFilesToDestination)); Stopwatch compressTimer = Stopwatch.StartNew(); // Check if we can use existing repair patch files for files that didn't change since the last patch string previousRepairPatchFilesRoot = null; if (previousVersionInfo != null && previousVersionInfo.CompressionFormat == compressionFormatRepairPatch) { previousRepairPatchFilesRoot = previousPatchFilesRoot + PatchParameters.REPAIR_PATCH_DIRECTORY + Path.DirectorySeparatorChar; if (!Directory.Exists(previousRepairPatchFilesRoot)) { previousRepairPatchFilesRoot = null; } } for (int i = 0; i < versionInfo.Files.Count; i++) { if (cancel) { return(PatchResult.Failed); } VersionItem patchItem = versionInfo.Files[i]; string fromAbsolutePath = rootPath + patchItem.Path; string toAbsolutePath = repairPatchOutputPath + patchItem.Path + PatchParameters.REPAIR_PATCH_FILE_EXTENSION; if (previousRepairPatchFilesRoot != null) { VersionItem previousPatchItem = previousVersionInfo.Files.Find((item) => item.Path == patchItem.Path); if (previousPatchItem != null && previousPatchItem.FileSize == patchItem.FileSize && previousPatchItem.Md5Hash == patchItem.Md5Hash) { if (dontCreatePatchFilesForUnchangedFiles) { patchItem.CompressedFileSize = previousPatchItem.CompressedFileSize; patchItem.CompressedMd5Hash = previousPatchItem.CompressedMd5Hash; continue; } FileInfo previousCompressedFile = new FileInfo(previousRepairPatchFilesRoot + patchItem.Path + PatchParameters.REPAIR_PATCH_FILE_EXTENSION); if (previousCompressedFile.Exists && previousCompressedFile.MatchesSignature(previousPatchItem.CompressedFileSize, previousPatchItem.CompressedMd5Hash)) { Log(Localization.Get(StringId.CopyingXToPatch, previousCompressedFile.FullName)); PatchUtils.CopyFile(previousCompressedFile.FullName, toAbsolutePath); patchItem.CompressedFileSize = previousPatchItem.CompressedFileSize; patchItem.CompressedMd5Hash = previousPatchItem.CompressedMd5Hash; continue; } } } Log(Localization.Get(StringId.CompressingXToY, fromAbsolutePath, toAbsolutePath)); compressTimer.Reset(); compressTimer.Start(); ZipUtils.CompressFile(fromAbsolutePath, toAbsolutePath, compressionFormatRepairPatch); Log(Localization.Get(StringId.CompressionFinishedInXSeconds, compressTimer.ElapsedSeconds())); patchItem.OnCompressed(new FileInfo(toAbsolutePath)); } if (cancel) { return(PatchResult.Failed); } Log(Localization.Get(StringId.PatchCreatedInXSeconds, timer.ElapsedSeconds())); // Calculate compression ratio long uncompressedTotal = 0L, compressedTotal = 0L; for (int i = 0; i < versionInfo.Files.Count; i++) { uncompressedTotal += versionInfo.Files[i].FileSize; compressedTotal += versionInfo.Files[i].CompressedFileSize; } Log(Localization.Get(StringId.CompressionRatioIsX, ((double)compressedTotal * 100 / uncompressedTotal).ToString("F2"))); return(PatchResult.Success); }