From a2f8a8d0600074625337479bbda25fa4527db841 Mon Sep 17 00:00:00 2001 From: Anton Firsin Date: Wed, 14 Feb 2018 14:56:15 +0500 Subject: [PATCH 1/2] issue with locked files: check files equality before delete and copy --- .../Tasks/FileUpdateTask.cs | 7 +++ src/NAppUpdate.Framework/Utils/FileSystem.cs | 61 ++++++++++++++----- 2 files changed, 53 insertions(+), 15 deletions(-) diff --git a/src/NAppUpdate.Framework/Tasks/FileUpdateTask.cs b/src/NAppUpdate.Framework/Tasks/FileUpdateTask.cs index 3830f2bf..d2c76a31 100644 --- a/src/NAppUpdate.Framework/Tasks/FileUpdateTask.cs +++ b/src/NAppUpdate.Framework/Tasks/FileUpdateTask.cs @@ -151,14 +151,21 @@ public override bool Rollback() { if (string.IsNullOrEmpty(_destinationFile)) return true; + if (!File.Exists(_backupFile)) + return false; // Copy the backup copy back to its original position if (File.Exists(_destinationFile)) + { + if (FileSystem.AreFileContentsEqual(_backupFile, _destinationFile)) + return true; File.Delete(_destinationFile); + } File.Copy(_backupFile, _destinationFile, true); return true; } + /// /// To mitigate problems with the files being locked even though the application mutex has been released. /// https://github.com/synhershko/NAppUpdate/issues/35 diff --git a/src/NAppUpdate.Framework/Utils/FileSystem.cs b/src/NAppUpdate.Framework/Utils/FileSystem.cs index e1d1a777..7e32cfdf 100644 --- a/src/NAppUpdate.Framework/Utils/FileSystem.cs +++ b/src/NAppUpdate.Framework/Utils/FileSystem.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.IO; +using System.Linq; using System.Security.AccessControl; using System.Security.Principal; @@ -99,21 +100,51 @@ public static bool IsFileLocked(FileInfo file) return false; } - public static void CopyAccessControl(FileInfo src, FileInfo dst) - { - FileSecurity fs = src.GetAccessControl(); - - bool hasInheritanceRules = fs.GetAccessRules(false, true, typeof(SecurityIdentifier)).Count > 0; - if (hasInheritanceRules) - { - fs.SetAccessRuleProtection(false, false); - } - else - { - fs.SetAccessRuleProtection(true, true); - } - - dst.SetAccessControl(fs); + public static void CopyAccessControl(FileInfo src, FileInfo dst) + { + FileSecurity fs = src.GetAccessControl(); + + bool hasInheritanceRules = fs.GetAccessRules(false, true, typeof(SecurityIdentifier)).Count > 0; + if (hasInheritanceRules) + { + fs.SetAccessRuleProtection(false, false); + } + else + { + fs.SetAccessRuleProtection(true, true); + } + + dst.SetAccessControl(fs); + } + + public static bool AreFileContentsEqual(string fileA, string fileB) + { + const int bufferSize = sizeof(Int64) << 10; + using (var streamA = new FileStream(fileA, FileMode.Open, FileAccess.Read, FileShare.Read, bufferSize)) + using (var streamB = new FileStream(fileB, FileMode.Open, FileAccess.Read, FileShare.Read, bufferSize)) + { + if (streamA.Length != streamB.Length) + return false; + var seqA = streamA.AsIenumerable(bufferSize); + var seqB = streamB.AsIenumerable(bufferSize); + return seqA.Zip(seqB, (bA, bB) => bA == bB).All(b => b); + } + } + + private static IEnumerable AsIenumerable(this Stream stream, int bufferSize) + { + var buffer = new byte[bufferSize]; + while (stream.Position < stream.Length) + { + bufferSize = Math.Min((int)(stream.Length - stream.Position), bufferSize); + var read = stream.Read(buffer, 0, bufferSize); + for (int i = 0; i < read; i++) + { + yield return buffer[i]; + } + if (read < bufferSize) + yield break; + } } } } From 5dfffdf5a0ea5cc5bd55bd8b619e02988eea1162 Mon Sep 17 00:00:00 2001 From: Anton Firsin Date: Wed, 14 Feb 2018 15:22:01 +0500 Subject: [PATCH 2/2] Automatic rollback after update fail --- src/NAppUpdate.Updater/AppStart.cs | 53 ++++++++++++++++++++++++------ 1 file changed, 43 insertions(+), 10 deletions(-) diff --git a/src/NAppUpdate.Updater/AppStart.cs b/src/NAppUpdate.Updater/AppStart.cs index 825206db..8918b760 100644 --- a/src/NAppUpdate.Updater/AppStart.cs +++ b/src/NAppUpdate.Updater/AppStart.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Reflection; @@ -154,7 +155,11 @@ private static void PerformUpdates() Log("Got {0} task objects", _dto.Tasks.Count); + var exceptions = new List(); + bool updateFailed = false; + // Perform the actual off-line update process + var completedTasks = new List(); foreach (var t in _dto.Tasks) { Log("Task \"{0}\": {1}", t.Description, t.ExecutionStatus); @@ -165,8 +170,6 @@ private static void PerformUpdates() continue; } - Exception exception = null; - try { Log("\tExecuting..."); @@ -174,23 +177,50 @@ private static void PerformUpdates() } catch (Exception ex) { + Log(Logger.SeverityLevel.Error, "\tFailed: " + ex.Message); t.ExecutionStatus = TaskExecutionStatus.Failed; - exception = ex; + exceptions.Add(new Exception(string.Format("Update failed, task execution failed, description: {0}, execution status: {1}", t.Description, t.ExecutionStatus), ex)); } + completedTasks.Add(t); if (t.ExecutionStatus != TaskExecutionStatus.Successful) { - string taskFailedMessage = string.Format("Update failed, task execution failed, description: {0}, execution status: {1}", t.Description, t.ExecutionStatus); - throw new Exception(taskFailedMessage, exception); + updateFailed = true; + break; } } - Log("Finished successfully"); - Log("Removing backup folder"); + bool rollbackFailed = false; + if (!updateFailed) + { + Log("Finished successfully"); + } + else + { + Log("\tRollback..."); + foreach (var task in completedTasks) + { + try + { + task.Rollback(); + } + catch (Exception ex) + { + Log(Logger.SeverityLevel.Error, "\t\tRollback failed: " + ex.Message); + exceptions.Add(ex); + rollbackFailed = true; + } + } + } - if (Directory.Exists(_dto.Configs.BackupFolder)) + if (!updateFailed || !rollbackFailed) { - FileSystem.DeleteDirectory(_dto.Configs.BackupFolder); + Log("Removing backup folder"); + + if (Directory.Exists(_dto.Configs.BackupFolder)) + { + FileSystem.DeleteDirectory(_dto.Configs.BackupFolder); + } } // Start the application only if requested to do so @@ -215,9 +245,12 @@ private static void PerformUpdates() } catch (Exception ex) { - throw new UpdateProcessFailedException("Unable to relaunch application and/or send DTO", ex); + exceptions.Add(new UpdateProcessFailedException("Unable to relaunch application and/or send DTO", ex)); } } + + if(updateFailed) + throw new AggregateException("Unable to update", exceptions); } private static void Teardown()