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);
        }
Exemple #3
0
 public static string PatchVersion(this PatchInfo patchInfo)
 {
     return(patchInfo.FromVersion.ToString().Replace('.', '_') + "__" + patchInfo.ToVersion.ToString().Replace('.', '_'));
 }
Exemple #4
0
        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);
        }