public IncrementalPatchApplier(PatchIntercomms comms, PatchInfo patchInfo) { this.comms = comms; this.patchInfo = patchInfo; string patchVersion = patchInfo.PatchVersion(); patchDownloadPath = comms.GetDownloadPathForPatch(patchVersion); patchDecompressPath = comms.GetDecompressPathForPatch(patchVersion); }
private PatchResult CreateIncrementalPatch() { if (cancel) { return(PatchResult.Failed); } Directory.CreateDirectory(incrementalPatchOutputPath); Directory.CreateDirectory(incrementalPatchTempPath); incrementalPatch = new PatchInfo { FromVersion = previousVersion, ToVersion = version }; Log(Localization.Get(StringId.CreatingIncrementalPatch)); Stopwatch timer = Stopwatch.StartNew(); DirectoryInfo rootDirectory = new DirectoryInfo(rootPath); TraverseIncrementalPatchRecursively(rootDirectory, ""); if (cancel) { return(PatchResult.Failed); } Log(Localization.Get(StringId.CompressingPatchIntoOneFile)); string compressedPatchPath = incrementalPatchOutputPath + incrementalPatch.PatchVersion() + PatchParameters.PATCH_FILE_EXTENSION; ZipUtils.CompressFolderLZMA(incrementalPatchTempPath, compressedPatchPath); Log(Localization.Get(StringId.WritingIncrementalPatchInfoToXML)); PatchUtils.SerializePatchInfoToXML(incrementalPatch, incrementalPatchOutputPath + incrementalPatch.PatchVersion() + PatchParameters.PATCH_INFO_EXTENSION); patch.Patches.Add(new IncrementalPatch(previousVersion, version, new FileInfo(compressedPatchPath))); PatchUtils.DeleteDirectory(incrementalPatchTempPath); Log(Localization.Get(StringId.IncrementalPatchCreatedInXSeconds, timer.ElapsedSeconds())); return(PatchResult.Success); }
public static string PatchVersion(this PatchInfo patchInfo) { return(patchInfo.FromVersion.ToString().Replace('.', '_') + "__" + patchInfo.ToVersion.ToString().Replace('.', '_')); }
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 (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); } PatchMethod patchMethod = PatchMethod.None; // Check if repair patch exists List <VersionItem> versionInfoFiles = comms.VersionInfo.Files; for (int i = 0; i < versionInfoFiles.Count; i++) { // Files will have default compressed values when repair patch is not generated VersionItem item = versionInfoFiles[i]; if (item.CompressedFileSize == 0L && string.IsNullOrEmpty(item.CompressedMd5Hash)) { canRepair = false; break; } } // Find patch method to use by default if (canIncrementalPatch) { // Find incremental patches to apply VersionCode thisVersion = rootVersion; List <IncrementalPatch> versionInfoPatches = comms.VersionInfo.Patches; 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) { patchMethod = canRepair ? PatchMethod.Repair : PatchMethod.None; } else if (!canRepair) { patchMethod = PatchMethod.IncrementalPatch; } else { // Find cheapest patch method long incrementalPatchSize = 0L; long repairSize = 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; } 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.FileSize, item.Md5Hash)) { continue; } if (comms.SelfPatching) { FileInfo decompressedFile = new FileInfo(comms.DecompressedFilesPath + item.Path); if (decompressedFile.Exists && decompressedFile.MatchesSignature(item.FileSize, item.Md5Hash)) { continue; } } repairSize += item.CompressedFileSize; } patchMethod = repairSize <= incrementalPatchSize ? PatchMethod.Repair : PatchMethod.IncrementalPatch; } } else if (canRepair) { patchMethod = PatchMethod.Repair; } if (patchMethod == PatchMethod.None) { FailReason = PatchFailReason.NoSuitablePatchMethodFound; FailDetails = Localization.Get(StringId.E_NoSuitablePatchMethodFound); return(PatchResult.Failed); } // Check if there is enough free disk space long requiredFreeSpaceInCache = 0L, 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); } } // Start patching if (patchMethod == PatchMethod.Repair) { if (!PatchUsingRepair()) { if (comms.Cancel) { return(PatchResult.Failed); } if (!canIncrementalPatch || !PatchUsingIncrementalPatches()) { return(PatchResult.Failed); } } } else if (!PatchUsingIncrementalPatches()) { if (comms.Cancel) { return(PatchResult.Failed); } if (canRepair) { canRepair = false; if (!PatchUsingRepair()) { return(PatchResult.Failed); } } else { return(PatchResult.Failed); } } PatchStage = PatchStage.CheckingFileIntegrity; if (!CheckLocalFilesUpToDate(false, comms.SelfPatching)) { if (patchMethod == PatchMethod.IncrementalPatch && canRepair) { comms.Log(Localization.Get(StringId.SomeFilesAreStillNotUpToDate)); if (!PatchUsingRepair()) { 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)); } comms.DisposeFileLogger(); // Can't delete CachePath while a StreamWriter is still open inside 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++) { PatchInfo 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 string selfPatcherDirectory = PatchParameters.SELF_PATCHER_DIRECTORY + Path.DirectorySeparatorChar; sb.Append(separator).Append(PatchParameters.SELF_PATCH_DELETE_OP); 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)) { if (File.Exists(absolutePath)) { File.Delete(absolutePath); } else if (Directory.Exists(absolutePath)) { Directory.Delete(absolutePath); } } else { sb.Append(separator).Append(absolutePath); } } 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); }