From 27bafb1552ad154fdb8ccf34e4325f9278bc28a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Domeradzki?= <JustArchi@JustArchi.net> Date: Sat, 6 Apr 2024 04:53:16 +0200 Subject: [PATCH] Resolve files in-use during update on Windows --- ArchiSteamFarm/Core/Utilities.cs | 33 +++++++++++++++++++++++++++----- 1 file changed, 28 insertions(+), 5 deletions(-) diff --git a/ArchiSteamFarm/Core/Utilities.cs b/ArchiSteamFarm/Core/Utilities.cs index 94641f7b95e85..600c6c2eb13f8 100644 --- a/ArchiSteamFarm/Core/Utilities.cs +++ b/ArchiSteamFarm/Core/Utilities.cs @@ -388,10 +388,10 @@ internal static async Task<bool> UpdateFromArchive(ZipArchive zipArchive, string Directory.CreateDirectory(backupDirectory); - MoveAllUpdateFiles(targetDirectory, backupDirectory, true); + MoveAllUpdateFiles(targetDirectory, backupDirectory); // Finally, we can move the newly extracted files to target directory - MoveAllUpdateFiles(updateDirectory, targetDirectory, false); + MoveAllUpdateFiles(updateDirectory, targetDirectory, backupDirectory); // Critical section has finished, we can now cleanup the update directory, backup directory must wait for the process restart Directory.Delete(updateDirectory, true); @@ -481,13 +481,16 @@ private static async Task DeletePotentiallyUsedDirectory(string directory) { } } - private static void MoveAllUpdateFiles(string sourceDirectory, string targetDirectory, bool keepUserFiles) { - ArgumentException.ThrowIfNullOrEmpty(sourceDirectory); + private static void MoveAllUpdateFiles(string sourceDirectory, string targetDirectory, string? backupDirectory = null) { ArgumentException.ThrowIfNullOrEmpty(sourceDirectory); + ArgumentException.ThrowIfNullOrEmpty(targetDirectory); // Determine if targetDirectory is within sourceDirectory, if yes we need to skip it from enumeration further below string targetRelativeDirectoryPath = Path.GetRelativePath(sourceDirectory, targetDirectory); + // We keep user files if backup directory is null, as it means we're creating one + bool keepUserFiles = string.IsNullOrEmpty(backupDirectory); + foreach (string file in Directory.EnumerateFiles(sourceDirectory, "*", SearchOption.AllDirectories)) { string fileName = Path.GetFileName(file); @@ -505,7 +508,7 @@ private static void MoveAllUpdateFiles(string sourceDirectory, string targetDire switch (relativeDirectoryName) { case null: - throw new InvalidOperationException(nameof(keepUserFiles)); + throw new InvalidOperationException(nameof(relativeDirectoryName)); case "": // No directory, root folder switch (fileName) { @@ -557,6 +560,26 @@ private static void MoveAllUpdateFiles(string sourceDirectory, string targetDire string targetUpdateFile = Path.Combine(targetUpdateDirectory, fileName); + // If target update file exists and we have a backup directory, we should consider moving it to the backup directory regardless whether or not we did that before as part of backup procedure + // This achieves two purposes, firstly, we ensure additional backup of user file in case something goes wrong, and secondly, we decrease a possibility of overwriting files that are in-use on Windows, since we move them out of the picture first + if (!string.IsNullOrEmpty(backupDirectory) && File.Exists(targetUpdateFile)) { + string targetBackupDirectory; + + if (relativeDirectoryName.Length > 0) { + // File inside a subdirectory + targetBackupDirectory = Path.Combine(backupDirectory, relativeDirectoryName); + + Directory.CreateDirectory(targetBackupDirectory); + } else { + // File in root directory + targetBackupDirectory = backupDirectory; + } + + string targetBackupFile = Path.Combine(targetBackupDirectory, fileName); + + File.Move(targetUpdateFile, targetBackupFile, true); + } + File.Move(file, targetUpdateFile, true); } }