From 7b869a24a426c3fa2f857a8a3d707736aa4a9be5 Mon Sep 17 00:00:00 2001 From: Matthias Beerens Date: Sat, 13 May 2017 18:25:45 +0200 Subject: [PATCH 01/16] refactor + start from commandline --- UpdateLib/TestApp/Form1.cs | 6 +- UpdateLib/TestApp/Program.cs | 22 ++++-- UpdateLib/TestApp/TestApp.csproj | 10 +++ UpdateLib/TestApp/app.manifest | 76 +++++++++++++++++++ .../UpdateLib/Tasks/CheckForUpdatesTask.cs | 3 +- UpdateLib/UpdateLib/UI/UpdaterForm.cs | 7 +- UpdateLib/UpdateLib/Updater.cs | 58 ++++++++++++++ 7 files changed, 168 insertions(+), 14 deletions(-) create mode 100644 UpdateLib/TestApp/app.manifest diff --git a/UpdateLib/TestApp/Form1.cs b/UpdateLib/TestApp/Form1.cs index e31f79b..6030dc0 100644 --- a/UpdateLib/TestApp/Form1.cs +++ b/UpdateLib/TestApp/Form1.cs @@ -7,6 +7,7 @@ using System.Drawing; using System.IO; using System.Security.Cryptography; +using System.Security.Permissions; using System.Text; using System.Threading; using System.Windows.Forms; @@ -79,6 +80,7 @@ private string ReadFile(string file) return string.Join(", ", lines); } + FileStream fs; /// /// Bad code that keeps the file open & locked /// Purpose: to demonstrate the updater still works on locked files. @@ -90,13 +92,13 @@ private string ReadFileAndKeepStreamOpen(string file) if (!File.Exists(file)) return "ERROR: File doesn't exist.."; - FileStream fs = new FileStream(file, FileMode.Open, FileAccess.ReadWrite, FileShare.None); + fs = new FileStream(file, FileMode.Open, FileAccess.ReadWrite, FileShare.None); StreamReader sr = new StreamReader(fs); string text = sr.ReadToEnd(); return text; } - + private void button1_Click(object sender, EventArgs e) { DummyTask task = new DummyTask(); diff --git a/UpdateLib/TestApp/Program.cs b/UpdateLib/TestApp/Program.cs index 0a911f8..979ca39 100644 --- a/UpdateLib/TestApp/Program.cs +++ b/UpdateLib/TestApp/Program.cs @@ -16,25 +16,31 @@ static class Program [STAThread] static void Main() { + // we still want our updater to have visual styles in case of update cmd argument switch + Application.EnableVisualStyles(); + Application.SetCompatibleTextRenderingDefault(false); + SetupLogging(); InitializeUpdater(); - Application.EnableVisualStyles(); - Application.SetCompatibleTextRenderingDefault(false); Application.Run(new Form1()); } + private static void SetupLogging() + { + Logger.Writers.Add(new ConsoleLogWriter()); + Logger.Writers.Add(new FileLogWriter()); + } + private static void InitializeUpdater() { - Updater.Instance.Initialize(); + // Set update url Updater.Instance.UpdateURL = "https://raw.githubusercontent.com/MatthiWare/UpdateLib.TestApp.UpdateExample/master/Dev/updatefile.xml"; //Updater.Instance.UpdateURL = "http://matthiware.dev/UpdateLib/Dev/updatefile.xml"; - } - private static void SetupLogging() - { - Logger.Writers.Add(new ConsoleLogWriter()); - Logger.Writers.Add(new FileLogWriter()); + Updater.Instance.Initialize(); } + + } } diff --git a/UpdateLib/TestApp/TestApp.csproj b/UpdateLib/TestApp/TestApp.csproj index 6328b00..91f86a9 100644 --- a/UpdateLib/TestApp/TestApp.csproj +++ b/UpdateLib/TestApp/TestApp.csproj @@ -34,6 +34,13 @@ prompt 4 + + + TestApp.Program + + + app.manifest + @@ -67,6 +74,9 @@ True Resources.resx + + Designer + SettingsSingleFileGenerator Settings.Designer.cs diff --git a/UpdateLib/TestApp/app.manifest b/UpdateLib/TestApp/app.manifest new file mode 100644 index 0000000..a6b46bb --- /dev/null +++ b/UpdateLib/TestApp/app.manifest @@ -0,0 +1,76 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/UpdateLib/UpdateLib/Tasks/CheckForUpdatesTask.cs b/UpdateLib/UpdateLib/Tasks/CheckForUpdatesTask.cs index 8193b4d..c33d1f3 100644 --- a/UpdateLib/UpdateLib/Tasks/CheckForUpdatesTask.cs +++ b/UpdateLib/UpdateLib/Tasks/CheckForUpdatesTask.cs @@ -5,6 +5,7 @@ using System.Drawing; using MatthiWare.UpdateLib.UI; using static MatthiWare.UpdateLib.Tasks.CheckForUpdatesTask; +using MatthiWare.UpdateLib.Logging; namespace MatthiWare.UpdateLib.Tasks { @@ -25,7 +26,7 @@ public CheckForUpdatesTask(string url) protected override void DoWork() { - if (string.IsNullOrEmpty(Url)) throw new WebException("Invalid Url"); + if (string.IsNullOrEmpty(Url)) throw new WebException("Invalid Url", WebExceptionStatus.NameResolutionFailure); Result = new Data(); diff --git a/UpdateLib/UpdateLib/UI/UpdaterForm.cs b/UpdateLib/UpdateLib/UI/UpdaterForm.cs index 13e84c8..559e310 100644 --- a/UpdateLib/UpdateLib/UI/UpdaterForm.cs +++ b/UpdateLib/UpdateLib/UI/UpdaterForm.cs @@ -13,7 +13,7 @@ public partial class UpdaterForm : Form internal bool NeedsRestart = true; private WizardPageCollection pages; - + public UpdaterForm(UpdateFile updateFile) { InitializeComponent(); @@ -61,7 +61,7 @@ private void Page_PageUpdate(object sender, EventArgs e) delegate void _OnPageUpdate(IWizardPage page); private void OnPageUpdate(IWizardPage page) { - this.InvokeOnUI(() => + this.InvokeOnUI(() => { if (page.IsDone && !page.IsBusy) { @@ -142,10 +142,11 @@ private void ExitUpdater() //} Application.Restart(); + Environment.Exit(0); } else { - Updater.Instance.Initialize(); + Updater.Instance.Initialize(); pages.Clear(); pages.Add(new FinishPage(this)); diff --git a/UpdateLib/UpdateLib/Updater.cs b/UpdateLib/UpdateLib/Updater.cs index b27eb85..42bebc9 100644 --- a/UpdateLib/UpdateLib/Updater.cs +++ b/UpdateLib/UpdateLib/Updater.cs @@ -47,6 +47,12 @@ public string UpdateURL } } + public bool EnableCmdArguments { get; set; } = true; + public bool UpdateSilently { get; set; } = false; + public string UpdateSilentlyCmdArg { get; set; } = "-silent"; + + public string StartUpdatingCmdArg { get; set; } = "-update"; + public bool ShowUpdateMessage { get; set; } = true; public bool ShowMessageOnNoUpdate { get; set; } = true; public bool ShowErrorMessage { get; set; } = true; @@ -66,6 +72,31 @@ public string UpdateURL internal string RemoteBasePath { get; set; } + #region Fluent API + + public Updater ConfigureCmdArgs(bool enabled) + { + EnableCmdArguments = enabled; + + return this; + } + + public Updater ConfigureSilentCmdArg(string cmdArg) + { + UpdateSilentlyCmdArg = cmdArg; + + return this; + } + + public Updater ConfigureUpdateCmdArg(string cmdArg) + { + StartUpdatingCmdArg = cmdArg; + + return this; + } + + #endregion + /// /// Initializes a new instance of with the default settings. /// @@ -78,6 +109,19 @@ private Updater() /// Initializes the updater /// public void Initialize() + { + StartInitializationTasks(); + + if (!EnableCmdArguments) + return; + + bool shouldStartUpdating = ParseCmdArguments(Environment.GetCommandLineArgs()); + + if (shouldStartUpdating) + CheckForUpdates(); + } + + private void StartInitializationTasks() { CleanUpTask = new CleanUpTask("."); CleanUpTask.ConfigureAwait(false).Start(); @@ -88,6 +132,20 @@ public void Initialize() initialized = true; } + private bool ParseCmdArguments(string[] args) + { + bool startUpdating = false; + foreach (string arg in args) + { + if (arg == StartUpdatingCmdArg) + startUpdating = true; + else if (arg == UpdateSilentlyCmdArg) + UpdateSilently = true; + } + + return startUpdating; + } + /// /// Starting the update process /// From 154c27906c5544189e893aa882571ade5905439f Mon Sep 17 00:00:00 2001 From: Matthias Beerens Date: Sun, 14 May 2017 16:29:07 +0200 Subject: [PATCH 02/16] Refactor Fluent Api in AsyncTask --- UpdateLib/UpdateLib/Tasks/AsyncTask.cs | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/UpdateLib/UpdateLib/Tasks/AsyncTask.cs b/UpdateLib/UpdateLib/Tasks/AsyncTask.cs index 34d534a..9d434df 100644 --- a/UpdateLib/UpdateLib/Tasks/AsyncTask.cs +++ b/UpdateLib/UpdateLib/Tasks/AsyncTask.cs @@ -5,6 +5,7 @@ using System.ComponentModel; using System.Diagnostics; using System.Threading; +using System.Linq; namespace MatthiWare.UpdateLib.Tasks { @@ -135,9 +136,7 @@ private set public static void WaitAll(IEnumerable tasks) { foreach (AsyncTask task in tasks) - { task.AwaitTask(); - } } #endregion @@ -277,7 +276,7 @@ protected void Enqueue(Delegate action, params object[] args) /// Blocks the calling thread until the complete task is done. /// DO NOT call this in the worker method use method instead. /// - public void AwaitTask() + public AsyncTask AwaitTask() { if (IsChildTask && !IsCompleted && !IsRunning) Reset(); @@ -288,6 +287,8 @@ public void AwaitTask() m_waitHandle.Close(); m_waitHandle = null; } + + return this; } private int x = 0; @@ -386,20 +387,17 @@ public abstract class AsyncTask : AsyncTask /// The task object for fluent API. public new AsyncTask ConfigureAwait(bool useSyncContext) { - base.ConfigureAwait(useSyncContext); - return this; + return (AsyncTask)base.ConfigureAwait(useSyncContext); } - #endregion - /// /// Starts the task /// /// Returns the current Task. public new AsyncTask Start() { - base.Start(); - return this; + return (AsyncTask)base.Start(); + } /// @@ -407,10 +405,14 @@ public abstract class AsyncTask : AsyncTask /// DO NOT call this in the worker method use method instead. /// /// - public new T AwaitTask() + public new AsyncTask AwaitTask() { - base.AwaitTask(); - return Result; + return (AsyncTask)base.AwaitTask(); + } + + #endregion + + } } From b859693bffa020715433a8e5a55a9b959c4a2084 Mon Sep 17 00:00:00 2001 From: Matthias Beerens Date: Sun, 14 May 2017 16:44:35 +0200 Subject: [PATCH 03/16] More commandline configuration options, local/shared install mode, refactor + unit tests --- UpdateLib/TestApp/Program.cs | 1 - .../UpdateLib.Tests/Tasks/AsyncTaskTest.cs | 4 +- .../UpdateLib.Tests/UpdateLib.Tests.csproj | 1 + UpdateLib/UpdateLib.Tests/UpdaterTest.cs | 53 +++++++++++++++++++ UpdateLib/UpdateLib/Files/HashCacheFile.cs | 26 ++++++++- UpdateLib/UpdateLib/InstallationMode.cs | 23 ++++++++ .../Logging/Writers/FileLogWriter.cs | 24 ++++++++- .../UpdateLib/Tasks/CheckForUpdatesTask.cs | 2 +- UpdateLib/UpdateLib/UpdateLib.csproj | 1 + UpdateLib/UpdateLib/Updater.cs | 22 +++++--- 10 files changed, 142 insertions(+), 15 deletions(-) create mode 100644 UpdateLib/UpdateLib.Tests/UpdaterTest.cs create mode 100644 UpdateLib/UpdateLib/InstallationMode.cs diff --git a/UpdateLib/TestApp/Program.cs b/UpdateLib/TestApp/Program.cs index 979ca39..31e5e68 100644 --- a/UpdateLib/TestApp/Program.cs +++ b/UpdateLib/TestApp/Program.cs @@ -22,7 +22,6 @@ static void Main() SetupLogging(); InitializeUpdater(); - Application.Run(new Form1()); } diff --git a/UpdateLib/UpdateLib.Tests/Tasks/AsyncTaskTest.cs b/UpdateLib/UpdateLib.Tests/Tasks/AsyncTaskTest.cs index ebec6ea..e68a1b4 100644 --- a/UpdateLib/UpdateLib.Tests/Tasks/AsyncTaskTest.cs +++ b/UpdateLib/UpdateLib.Tests/Tasks/AsyncTaskTest.cs @@ -56,7 +56,7 @@ public void TestMethod() object o = new object(); ResultTask task = new ResultTask(o); task.Start(); - Assert.AreEqual(o, task.AwaitTask()); + Assert.AreEqual(o, task.AwaitTask().Result); } [Test, Parallelizable] @@ -79,7 +79,7 @@ private void TestResultTask(T input) }; task.Start(); - Assert.AreEqual(input, task.AwaitTask()); + Assert.AreEqual(input, task.AwaitTask().Result); } [Test, Parallelizable] diff --git a/UpdateLib/UpdateLib.Tests/UpdateLib.Tests.csproj b/UpdateLib/UpdateLib.Tests/UpdateLib.Tests.csproj index 2abb0b7..897596e 100644 --- a/UpdateLib/UpdateLib.Tests/UpdateLib.Tests.csproj +++ b/UpdateLib/UpdateLib.Tests/UpdateLib.Tests.csproj @@ -66,6 +66,7 @@ + diff --git a/UpdateLib/UpdateLib.Tests/UpdaterTest.cs b/UpdateLib/UpdateLib.Tests/UpdaterTest.cs new file mode 100644 index 0000000..5795c74 --- /dev/null +++ b/UpdateLib/UpdateLib.Tests/UpdaterTest.cs @@ -0,0 +1,53 @@ +using MatthiWare.UpdateLib; +using MatthiWare.UpdateLib.Tasks; +using NUnit.Framework; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace UpdateLib.Tests +{ + [TestFixture] + public class UpdaterTest + { + [Test] + public void EnsureMultithreadedAccessWorks() + { + int samples = 10; + + AsyncTask[] tasks = new AsyncTask[samples]; + Updater[] updaters = new Updater[samples]; + + Func getUpdaterAction = new Func(() => Updater.Instance); + + for (int i = 0; i < samples; i++) + tasks[i] = AsyncTaskFactory.StartNew(getUpdaterAction, null); + + AsyncTask.WaitAll(tasks); + + for (int i = 0; i < samples; i++) + updaters[i] = tasks[i].Result; + + int amountOfDistinctUpdaters = updaters.Distinct().Count(); + + Assert.AreEqual(1, amountOfDistinctUpdaters); + } + + [Test] + public void TestInitializationActuallyInitializes() + { + Updater u = Updater.Instance; + + u.Initialize(); + + Assert.IsTrue(u.IsInitialized); + + AsyncTask task = u.CleanUpTask.AwaitTask(); + AsyncTask task2 = u.UpdateCacheTask.AwaitTask(); + + Assert.IsTrue(task.IsRunning || task.IsCompleted); + Assert.IsTrue(task2.IsRunning || task2.IsCompleted); + } + } +} diff --git a/UpdateLib/UpdateLib/Files/HashCacheFile.cs b/UpdateLib/UpdateLib/Files/HashCacheFile.cs index cb7e7da..b82890d 100644 --- a/UpdateLib/UpdateLib/Files/HashCacheFile.cs +++ b/UpdateLib/UpdateLib/Files/HashCacheFile.cs @@ -9,6 +9,9 @@ namespace MatthiWare.UpdateLib.Files [Serializable] public class HashCacheFile { + public const string CACHE_FOLDER_NAME = "Cache"; + public const string FILE_NAME = "HashCacheFile.xml"; + [XmlArray("Items")] [XmlArrayItem("Entry")] public List Items { get; set; } @@ -25,14 +28,33 @@ private static string GetStoragePath() { if (string.IsNullOrEmpty(storagePath)) { - string appdata = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData); + string path = GetPathPrefix(); + string productName = GetProductName(); string name = Assembly.GetEntryAssembly().GetName().Name; - storagePath = $@"{appdata}\{name}\UpdateLib\Cache\HashCacheFile.xml"; + + storagePath = $@"{path}\{name}\{productName}\{CACHE_FOLDER_NAME}\{FILE_NAME}"; } return storagePath; } + private static string GetProductName() + { + AssemblyProductAttribute attr = Attribute.GetCustomAttribute(Assembly.GetAssembly(typeof(HashCacheFile)), typeof(AssemblyProductAttribute)) as AssemblyProductAttribute; + return attr?.Product ?? ""; + } + + private static string GetPathPrefix() + { + switch (Updater.Instance.InstallationMode) + { + case InstallationMode.Local: + return Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData); + default: + return Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData); + } + } + public static HashCacheFile Load() { if (!File.Exists(GetStoragePath())) diff --git a/UpdateLib/UpdateLib/InstallationMode.cs b/UpdateLib/UpdateLib/InstallationMode.cs new file mode 100644 index 0000000..465938a --- /dev/null +++ b/UpdateLib/UpdateLib/InstallationMode.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace MatthiWare.UpdateLib +{ + /// + /// Indicates the how the underlaying application is installed. + /// + public enum InstallationMode + { + /// + /// Shared installation we will use the roaming folder + /// + Shared = 0, + + /// + /// Single user installation we will use the local folder + /// + Local = 1 + } +} diff --git a/UpdateLib/UpdateLib/Logging/Writers/FileLogWriter.cs b/UpdateLib/UpdateLib/Logging/Writers/FileLogWriter.cs index 822f489..e48541c 100644 --- a/UpdateLib/UpdateLib/Logging/Writers/FileLogWriter.cs +++ b/UpdateLib/UpdateLib/Logging/Writers/FileLogWriter.cs @@ -9,6 +9,8 @@ namespace MatthiWare.UpdateLib.Logging.Writers { public class FileLogWriter : ILogWriter { + public const string LOG_FOLDER_NAME = "Log"; + public LoggingLevel LoggingLevel { get { return LoggingLevel.Debug; } } private FileInfo logFile; @@ -17,14 +19,32 @@ public class FileLogWriter : ILogWriter public FileLogWriter() { - string appdata = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData); + string path = GetPathPrefix(); + string productName = GetProductName(); string name = Assembly.GetEntryAssembly().GetName().Name; - logFile = new FileInfo($@"{appdata}\{name}\UpdateLib\Log\log_{DateTime.Now.ToString("yyyyMMdd")}.log"); + logFile = new FileInfo($@"{path}\{name}\{productName}\{LOG_FOLDER_NAME}\log_{DateTime.Now.ToString("yyyyMMdd")}.log"); if (!logFile.Directory.Exists) logFile.Directory.Create(); } + private string GetProductName() + { + AssemblyProductAttribute attr = Attribute.GetCustomAttribute(Assembly.GetAssembly(typeof(FileLogWriter)), typeof(AssemblyProductAttribute)) as AssemblyProductAttribute; + return attr?.Product ?? ""; + } + + private string GetPathPrefix() + { + switch (Updater.Instance.InstallationMode) + { + case InstallationMode.Local: + return Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData); + default: + return Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData); + } + } + public void Log(string text) { Action logAction = new Action(LogAsync); diff --git a/UpdateLib/UpdateLib/Tasks/CheckForUpdatesTask.cs b/UpdateLib/UpdateLib/Tasks/CheckForUpdatesTask.cs index c33d1f3..a92e834 100644 --- a/UpdateLib/UpdateLib/Tasks/CheckForUpdatesTask.cs +++ b/UpdateLib/UpdateLib/Tasks/CheckForUpdatesTask.cs @@ -50,7 +50,7 @@ protected override void DoWork() * Start a task to get all the files that need to be updated * Returns if there is anything to update */ - Result.UpdateAvailable = CheckForUpdatedFiles(Result.UpdateFile, cache).AwaitTask(); + Result.UpdateAvailable = CheckForUpdatedFiles(Result.UpdateFile, cache).AwaitTask().Result; } private CheckForUpdatedFilesTask CheckForUpdatedFiles(UpdateFile file, HashCacheFile cache) diff --git a/UpdateLib/UpdateLib/UpdateLib.csproj b/UpdateLib/UpdateLib/UpdateLib.csproj index 54e0fba..ce52d0c 100644 --- a/UpdateLib/UpdateLib/UpdateLib.csproj +++ b/UpdateLib/UpdateLib/UpdateLib.csproj @@ -63,6 +63,7 @@ + diff --git a/UpdateLib/UpdateLib/Updater.cs b/UpdateLib/UpdateLib/Updater.cs index 42bebc9..e0ad119 100644 --- a/UpdateLib/UpdateLib/Updater.cs +++ b/UpdateLib/UpdateLib/Updater.cs @@ -1,7 +1,6 @@ using System; using System.Text; using MatthiWare.UpdateLib.Files; -using System.ComponentModel; using System.Windows.Forms; using MatthiWare.UpdateLib.UI; using System.Drawing; @@ -47,6 +46,8 @@ public string UpdateURL } } + public InstallationMode InstallationMode { get; set; } = InstallationMode.Shared; + public bool EnableCmdArguments { get; set; } = true; public bool UpdateSilently { get; set; } = false; public string UpdateSilentlyCmdArg { get; set; } = "-silent"; @@ -68,12 +69,19 @@ public string UpdateURL /// public UpdateCacheTask UpdateCacheTask { get; private set; } - private bool initialized = false; + public bool IsInitialized { get; private set; } internal string RemoteBasePath { get; set; } #region Fluent API + public Updater ConfigureInstallationMode(InstallationMode mode) + { + InstallationMode = mode; + + return this; + } + public Updater ConfigureCmdArgs(bool enabled) { EnableCmdArguments = enabled; @@ -129,7 +137,7 @@ private void StartInitializationTasks() UpdateCacheTask = new UpdateCacheTask(); UpdateCacheTask.ConfigureAwait(false).Start(); - initialized = true; + IsInitialized = true; } private bool ParseCmdArguments(string[] args) @@ -152,7 +160,7 @@ private bool ParseCmdArguments(string[] args) /// Whether or not there is an update available and the latest version public CheckForUpdatesTask.Data CheckForUpdates() { - return CheckForUpdatesAsync().AwaitTask(); + return CheckForUpdatesAsync().AwaitTask().Result; } /// @@ -162,7 +170,7 @@ public CheckForUpdatesTask.Data CheckForUpdates() /// Whether or not there is an update available and the latest version public CheckForUpdatesTask.Data CheckForUpdates(IWin32Window owner) { - return CheckForUpdatesAsync(owner).AwaitTask(); + return CheckForUpdatesAsync(owner).AwaitTask().Result; } /// @@ -181,7 +189,7 @@ public CheckForUpdatesTask CheckForUpdatesAsync() /// The update checker task. public CheckForUpdatesTask CheckForUpdatesAsync(IWin32Window owner) { - if (!initialized) + if (!IsInitialized) throw new InvalidOperationException("The updater needs to be initialized first."); if (string.IsNullOrEmpty(UpdateURL)) @@ -220,7 +228,7 @@ public CheckForUpdatesTask CheckForUpdatesAsync(IWin32Window owner) /// The of the current application public HashCacheFile GetCache() { - return UpdateCacheTask.AwaitTask(); + return UpdateCacheTask.AwaitTask().Result; } private string GetRemoteBasePath() From 0fb4ae022f792eb137de5b71aae5bfdbb744ea27 Mon Sep 17 00:00:00 2001 From: Matthias Beerens Date: Sun, 14 May 2017 18:19:55 +0200 Subject: [PATCH 04/16] Lazy initialization helper class --- UpdateLib/TestApp/Program.cs | 7 ++- UpdateLib/UpdateLib/Files/HashCacheFile.cs | 24 ++++----- .../Logging/Writers/FileLogWriter.cs | 22 ++++---- UpdateLib/UpdateLib/UpdateLib.csproj | 1 + UpdateLib/UpdateLib/Utils/Lazy.cs | 51 +++++++++++++++++++ 5 files changed, 80 insertions(+), 25 deletions(-) create mode 100644 UpdateLib/UpdateLib/Utils/Lazy.cs diff --git a/UpdateLib/TestApp/Program.cs b/UpdateLib/TestApp/Program.cs index 31e5e68..42eeaf4 100644 --- a/UpdateLib/TestApp/Program.cs +++ b/UpdateLib/TestApp/Program.cs @@ -22,6 +22,7 @@ static void Main() SetupLogging(); InitializeUpdater(); + Application.Run(new Form1()); } @@ -37,9 +38,11 @@ private static void InitializeUpdater() Updater.Instance.UpdateURL = "https://raw.githubusercontent.com/MatthiWare/UpdateLib.TestApp.UpdateExample/master/Dev/updatefile.xml"; //Updater.Instance.UpdateURL = "http://matthiware.dev/UpdateLib/Dev/updatefile.xml"; - Updater.Instance.Initialize(); + Updater.Instance + .ConfigureInstallationMode(InstallationMode.Local) + .Initialize(); } - + } } diff --git a/UpdateLib/UpdateLib/Files/HashCacheFile.cs b/UpdateLib/UpdateLib/Files/HashCacheFile.cs index b82890d..b8d30b9 100644 --- a/UpdateLib/UpdateLib/Files/HashCacheFile.cs +++ b/UpdateLib/UpdateLib/Files/HashCacheFile.cs @@ -1,4 +1,5 @@ -using System; +using MatthiWare.UpdateLib.Utils; +using System; using System.Collections.Generic; using System.IO; using System.Reflection; @@ -16,7 +17,7 @@ public class HashCacheFile [XmlArrayItem("Entry")] public List Items { get; set; } - private static string storagePath; + private static Lazy storagePath = new Lazy(GetStoragePath); public HashCacheFile() { @@ -26,16 +27,11 @@ public HashCacheFile() #region Save/Load private static string GetStoragePath() { - if (string.IsNullOrEmpty(storagePath)) - { - string path = GetPathPrefix(); - string productName = GetProductName(); - string name = Assembly.GetEntryAssembly().GetName().Name; - - storagePath = $@"{path}\{name}\{productName}\{CACHE_FOLDER_NAME}\{FILE_NAME}"; - } + string path = GetPathPrefix(); + string productName = GetProductName(); + string name = Assembly.GetEntryAssembly().GetName().Name; - return storagePath; + return $@"{path}\{name}\{productName}\{CACHE_FOLDER_NAME}\{FILE_NAME}"; } private static string GetProductName() @@ -57,10 +53,10 @@ private static string GetPathPrefix() public static HashCacheFile Load() { - if (!File.Exists(GetStoragePath())) + if (!File.Exists(storagePath.Value)) return null; - using (Stream stream = File.Open(GetStoragePath(), FileMode.Open, FileAccess.Read)) + using (Stream stream = File.Open(storagePath.Value, FileMode.Open, FileAccess.Read)) { XmlSerializer serializer = new XmlSerializer(typeof(HashCacheFile)); return (HashCacheFile)serializer.Deserialize(stream); @@ -69,7 +65,7 @@ public static HashCacheFile Load() public void Save() { - FileInfo fi = new FileInfo(GetStoragePath()); + FileInfo fi = new FileInfo(storagePath.Value); if (!fi.Directory.Exists) fi.Directory.Create(); diff --git a/UpdateLib/UpdateLib/Logging/Writers/FileLogWriter.cs b/UpdateLib/UpdateLib/Logging/Writers/FileLogWriter.cs index e48541c..0ac67f0 100644 --- a/UpdateLib/UpdateLib/Logging/Writers/FileLogWriter.cs +++ b/UpdateLib/UpdateLib/Logging/Writers/FileLogWriter.cs @@ -1,4 +1,5 @@ -using System; +using MatthiWare.UpdateLib.Utils; +using System; using System.Collections.Generic; using System.IO; using System.Linq; @@ -13,28 +14,31 @@ public class FileLogWriter : ILogWriter public LoggingLevel LoggingLevel { get { return LoggingLevel.Debug; } } - private FileInfo logFile; + private Lazy m_logFile = new Lazy(GetLogFile); private readonly object sync = new object(); - public FileLogWriter() + private static FileInfo GetLogFile() { string path = GetPathPrefix(); string productName = GetProductName(); string name = Assembly.GetEntryAssembly().GetName().Name; - logFile = new FileInfo($@"{path}\{name}\{productName}\{LOG_FOLDER_NAME}\log_{DateTime.Now.ToString("yyyyMMdd")}.log"); - if (!logFile.Directory.Exists) - logFile.Directory.Create(); + FileInfo m_logFile = new FileInfo($@"{path}\{name}\{productName}\{LOG_FOLDER_NAME}\log_{DateTime.Now.ToString("yyyyMMdd")}.log"); + + if (!m_logFile.Directory.Exists) + m_logFile.Directory.Create(); + + return m_logFile; } - private string GetProductName() + private static string GetProductName() { AssemblyProductAttribute attr = Attribute.GetCustomAttribute(Assembly.GetAssembly(typeof(FileLogWriter)), typeof(AssemblyProductAttribute)) as AssemblyProductAttribute; return attr?.Product ?? ""; } - private string GetPathPrefix() + private static string GetPathPrefix() { switch (Updater.Instance.InstallationMode) { @@ -55,7 +59,7 @@ private void LogAsync(string text) { lock (sync) { - using (StreamWriter writer = new StreamWriter(logFile.Open(FileMode.OpenOrCreate, FileAccess.Write))) + using (StreamWriter writer = new StreamWriter(m_logFile.Value.Open(FileMode.OpenOrCreate, FileAccess.Write))) { writer.BaseStream.Seek(0, SeekOrigin.End); writer.WriteLine(text); diff --git a/UpdateLib/UpdateLib/UpdateLib.csproj b/UpdateLib/UpdateLib/UpdateLib.csproj index ce52d0c..4580039 100644 --- a/UpdateLib/UpdateLib/UpdateLib.csproj +++ b/UpdateLib/UpdateLib/UpdateLib.csproj @@ -128,6 +128,7 @@ + diff --git a/UpdateLib/UpdateLib/Utils/Lazy.cs b/UpdateLib/UpdateLib/Utils/Lazy.cs new file mode 100644 index 0000000..3738154 --- /dev/null +++ b/UpdateLib/UpdateLib/Utils/Lazy.cs @@ -0,0 +1,51 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace MatthiWare.UpdateLib.Utils +{ + /// + /// Threadsafe lazy initialization + /// + /// The return type + public class Lazy + { + private readonly Func m_initFunction; + + private readonly object sync = new object(); + private bool m_initialized = false; + + private T m_storedValue = default(T); + + /// + /// Gets the value and initializes once. + /// + public T Value + { + get + { + if (!m_initialized) + lock (sync) + if (!m_initialized) + { + m_storedValue = m_initFunction(); + m_initialized = true; + } + + return m_storedValue; + } + } + + /// + /// Makes a new instance of an lazy initializer + /// + /// The lazy initialization function + public Lazy(Func initFunction) + { + m_initFunction = initFunction; + } + + + } +} From e77da1a5333cee7c0c4318857cdc19544967bace Mon Sep 17 00:00:00 2001 From: Matthias Beerens Date: Thu, 18 May 2017 22:19:54 +0200 Subject: [PATCH 05/16] Updating from unsecure connection will now throw an SecurityException unless you disable this safety check. --- UpdateLib/UpdateLib/Updater.cs | 35 +++++++++++++++++++++++++++++++--- 1 file changed, 32 insertions(+), 3 deletions(-) diff --git a/UpdateLib/UpdateLib/Updater.cs b/UpdateLib/UpdateLib/Updater.cs index e0ad119..ca6d846 100644 --- a/UpdateLib/UpdateLib/Updater.cs +++ b/UpdateLib/UpdateLib/Updater.cs @@ -7,6 +7,7 @@ using MatthiWare.UpdateLib.Tasks; using MatthiWare.UpdateLib.Logging.Writers; using MatthiWare.UpdateLib.Logging; +using System.Security; namespace MatthiWare.UpdateLib { @@ -59,6 +60,8 @@ public string UpdateURL public bool ShowErrorMessage { get; set; } = true; public PathVariableConverter Converter { get; private set; } + public bool AllowUnsafeConnection { get; set; } = false; + /// /// Gets the clean up task /// @@ -75,6 +78,13 @@ public string UpdateURL #region Fluent API + public Updater ConfigureUnsafeConnections(bool allow) + { + AllowUnsafeConnection = allow; + + return this; + } + public Updater ConfigureInstallationMode(InstallationMode mode) { InstallationMode = mode; @@ -195,6 +205,9 @@ public CheckForUpdatesTask CheckForUpdatesAsync(IWin32Window owner) if (string.IsNullOrEmpty(UpdateURL)) throw new ArgumentException("You need to specifify an update url", nameof(UpdateURL)); + if (!AllowUnsafeConnection && new Uri(UpdateURL).Scheme != Uri.UriSchemeHttps) + throw new SecurityException("Using unsafe connections to update from is not allowed"); + CheckForUpdatesTask task = new CheckForUpdatesTask(UpdateURL); task.TaskCompleted += (o, e) => { @@ -204,7 +217,10 @@ public CheckForUpdatesTask CheckForUpdatesAsync(IWin32Window owner) return; } - DialogResult result = MessageDialog.Show( + DialogResult result = DialogResult.Yes; + + if (!UpdateSilently) + result = MessageDialog.Show( "Update available", $"Version {task.Result.Version} available", "Update now?\nPress yes to update or no to cancel.", @@ -213,8 +229,15 @@ public CheckForUpdatesTask CheckForUpdatesAsync(IWin32Window owner) if (result == DialogResult.Yes) { - UpdaterForm updateForm = new UpdaterForm(task.Result.UpdateFile); - updateForm.ShowDialog(owner); + if (UpdateSilently) + { + UpdateWithoutGUI(task.Result.UpdateFile); + } + else + { + UpdaterForm updateForm = new UpdaterForm(task.Result.UpdateFile); + updateForm.ShowDialog(owner); + } } CheckForUpdatesCompleted?.Invoke(task, new CheckForUpdatesCompletedEventArgs(task.Result, e)); @@ -245,5 +268,11 @@ private string GetRemoteBasePath() return builder.ToString(); } + private void UpdateWithoutGUI(UpdateFile file) + { + DownloadManager downloader = new DownloadManager(file); + downloader.Update(); + } + } } From 43a637f8b5c918bb5c496b8cd1ad51d635f9ef2d Mon Sep 17 00:00:00 2001 From: Matthias Beerens Date: Fri, 19 May 2017 23:05:15 +0200 Subject: [PATCH 06/16] More config options. Updater now waits for parent process to close. More unit tests. New security checks and custom exceptions. Downloadmanager for UI-less updating. --- UpdateLib/TestApp/Program.cs | 2 +- .../UI/Pages/PageControlBase.cs | 2 +- .../UpdateLib.Tests/UpdateLib.Tests.csproj | 1 + UpdateLib/UpdateLib.Tests/Util/LazyTests.cs | 28 ++++++ .../Security/InvalidHashException.cs | 26 +++++ UpdateLib/UpdateLib/Tasks/DownloadManager.cs | 99 +++++++++++++++++++ UpdateLib/UpdateLib/Tasks/DownloadTask.cs | 17 +++- .../UpdateLib/UI/Components/UpdatePage.cs | 72 +------------- UpdateLib/UpdateLib/UpdateLib.csproj | 3 + UpdateLib/UpdateLib/Updater.cs | 40 +++++++- UpdateLib/UpdateLib/Utils/ExtensionMethods.cs | 29 ++++++ 11 files changed, 242 insertions(+), 77 deletions(-) create mode 100644 UpdateLib/UpdateLib.Tests/Util/LazyTests.cs create mode 100644 UpdateLib/UpdateLib/Security/InvalidHashException.cs create mode 100644 UpdateLib/UpdateLib/Tasks/DownloadManager.cs create mode 100644 UpdateLib/UpdateLib/Utils/ExtensionMethods.cs diff --git a/UpdateLib/TestApp/Program.cs b/UpdateLib/TestApp/Program.cs index 42eeaf4..64c35cc 100644 --- a/UpdateLib/TestApp/Program.cs +++ b/UpdateLib/TestApp/Program.cs @@ -39,7 +39,7 @@ private static void InitializeUpdater() //Updater.Instance.UpdateURL = "http://matthiware.dev/UpdateLib/Dev/updatefile.xml"; Updater.Instance - .ConfigureInstallationMode(InstallationMode.Local) + .ConfigureInstallationMode(InstallationMode.Shared) .Initialize(); } diff --git a/UpdateLib/UpdateLib.Generator/UI/Pages/PageControlBase.cs b/UpdateLib/UpdateLib.Generator/UI/Pages/PageControlBase.cs index f5f8129..85a431b 100644 --- a/UpdateLib/UpdateLib.Generator/UI/Pages/PageControlBase.cs +++ b/UpdateLib/UpdateLib.Generator/UI/Pages/PageControlBase.cs @@ -36,7 +36,7 @@ public AsyncTask InitializePage(EventHandler callBack) if (IsPageInitialized || (taskInitialize != null && taskInitialize.IsRunning)) return taskInitialize; - taskInitialize = AsyncTaskFactory.From(new Action(() => OnPageInitialize()), null); + taskInitialize = AsyncTaskFactory.From(new Action(OnPageInitialize), null); taskInitialize.TaskCompleted += (o, e) => { diff --git a/UpdateLib/UpdateLib.Tests/UpdateLib.Tests.csproj b/UpdateLib/UpdateLib.Tests/UpdateLib.Tests.csproj index 897596e..f96b7f9 100644 --- a/UpdateLib/UpdateLib.Tests/UpdateLib.Tests.csproj +++ b/UpdateLib/UpdateLib.Tests/UpdateLib.Tests.csproj @@ -67,6 +67,7 @@ + diff --git a/UpdateLib/UpdateLib.Tests/Util/LazyTests.cs b/UpdateLib/UpdateLib.Tests/Util/LazyTests.cs new file mode 100644 index 0000000..044701c --- /dev/null +++ b/UpdateLib/UpdateLib.Tests/Util/LazyTests.cs @@ -0,0 +1,28 @@ +using NUnit.Framework; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using MatthiWare.UpdateLib.Utils; + +namespace UpdateLib.Tests.Util +{ + [TestFixture] + public class LazyTests + { + + [Test] + public void TestLazyInitializesCorreclty() + { + MatthiWare.UpdateLib.Utils.Lazy myObject = new MatthiWare.UpdateLib.Utils.Lazy(GetMyInitValue); + + Assert.AreEqual("test", myObject.Value); + } + + private string GetMyInitValue() + { + return "test"; + } + + } +} diff --git a/UpdateLib/UpdateLib/Security/InvalidHashException.cs b/UpdateLib/UpdateLib/Security/InvalidHashException.cs new file mode 100644 index 0000000..847b7dc --- /dev/null +++ b/UpdateLib/UpdateLib/Security/InvalidHashException.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.Serialization; +using System.Text; + +namespace MatthiWare.UpdateLib.Security +{ + [Serializable] + public class InvalidHashException : Exception + { + public InvalidHashException() { } + + public InvalidHashException(string message) : + base(message) + { } + + public InvalidHashException(string message, Exception inner) : + base(message, inner) + { } + + protected InvalidHashException(SerializationInfo info, StreamingContext context) : + base(info, context) + { } + } +} diff --git a/UpdateLib/UpdateLib/Tasks/DownloadManager.cs b/UpdateLib/UpdateLib/Tasks/DownloadManager.cs new file mode 100644 index 0000000..da15d89 --- /dev/null +++ b/UpdateLib/UpdateLib/Tasks/DownloadManager.cs @@ -0,0 +1,99 @@ +using MatthiWare.UpdateLib.Files; +using MatthiWare.UpdateLib.Logging; +using MatthiWare.UpdateLib.Security; +using MatthiWare.UpdateLib.Threading; +using MatthiWare.UpdateLib.Utils; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Text; +using System.Windows.Forms; + +namespace MatthiWare.UpdateLib.Tasks +{ + public class DownloadManager + { + private AtomicInteger amountToDownload = new AtomicInteger(); + private UpdateFile file; + + private List tasks = new List(); + private bool succes = true; + + public DownloadManager(UpdateFile file) + { + amountToDownload.Value = file.Count; + this.file = file; + } + + public void Update() + { + foreach (DirectoryEntry dir in file.Folders) + UpdateFilesFromDirectoryEntry(dir); + } + + private void UpdateFilesFromDirectoryEntry(DirectoryEntry dir) + { + foreach (FileEntry f in dir.Files) + UpdateEntry(f); + + foreach (DirectoryEntry d in dir.Directories) + UpdateFilesFromDirectoryEntry(d); + } + + private void UpdateEntry(FileEntry entry) + { + DownloadTask task = new DownloadTask(entry); + task.TaskCompleted += Task_TaskCompleted; + + tasks.Add(task); + + task.Start(); + } + + private void Task_TaskCompleted(object sender, System.ComponentModel.AsyncCompletedEventArgs e) + { + if (e.Error != null) + { + succes = false; + CancelOtherTasks(); + + Logger.Error(GetType().Name, e.Error); + } + + if (amountToDownload.Decrement() == 0) + { + RestartApp(); + } + } + + private void CancelOtherTasks() + { + foreach (DownloadTask task in tasks) + task.Cancel(); + } + + private void RestartApp() + { + Updater instance = Updater.Instance; + List args = new List(Environment.GetCommandLineArgs()); + + if (instance.EnableCmdArguments) + for (int i = 0; i < args.Count; i++) + if (args[i] == instance.StartUpdatingCmdArg || args[i] == instance.UpdateSilentlyCmdArg) + args[i] = string.Empty; + + args.Add(instance.WaitForProcessCmdArg); + args.Add(Process.GetCurrentProcess().Id.ToString()); + + string arguments = args.Where(a => !string.IsNullOrEmpty(a)).Distinct().AppendAll(" "); + + string startupPath = Application.StartupPath; + + ProcessStartInfo startInfo = new ProcessStartInfo(startupPath, arguments); + Process.Start(startInfo); + + Environment.Exit(0); + } + } +} diff --git a/UpdateLib/UpdateLib/Tasks/DownloadTask.cs b/UpdateLib/UpdateLib/Tasks/DownloadTask.cs index 800bc48..d9e3074 100644 --- a/UpdateLib/UpdateLib/Tasks/DownloadTask.cs +++ b/UpdateLib/UpdateLib/Tasks/DownloadTask.cs @@ -1,8 +1,10 @@ using MatthiWare.UpdateLib.Files; using MatthiWare.UpdateLib.Logging; +using MatthiWare.UpdateLib.Security; using System; using System.IO; using System.Net; +using System.Security; using System.Threading; using System.Windows.Forms; @@ -15,14 +17,18 @@ public class DownloadTask : AsyncTask public ListViewItem Item { get; private set; } public FileEntry Entry { get; private set; } - + public DownloadTask(ListViewItem item) + : this((FileEntry)item.Tag) { Item = item; - Entry = (FileEntry)Item.Tag; + } + public DownloadTask(FileEntry entry) + { + Entry = entry; webClient = new WebClient(); - webClient.DownloadProgressChanged += (o, e)=> { OnTaskProgressChanged(e); }; + webClient.DownloadProgressChanged += (o, e) => { OnTaskProgressChanged(e); }; webClient.DownloadFileCompleted += (o, e) => { wait.Set(); }; } @@ -44,6 +50,11 @@ protected override void DoWork() wait.WaitOne(); wait.Close(); wait = null; + + string hash = HashUtil.GetHash(localFile); + + if (hash.Length != Entry.Hash.Length || hash != Entry.Hash) + throw new InvalidHashException($"Calculated hash doesn't match provided hash for file: {localFile}"); } public override void Cancel() diff --git a/UpdateLib/UpdateLib/UI/Components/UpdatePage.cs b/UpdateLib/UpdateLib/UI/Components/UpdatePage.cs index 4976acb..1d40647 100644 --- a/UpdateLib/UpdateLib/UI/Components/UpdatePage.cs +++ b/UpdateLib/UpdateLib/UI/Components/UpdatePage.cs @@ -7,6 +7,7 @@ using MatthiWare.UpdateLib.Tasks; using MatthiWare.UpdateLib.Security; using MatthiWare.UpdateLib.Logging; +using MatthiWare.UpdateLib.Threading; namespace MatthiWare.UpdateLib.UI.Components { @@ -17,7 +18,7 @@ public partial class UpdatePage : UserControl, IWizardPage public event EventHandler PageUpdate; - private int amountToDownload; + private AtomicInteger amountToDownload = new AtomicInteger(); public UpdatePage(UpdaterForm parent) { @@ -52,7 +53,7 @@ private ImageList MakeImageList() private void FillListView() { - amountToDownload = UpdateFile.Count; + amountToDownload.Value = UpdateFile.Count; lvItems.BeginUpdate(); @@ -99,10 +100,8 @@ public void StartUpdate() private void Task_TaskCompleted(object sender, AsyncCompletedEventArgs e) { DownloadTask task = (DownloadTask)sender; - - int amountLeft = Interlocked.Decrement(ref amountToDownload); - - if (amountLeft == 0) + + if (amountToDownload.Decrement() == 0) { IsBusy = false; IsDone = true; @@ -131,34 +130,11 @@ private void Task_TaskCompleted(object sender, AsyncCompletedEventArgs e) return; } - // Everything went good lets just check the hash again to be a bit more secure against attacks - if (!VerifyDownloadedFileSignature(task)) - { - Logger.Error(nameof(DownloadTask), $"Signature match fail for file: {task.Entry.Name}"); - - task.Cancel(); - - SetSubItemText(task.Item.SubItems[2], "Error"); - - SetImageKey(task.Item, "status_error"); - - return; - } - - SetSubItemText(task.Item.SubItems[2], "Done"); SetImageKey(task.Item, "status_done"); } - private bool VerifyDownloadedFileSignature(DownloadTask task) - { - string localFile = Updater.Instance.Converter.Replace(task.Entry.DestinationLocation); - string md5local = HashUtil.GetHash(localFile); - - return md5local.Equals(task.Entry.Hash); - } - private void Task_TaskProgressChanged(object sender, ProgressChangedEventArgs e) { DownloadTask task = (DownloadTask)sender; @@ -178,44 +154,6 @@ private void StartDownloadItem(ListViewItem item) } - Random rnd = new Random(); - - - - private void Test(ListViewItem item) - { - - int wait = rnd.Next(2000); - - Thread.Sleep(wait); - - SetImageKey(item, "status_download"); - SetSubItemText(item.SubItems[2], "Downloading.."); - - wait = rnd.Next(100); - for (int i = 0; i <= 100; i++) - { - - Thread.Sleep(wait); - } - - bool val = rnd.Next(0, 2) == 0 ? false : true; - SetSubItemText(item.SubItems[2], val ? "Done" : "Error"); - - SetImageKey(item, val ? "status_done" : "status_error"); - - - int amountLeft = Interlocked.Decrement(ref amountToDownload); - - if (amountLeft != 0) - return; - - IsBusy = false; - IsDone = true; - - PageUpdate?.Invoke(this, new EventArgs()); - } - private void SetImageKey(ListViewItem item, string key) { this.InvokeOnUI(() => item.ImageKey = key); diff --git a/UpdateLib/UpdateLib/UpdateLib.csproj b/UpdateLib/UpdateLib/UpdateLib.csproj index 4580039..4f598af 100644 --- a/UpdateLib/UpdateLib/UpdateLib.csproj +++ b/UpdateLib/UpdateLib/UpdateLib.csproj @@ -69,9 +69,11 @@ + + @@ -128,6 +130,7 @@ + diff --git a/UpdateLib/UpdateLib/Updater.cs b/UpdateLib/UpdateLib/Updater.cs index ca6d846..755b270 100644 --- a/UpdateLib/UpdateLib/Updater.cs +++ b/UpdateLib/UpdateLib/Updater.cs @@ -8,6 +8,7 @@ using MatthiWare.UpdateLib.Logging.Writers; using MatthiWare.UpdateLib.Logging; using System.Security; +using System.Diagnostics; namespace MatthiWare.UpdateLib { @@ -34,6 +35,8 @@ public static Updater Instance } #endregion + private int m_parentPid; + public event EventHandler CheckForUpdatesCompleted; private string m_updateUrl = ""; @@ -51,9 +54,12 @@ public string UpdateURL public bool EnableCmdArguments { get; set; } = true; public bool UpdateSilently { get; set; } = false; - public string UpdateSilentlyCmdArg { get; set; } = "-silent"; + public string UpdateSilentlyCmdArg { get; set; } = "--silent"; + + public string StartUpdatingCmdArg { get; set; } = "--update"; - public string StartUpdatingCmdArg { get; set; } = "-update"; + public bool WaitForProcessExit { get; set; } + public string WaitForProcessCmdArg { get; set; } = "--wait"; public bool ShowUpdateMessage { get; set; } = true; public bool ShowMessageOnNoUpdate { get; set; } = true; @@ -113,6 +119,13 @@ public Updater ConfigureUpdateCmdArg(string cmdArg) return this; } + public Updater ConfigureWaitForProcessCmdArg(string cmdArg) + { + WaitForProcessCmdArg = cmdArg; + + return this; + } + #endregion /// @@ -135,6 +148,9 @@ public void Initialize() bool shouldStartUpdating = ParseCmdArguments(Environment.GetCommandLineArgs()); + if (WaitForProcessExit) + WaitForProcessToExit(m_parentPid); + if (shouldStartUpdating) CheckForUpdates(); } @@ -153,17 +169,31 @@ private void StartInitializationTasks() private bool ParseCmdArguments(string[] args) { bool startUpdating = false; - foreach (string arg in args) + for (int i = 0; i < args.Length; i++) { - if (arg == StartUpdatingCmdArg) + if (args[i] == StartUpdatingCmdArg) startUpdating = true; - else if (arg == UpdateSilentlyCmdArg) + else if (args[i] == UpdateSilentlyCmdArg) UpdateSilently = true; + else if (args[i] == WaitForProcessCmdArg) + if (i + 1 <= args.Length && int.TryParse(args[i + 1], out m_parentPid)) + { + i++; + WaitForProcessExit = true; + } + } return startUpdating; } + private void WaitForProcessToExit(int pid) + { + Process watchdog = Process.GetProcessById(pid); + watchdog.CloseMainWindow(); + watchdog.WaitForExit(); + } + /// /// Starting the update process /// diff --git a/UpdateLib/UpdateLib/Utils/ExtensionMethods.cs b/UpdateLib/UpdateLib/Utils/ExtensionMethods.cs new file mode 100644 index 0000000..d50ccc1 --- /dev/null +++ b/UpdateLib/UpdateLib/Utils/ExtensionMethods.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace MatthiWare.UpdateLib.Utils +{ + public static class ExtensionMethods + { + + public static string AppendAll(this IEnumerable collection, string seperator) + { + using (var enumerator = collection.GetEnumerator()) + { + if (!enumerator.MoveNext()) + return string.Empty; + + var builder = new StringBuilder().Append(enumerator.Current); + + while (enumerator.MoveNext()) + builder.Append(seperator).Append(enumerator.Current); + + return builder.ToString(); + } + + } + + } +} From c666c50912715b3753ec6ea1d65fbda71e73adf4 Mon Sep 17 00:00:00 2001 From: Matthias Beerens Date: Wed, 24 May 2017 21:29:24 +0200 Subject: [PATCH 07/16] Added restart code --- UpdateLib/TestApp/Program.cs | 5 +- UpdateLib/UpdateLib/Files/HashCacheFile.cs | 13 +- .../Logging/Writers/FileLogWriter.cs | 13 +- UpdateLib/UpdateLib/Tasks/DownloadManager.cs | 27 +--- UpdateLib/UpdateLib/UI/UpdaterForm.cs | 11 +- UpdateLib/UpdateLib/UpdateLib.csproj | 1 + UpdateLib/UpdateLib/Updater.cs | 152 ++++++++++++------ UpdateLib/UpdateLib/Utils/IOUtils.cs | 42 +++++ 8 files changed, 156 insertions(+), 108 deletions(-) create mode 100644 UpdateLib/UpdateLib/Utils/IOUtils.cs diff --git a/UpdateLib/TestApp/Program.cs b/UpdateLib/TestApp/Program.cs index 64c35cc..f1098e3 100644 --- a/UpdateLib/TestApp/Program.cs +++ b/UpdateLib/TestApp/Program.cs @@ -35,10 +35,11 @@ private static void SetupLogging() private static void InitializeUpdater() { // Set update url - Updater.Instance.UpdateURL = "https://raw.githubusercontent.com/MatthiWare/UpdateLib.TestApp.UpdateExample/master/Dev/updatefile.xml"; - //Updater.Instance.UpdateURL = "http://matthiware.dev/UpdateLib/Dev/updatefile.xml"; + //Updater.Instance.UpdateURL = "https://raw.githubusercontent.com/MatthiWare/UpdateLib.TestApp.UpdateExample/master/Dev/updatefile.xml"; + Updater.Instance.UpdateURL = "http://matthiware.dev/UpdateLib/Dev/updatefile.xml"; Updater.Instance + .ConfigureUnsafeConnections(true) .ConfigureInstallationMode(InstallationMode.Shared) .Initialize(); } diff --git a/UpdateLib/UpdateLib/Files/HashCacheFile.cs b/UpdateLib/UpdateLib/Files/HashCacheFile.cs index b8d30b9..7bc03e4 100644 --- a/UpdateLib/UpdateLib/Files/HashCacheFile.cs +++ b/UpdateLib/UpdateLib/Files/HashCacheFile.cs @@ -27,7 +27,7 @@ public HashCacheFile() #region Save/Load private static string GetStoragePath() { - string path = GetPathPrefix(); + string path = IOUtils.GetAppDataPath(); string productName = GetProductName(); string name = Assembly.GetEntryAssembly().GetName().Name; @@ -40,16 +40,7 @@ private static string GetProductName() return attr?.Product ?? ""; } - private static string GetPathPrefix() - { - switch (Updater.Instance.InstallationMode) - { - case InstallationMode.Local: - return Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData); - default: - return Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData); - } - } + public static HashCacheFile Load() { diff --git a/UpdateLib/UpdateLib/Logging/Writers/FileLogWriter.cs b/UpdateLib/UpdateLib/Logging/Writers/FileLogWriter.cs index 0ac67f0..777237c 100644 --- a/UpdateLib/UpdateLib/Logging/Writers/FileLogWriter.cs +++ b/UpdateLib/UpdateLib/Logging/Writers/FileLogWriter.cs @@ -20,7 +20,7 @@ public class FileLogWriter : ILogWriter private static FileInfo GetLogFile() { - string path = GetPathPrefix(); + string path = IOUtils.GetAppDataPath(); string productName = GetProductName(); string name = Assembly.GetEntryAssembly().GetName().Name; @@ -38,17 +38,6 @@ private static string GetProductName() return attr?.Product ?? ""; } - private static string GetPathPrefix() - { - switch (Updater.Instance.InstallationMode) - { - case InstallationMode.Local: - return Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData); - default: - return Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData); - } - } - public void Log(string text) { Action logAction = new Action(LogAsync); diff --git a/UpdateLib/UpdateLib/Tasks/DownloadManager.cs b/UpdateLib/UpdateLib/Tasks/DownloadManager.cs index da15d89..87689b9 100644 --- a/UpdateLib/UpdateLib/Tasks/DownloadManager.cs +++ b/UpdateLib/UpdateLib/Tasks/DownloadManager.cs @@ -62,9 +62,7 @@ private void Task_TaskCompleted(object sender, System.ComponentModel.AsyncComple } if (amountToDownload.Decrement() == 0) - { - RestartApp(); - } + Updater.Instance.RestartApp(); } private void CancelOtherTasks() @@ -73,27 +71,6 @@ private void CancelOtherTasks() task.Cancel(); } - private void RestartApp() - { - Updater instance = Updater.Instance; - List args = new List(Environment.GetCommandLineArgs()); - - if (instance.EnableCmdArguments) - for (int i = 0; i < args.Count; i++) - if (args[i] == instance.StartUpdatingCmdArg || args[i] == instance.UpdateSilentlyCmdArg) - args[i] = string.Empty; - - args.Add(instance.WaitForProcessCmdArg); - args.Add(Process.GetCurrentProcess().Id.ToString()); - - string arguments = args.Where(a => !string.IsNullOrEmpty(a)).Distinct().AppendAll(" "); - - string startupPath = Application.StartupPath; - - ProcessStartInfo startInfo = new ProcessStartInfo(startupPath, arguments); - Process.Start(startInfo); - - Environment.Exit(0); - } + } } diff --git a/UpdateLib/UpdateLib/UI/UpdaterForm.cs b/UpdateLib/UpdateLib/UI/UpdaterForm.cs index 559e310..ff2a828 100644 --- a/UpdateLib/UpdateLib/UI/UpdaterForm.cs +++ b/UpdateLib/UpdateLib/UI/UpdaterForm.cs @@ -133,16 +133,7 @@ private void ExitUpdater() { if (NeedsRestart) { - //Process current = Process.GetCurrentProcess(); - //Process[] processes = Process.GetProcessesByName(current.ProcessName); - //foreach(Process p in processes) - //{ - // if (current != p) - // p.Kill(); - //} - - Application.Restart(); - Environment.Exit(0); + Updater.Instance.RestartApp(); } else { diff --git a/UpdateLib/UpdateLib/UpdateLib.csproj b/UpdateLib/UpdateLib/UpdateLib.csproj index 4f598af..565a8e5 100644 --- a/UpdateLib/UpdateLib/UpdateLib.csproj +++ b/UpdateLib/UpdateLib/UpdateLib.csproj @@ -131,6 +131,7 @@ + diff --git a/UpdateLib/UpdateLib/Updater.cs b/UpdateLib/UpdateLib/Updater.cs index 755b270..2fb2e41 100644 --- a/UpdateLib/UpdateLib/Updater.cs +++ b/UpdateLib/UpdateLib/Updater.cs @@ -9,6 +9,10 @@ using MatthiWare.UpdateLib.Logging; using System.Security; using System.Diagnostics; +using System.Linq; +using MatthiWare.UpdateLib.Utils; +using System.Collections.Generic; +using System.Reflection; namespace MatthiWare.UpdateLib { @@ -35,37 +39,38 @@ public static Updater Instance } #endregion + #region Fields + private int m_parentPid; + private string m_updateUrl = ""; + + #endregion + + #region Events public event EventHandler CheckForUpdatesCompleted; - private string m_updateUrl = ""; + #endregion + + #region Properties + public string UpdateURL { get { return m_updateUrl; } set { m_updateUrl = value; - RemoteBasePath = GetRemoteBasePath(); + RemoteBasePath = IOUtils.GetRemoteBasePath(value); } } - public InstallationMode InstallationMode { get; set; } = InstallationMode.Shared; - - public bool EnableCmdArguments { get; set; } = true; + public bool StartUpdating { get; set; } = false; public bool UpdateSilently { get; set; } = false; public string UpdateSilentlyCmdArg { get; set; } = "--silent"; - public string StartUpdatingCmdArg { get; set; } = "--update"; - public bool WaitForProcessExit { get; set; } public string WaitForProcessCmdArg { get; set; } = "--wait"; - - public bool ShowUpdateMessage { get; set; } = true; - public bool ShowMessageOnNoUpdate { get; set; } = true; - public bool ShowErrorMessage { get; set; } = true; public PathVariableConverter Converter { get; private set; } - public bool AllowUnsafeConnection { get; set; } = false; /// @@ -82,8 +87,16 @@ public string UpdateURL internal string RemoteBasePath { get; set; } + #endregion + #region Fluent API + /// + /// Configures if unsafe connections are allowed + /// + /// Do not enable this unless you know what you are doing + /// Allowed? + /// public Updater ConfigureUnsafeConnections(bool allow) { AllowUnsafeConnection = allow; @@ -91,6 +104,11 @@ public Updater ConfigureUnsafeConnections(bool allow) return this; } + /// + /// Configures the installation mode for the client + /// + /// The + /// public Updater ConfigureInstallationMode(InstallationMode mode) { InstallationMode = mode; @@ -98,13 +116,11 @@ public Updater ConfigureInstallationMode(InstallationMode mode) return this; } - public Updater ConfigureCmdArgs(bool enabled) - { - EnableCmdArguments = enabled; - - return this; - } - + /// + /// Configures the update silently command switch + /// + /// Command name + /// public Updater ConfigureSilentCmdArg(string cmdArg) { UpdateSilentlyCmdArg = cmdArg; @@ -112,6 +128,11 @@ public Updater ConfigureSilentCmdArg(string cmdArg) return this; } + /// + /// Configures the update command switch + /// + /// Command name + /// public Updater ConfigureUpdateCmdArg(string cmdArg) { StartUpdatingCmdArg = cmdArg; @@ -119,6 +140,11 @@ public Updater ConfigureUpdateCmdArg(string cmdArg) return this; } + /// + /// Configures the wait for process to exit command switch + /// + /// Command name + /// public Updater ConfigureWaitForProcessCmdArg(string cmdArg) { WaitForProcessCmdArg = cmdArg; @@ -143,18 +169,18 @@ public void Initialize() { StartInitializationTasks(); - if (!EnableCmdArguments) - return; - - bool shouldStartUpdating = ParseCmdArguments(Environment.GetCommandLineArgs()); + ParseCmdArguments(Environment.GetCommandLineArgs()); if (WaitForProcessExit) WaitForProcessToExit(m_parentPid); - if (shouldStartUpdating) + if (StartUpdating) CheckForUpdates(); } + /// + /// Starts the initialization tasks + /// private void StartInitializationTasks() { CleanUpTask = new CleanUpTask("."); @@ -166,32 +192,40 @@ private void StartInitializationTasks() IsInitialized = true; } - private bool ParseCmdArguments(string[] args) + /// + /// Parses the given arguments + /// + /// The arguments to parse + private void ParseCmdArguments(string[] args) { - bool startUpdating = false; for (int i = 0; i < args.Length; i++) { - if (args[i] == StartUpdatingCmdArg) - startUpdating = true; - else if (args[i] == UpdateSilentlyCmdArg) + if (!string.IsNullOrEmpty(StartUpdatingCmdArg) && args[i] == StartUpdatingCmdArg) + StartUpdating = true; + else if (!string.IsNullOrEmpty(UpdateSilentlyCmdArg) && args[i] == UpdateSilentlyCmdArg) UpdateSilently = true; - else if (args[i] == WaitForProcessCmdArg) + else if (!string.IsNullOrEmpty(WaitForProcessCmdArg) && args[i] == WaitForProcessCmdArg) if (i + 1 <= args.Length && int.TryParse(args[i + 1], out m_parentPid)) { i++; WaitForProcessExit = true; } - } - - return startUpdating; } + /// + /// Waits for a process to exit on the current thread + /// + /// Process ID private void WaitForProcessToExit(int pid) { - Process watchdog = Process.GetProcessById(pid); - watchdog.CloseMainWindow(); - watchdog.WaitForExit(); + Process[] processes = Process.GetProcesses(); + Process toWatch = processes.FirstOrDefault(p => p.Id == pid); + + if (toWatch == null) return; + + toWatch.CloseMainWindow(); + toWatch.WaitForExit(); } /// @@ -281,27 +315,49 @@ public CheckForUpdatesTask CheckForUpdatesAsync(IWin32Window owner) /// The of the current application public HashCacheFile GetCache() { + if (!IsInitialized) throw new InvalidOperationException("Updater has not been initialized yet!"); + return UpdateCacheTask.AwaitTask().Result; } - private string GetRemoteBasePath() + /// + /// Updates without user interaction + /// + /// The update specifications file + private void UpdateWithoutGUI(UpdateFile file) + { + DownloadManager downloader = new DownloadManager(file); + downloader.Update(); + } + + internal void RestartApp() { - string[] tokens = UpdateURL.Split('/'); - StringBuilder builder = new StringBuilder(); + List args = new List(Environment.GetCommandLineArgs()); - for (int i = 0; i < tokens.Length - 1; i++) + for (int i = 0; i < args.Count; i++) { - builder.Append(tokens[i]); - builder.Append('/'); + if (args[i] == StartUpdatingCmdArg || args[i] == UpdateSilentlyCmdArg) + { + args[i] = string.Empty; + } + else if (args[i] == WaitForProcessCmdArg) + { + args[i] = string.Empty; + if (i + 1 < args.Count) + args[++i] = string.Empty; + } } - return builder.ToString(); - } + args.Add(instance.WaitForProcessCmdArg); + args.Add(Process.GetCurrentProcess().Id.ToString()); - private void UpdateWithoutGUI(UpdateFile file) - { - DownloadManager downloader = new DownloadManager(file); - downloader.Update(); + string arguments = args.Where(a => !string.IsNullOrEmpty(a)).Distinct().AppendAll(" "); + + ProcessStartInfo startInfo = new ProcessStartInfo(Assembly.GetEntryAssembly().Location, arguments); + startInfo.UseShellExecute = false; + Process.Start(startInfo); + + Environment.Exit(0); } } diff --git a/UpdateLib/UpdateLib/Utils/IOUtils.cs b/UpdateLib/UpdateLib/Utils/IOUtils.cs new file mode 100644 index 0000000..fb754cf --- /dev/null +++ b/UpdateLib/UpdateLib/Utils/IOUtils.cs @@ -0,0 +1,42 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace MatthiWare.UpdateLib.Utils +{ + public static class IOUtils + { + + public static string GetRemoteBasePath(string url) + { + if (string.IsNullOrEmpty(url)) throw new ArgumentNullException(nameof(url)); + + const char splitter = '/'; + + string[] tokens = url.Split(splitter); + StringBuilder builder = new StringBuilder(); + + for (int i = 0; i < tokens.Length - 1; i++) + { + builder.Append(tokens[i]); + builder.Append(splitter); + } + + return builder.ToString(); + } + + public static string GetAppDataPath() + { + switch (Updater.Instance.InstallationMode) + { + case InstallationMode.Local: + return Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData); + case InstallationMode.Shared: + default: + return Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData); + } + } + + } +} From 5b85fddaad21388b07a9f72e07307f2142c9bc54 Mon Sep 17 00:00:00 2001 From: Matthias Beerens Date: Thu, 25 May 2017 15:46:11 +0200 Subject: [PATCH 08/16] Comments + small refactor --- UpdateLib/UpdateLib/Updater.cs | 110 +++++++++++++++++++++++++----- UpdateLib/UpdateLib/Utils/Lazy.cs | 8 +++ 2 files changed, 102 insertions(+), 16 deletions(-) diff --git a/UpdateLib/UpdateLib/Updater.cs b/UpdateLib/UpdateLib/Updater.cs index 2fb2e41..c6509e5 100644 --- a/UpdateLib/UpdateLib/Updater.cs +++ b/UpdateLib/UpdateLib/Updater.cs @@ -13,6 +13,7 @@ using MatthiWare.UpdateLib.Utils; using System.Collections.Generic; using System.Reflection; +using System.IO; namespace MatthiWare.UpdateLib { @@ -41,19 +42,30 @@ public static Updater Instance #region Fields - private int m_parentPid; + private int m_pid; private string m_updateUrl = ""; + private string m_argUpdateSilent = "--silent"; + private string m_argUpdate = "--update"; + private string m_argWait = "--wait"; + private Lazy m_lazyPathVarConv = new Lazy(() => new PathVariableConverter()); #endregion #region Events + /// + /// Check for updates completed event. + /// public event EventHandler CheckForUpdatesCompleted; #endregion #region Properties + /// + /// Gets or sets the url to update from + /// + /// If you want to specify an unsafe connection you should enable public string UpdateURL { get { return m_updateUrl; } @@ -63,14 +75,73 @@ public string UpdateURL RemoteBasePath = IOUtils.GetRemoteBasePath(value); } } + + /// + /// Gets or sets the Updater Installation mode + /// public InstallationMode InstallationMode { get; set; } = InstallationMode.Shared; + + /// + /// Gets or sets if we want to check for updates before the actual program is loaded. + /// public bool StartUpdating { get; set; } = false; + + /// + /// Gets or sets if we want to update silently (no UI interaction). + /// public bool UpdateSilently { get; set; } = false; - public string UpdateSilentlyCmdArg { get; set; } = "--silent"; - public string StartUpdatingCmdArg { get; set; } = "--update"; + + /// + /// Gets or sets the command line argument for the silent switch + /// If this argument has been set and is passed in the command line it will set to True + /// + public string UpdateSilentlyCmdArg + { + get { return m_argUpdateSilent; } + set { m_argUpdateSilent = SetAndVerifyCmdArgument(value); } + } + + /// + /// Gets or sets the command line argument for the update switch + /// If this argument has been set and is passed in the command line it will set to True + /// + public string StartUpdatingCmdArg + { + get { return m_argUpdate; } + set { m_argUpdate = SetAndVerifyCmdArgument(value); } + } + + /// + /// Gets or sets the command line argument for the wait switch + /// If this argument has been set and passed in the command line followed by an it will set to True + /// + public string WaitForProcessCmdArg + { + get { return m_argWait; } + set { m_argWait = SetAndVerifyCmdArgument(value); } + } + + /// + /// Indicates if we want to wait for the given process to wait + /// See and + /// If this property has been set to true it will wait for the to exit before continuing when has been called. + /// public bool WaitForProcessExit { get; set; } - public string WaitForProcessCmdArg { get; set; } = "--wait"; - public PathVariableConverter Converter { get; private set; } + + /// + /// Gets the . + /// This property is only initialized when called. + /// + public PathVariableConverter Converter + { + get { return m_lazyPathVarConv.Value; } + private set { m_lazyPathVarConv.Value = value; } + } + + /// + /// Gets or sets if the updater allows unsafe connection + /// `True` to allow HTTP connections, `False` to only allow HTTPS connections + /// public bool AllowUnsafeConnection { get; set; } = false; /// @@ -83,8 +154,14 @@ public string UpdateURL /// public UpdateCacheTask UpdateCacheTask { get; private set; } + /// + /// Is the updater already initialized? + /// public bool IsInitialized { get; private set; } + /// + /// The remote base path to use for downloading etc.. + /// internal string RemoteBasePath { get; set; } #endregion @@ -157,10 +234,7 @@ public Updater ConfigureWaitForProcessCmdArg(string cmdArg) /// /// Initializes a new instance of with the default settings. /// - private Updater() - { - Converter = new PathVariableConverter(); - } + private Updater() { } /// /// Initializes the updater @@ -172,7 +246,7 @@ public void Initialize() ParseCmdArguments(Environment.GetCommandLineArgs()); if (WaitForProcessExit) - WaitForProcessToExit(m_parentPid); + WaitForProcessToExit(m_pid); if (StartUpdating) CheckForUpdates(); @@ -204,12 +278,8 @@ private void ParseCmdArguments(string[] args) StartUpdating = true; else if (!string.IsNullOrEmpty(UpdateSilentlyCmdArg) && args[i] == UpdateSilentlyCmdArg) UpdateSilently = true; - else if (!string.IsNullOrEmpty(WaitForProcessCmdArg) && args[i] == WaitForProcessCmdArg) - if (i + 1 <= args.Length && int.TryParse(args[i + 1], out m_parentPid)) - { - i++; - WaitForProcessExit = true; - } + else if (!string.IsNullOrEmpty(WaitForProcessCmdArg) && args[i] == WaitForProcessCmdArg && i + 1 < args.Length && int.TryParse(args[++i], out m_pid)) + WaitForProcessExit = true; } } @@ -360,5 +430,13 @@ internal void RestartApp() Environment.Exit(0); } + private string SetAndVerifyCmdArgument(string value) + { + if (value.Contains(' ')) + throw new ArgumentException("Command line argument can not contain spaces"); + + return value; + } + } } diff --git a/UpdateLib/UpdateLib/Utils/Lazy.cs b/UpdateLib/UpdateLib/Utils/Lazy.cs index 3738154..bea660d 100644 --- a/UpdateLib/UpdateLib/Utils/Lazy.cs +++ b/UpdateLib/UpdateLib/Utils/Lazy.cs @@ -35,6 +35,14 @@ public T Value return m_storedValue; } + set + { + lock (sync) + { + m_initialized = true; + m_storedValue = value; + } + } } /// From 18754ddc0536a77f09e56a9b3198902626981056 Mon Sep 17 00:00:00 2001 From: Matthias Beerens Date: Thu, 25 May 2017 19:28:31 +0200 Subject: [PATCH 09/16] Add ConfigureUpdateUrl --- UpdateLib/TestApp/Program.cs | 6 ++---- UpdateLib/UpdateLib/Tasks/CheckForUpdatesTask.cs | 7 ++++--- UpdateLib/UpdateLib/Updater.cs | 15 ++++++++++++++- 3 files changed, 20 insertions(+), 8 deletions(-) diff --git a/UpdateLib/TestApp/Program.cs b/UpdateLib/TestApp/Program.cs index f1098e3..d463264 100644 --- a/UpdateLib/TestApp/Program.cs +++ b/UpdateLib/TestApp/Program.cs @@ -34,11 +34,9 @@ private static void SetupLogging() private static void InitializeUpdater() { - // Set update url - //Updater.Instance.UpdateURL = "https://raw.githubusercontent.com/MatthiWare/UpdateLib.TestApp.UpdateExample/master/Dev/updatefile.xml"; - Updater.Instance.UpdateURL = "http://matthiware.dev/UpdateLib/Dev/updatefile.xml"; - Updater.Instance + //.ConfigureUpdateUrl("https://raw.githubusercontent.com/MatthiWare/UpdateLib.TestApp.UpdateExample/master/Dev/updatefile.xml") + .ConfigureUpdateUrl("http://matthiware.dev/UpdateLib/Dev/updatefile.xml") .ConfigureUnsafeConnections(true) .ConfigureInstallationMode(InstallationMode.Shared) .Initialize(); diff --git a/UpdateLib/UpdateLib/Tasks/CheckForUpdatesTask.cs b/UpdateLib/UpdateLib/Tasks/CheckForUpdatesTask.cs index a92e834..95877c3 100644 --- a/UpdateLib/UpdateLib/Tasks/CheckForUpdatesTask.cs +++ b/UpdateLib/UpdateLib/Tasks/CheckForUpdatesTask.cs @@ -62,13 +62,14 @@ private CheckForUpdatedFilesTask CheckForUpdatedFiles(UpdateFile file, HashCache private string GetLocalFileName() { - string[] tokens = Url.Split('/'); - return string.Concat("./", tokens[tokens.Length - 1]); + const char slash = '/'; + string[] tokens = Url.Split(slash); + return string.Concat(".", slash, tokens[tokens.Length - 1]); } public class Data { - public string Version { get; set; } = ""; + public string Version { get; set; } = string.Empty; public bool UpdateAvailable { get; set; } = false; public UpdateFile UpdateFile { get; set; } } diff --git a/UpdateLib/UpdateLib/Updater.cs b/UpdateLib/UpdateLib/Updater.cs index c6509e5..baa8adc 100644 --- a/UpdateLib/UpdateLib/Updater.cs +++ b/UpdateLib/UpdateLib/Updater.cs @@ -17,7 +17,7 @@ namespace MatthiWare.UpdateLib { - public class Updater + public sealed class Updater { #region Singleton private static volatile Updater instance = null; @@ -229,6 +229,19 @@ public Updater ConfigureWaitForProcessCmdArg(string cmdArg) return this; } + /// + /// Configures the update url + /// + /// To use HTTP you should enable + /// Url to update from + /// + public Updater ConfigureUpdateUrl(string url) + { + UpdateURL = url; + + return this; + } + #endregion /// From cbb92ac3799d4507dde18655a02fe82f7c240a8c Mon Sep 17 00:00:00 2001 From: Matthias Beerens Date: Sat, 27 May 2017 21:25:36 +0200 Subject: [PATCH 10/16] Refactor logging into the Updater class --- UpdateLib/TestApp/Form1.Designer.cs | 13 ++++ UpdateLib/TestApp/Form1.cs | 7 +- UpdateLib/TestApp/Program.cs | 21 ++---- UpdateLib/TestApp/Testing/DummyTask.cs | 5 +- UpdateLib/UpdateLib.Generator/Program.cs | 2 +- .../UI/Pages/BuilderPage.cs | 2 +- .../UpdateLib.Tests/Logging/LoggingTests.cs | 36 +++++---- UpdateLib/UpdateLib/Files/HashCacheEntry.cs | 4 +- UpdateLib/UpdateLib/Logging/ILogger.cs | 19 +++++ UpdateLib/UpdateLib/Logging/Logger.cs | 22 +++--- UpdateLib/UpdateLib/Tasks/AsyncTask.cs | 6 +- .../UpdateLib/Tasks/CheckForUpdatesTask.cs | 24 +++++- UpdateLib/UpdateLib/Tasks/CleanUpTask.cs | 2 +- UpdateLib/UpdateLib/Tasks/DownloadManager.cs | 2 +- UpdateLib/UpdateLib/Tasks/DownloadTask.cs | 4 +- UpdateLib/UpdateLib/Tasks/UpdateCacheTask.cs | 12 +-- UpdateLib/UpdateLib/Tasks/WorkerScheduler.cs | 4 +- .../UpdateLib/UI/Components/UpdatePage.cs | 4 +- UpdateLib/UpdateLib/UI/UpdaterForm.cs | 2 - UpdateLib/UpdateLib/UpdateLib.csproj | 1 + UpdateLib/UpdateLib/Updater.cs | 73 ++++++++++++++++--- 21 files changed, 184 insertions(+), 81 deletions(-) create mode 100644 UpdateLib/UpdateLib/Logging/ILogger.cs diff --git a/UpdateLib/TestApp/Form1.Designer.cs b/UpdateLib/TestApp/Form1.Designer.cs index 59b35cd..863977a 100644 --- a/UpdateLib/TestApp/Form1.Designer.cs +++ b/UpdateLib/TestApp/Form1.Designer.cs @@ -64,6 +64,7 @@ private void InitializeComponent() this.label2 = new System.Windows.Forms.Label(); this.label3 = new System.Windows.Forms.Label(); this.button1 = new System.Windows.Forms.Button(); + this.button2 = new System.Windows.Forms.Button(); this.menuStrip1.SuspendLayout(); this.SuspendLayout(); // @@ -350,11 +351,22 @@ private void InitializeComponent() this.button1.UseVisualStyleBackColor = true; this.button1.Click += new System.EventHandler(this.button1_Click); // + // button2 + // + this.button2.Location = new System.Drawing.Point(282, 132); + this.button2.Name = "button2"; + this.button2.Size = new System.Drawing.Size(75, 23); + this.button2.TabIndex = 5; + this.button2.Text = "button2"; + this.button2.UseVisualStyleBackColor = true; + this.button2.Click += new System.EventHandler(this.button2_Click); + // // Form1 // this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; this.ClientSize = new System.Drawing.Size(490, 266); + this.Controls.Add(this.button2); this.Controls.Add(this.button1); this.Controls.Add(this.label3); this.Controls.Add(this.label2); @@ -408,6 +420,7 @@ private void InitializeComponent() private System.Windows.Forms.Label label2; private System.Windows.Forms.Label label3; private System.Windows.Forms.Button button1; + private System.Windows.Forms.Button button2; } } diff --git a/UpdateLib/TestApp/Form1.cs b/UpdateLib/TestApp/Form1.cs index 6030dc0..ded7dea 100644 --- a/UpdateLib/TestApp/Form1.cs +++ b/UpdateLib/TestApp/Form1.cs @@ -102,8 +102,13 @@ private string ReadFileAndKeepStreamOpen(string file) private void button1_Click(object sender, EventArgs e) { DummyTask task = new DummyTask(); - task.TaskCompleted += (o, ex) => Logger.Debug(nameof(DummyTask), "Callback task completed!"); + task.TaskCompleted += (o, ex) => Updater.Instance.Logger.Debug(nameof(DummyTask), "Callback task completed!"); task.Start(); } + + private void button2_Click(object sender, EventArgs e) + { + //Updater.Instance.RestartApp(false, false, true, true); + } } } diff --git a/UpdateLib/TestApp/Program.cs b/UpdateLib/TestApp/Program.cs index d463264..a828d0c 100644 --- a/UpdateLib/TestApp/Program.cs +++ b/UpdateLib/TestApp/Program.cs @@ -20,28 +20,17 @@ static void Main() Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); - SetupLogging(); - InitializeUpdater(); - - Application.Run(new Form1()); - } - - private static void SetupLogging() - { - Logger.Writers.Add(new ConsoleLogWriter()); - Logger.Writers.Add(new FileLogWriter()); - } - - private static void InitializeUpdater() - { Updater.Instance //.ConfigureUpdateUrl("https://raw.githubusercontent.com/MatthiWare/UpdateLib.TestApp.UpdateExample/master/Dev/updatefile.xml") .ConfigureUpdateUrl("http://matthiware.dev/UpdateLib/Dev/updatefile.xml") + .ConfigureLogger((logger) => logger.LogLevel = LoggingLevel.Debug) + .ConfigureLogger((logger) => logger.Writers.Add(new ConsoleLogWriter())) + .ConfigureLogger((logger) => logger.Writers.Add(new FileLogWriter())) .ConfigureUnsafeConnections(true) .ConfigureInstallationMode(InstallationMode.Shared) .Initialize(); - } - + Application.Run(new Form1()); + } } } diff --git a/UpdateLib/TestApp/Testing/DummyTask.cs b/UpdateLib/TestApp/Testing/DummyTask.cs index 8178dc5..e5e16cf 100644 --- a/UpdateLib/TestApp/Testing/DummyTask.cs +++ b/UpdateLib/TestApp/Testing/DummyTask.cs @@ -1,4 +1,5 @@ -using MatthiWare.UpdateLib.Logging; +using MatthiWare.UpdateLib; +using MatthiWare.UpdateLib.Logging; using MatthiWare.UpdateLib.Tasks; using System; using System.Collections.Generic; @@ -26,7 +27,7 @@ private void ChildWorkStuff(int id) Thread.Sleep(waitTime); - Logger.Debug(nameof(ChildWorkStuff), $"Task[{id.ToString("X2")}] Completed"); + Updater.Instance.Logger.Debug(nameof(ChildWorkStuff), $"Task[{id.ToString("X2")}] Completed"); } diff --git a/UpdateLib/UpdateLib.Generator/Program.cs b/UpdateLib/UpdateLib.Generator/Program.cs index bfe5e27..aadcb66 100644 --- a/UpdateLib/UpdateLib.Generator/Program.cs +++ b/UpdateLib/UpdateLib.Generator/Program.cs @@ -15,7 +15,7 @@ static class Program [STAThread] static void Main() { - Logger.Writers.Add(new ConsoleLogWriter()); + Updater.Instance.ConfigureLogger((logger) => logger.Writers.Add(new ConsoleLogWriter())); Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); diff --git a/UpdateLib/UpdateLib.Generator/UI/Pages/BuilderPage.cs b/UpdateLib/UpdateLib.Generator/UI/Pages/BuilderPage.cs index 12de21a..27d396d 100644 --- a/UpdateLib/UpdateLib.Generator/UI/Pages/BuilderPage.cs +++ b/UpdateLib/UpdateLib.Generator/UI/Pages/BuilderPage.cs @@ -83,7 +83,7 @@ private AsyncTask Build(Stream s) { sw.Stop(); - Logger.Debug(GetType().Name, $"File generation completed in {sw.ElapsedMilliseconds} ms."); + Updater.Instance.Logger.Debug(GetType().Name, $"File generation completed in {sw.ElapsedMilliseconds} ms."); diff --git a/UpdateLib/UpdateLib.Tests/Logging/LoggingTests.cs b/UpdateLib/UpdateLib.Tests/Logging/LoggingTests.cs index d9bacf1..6af0988 100644 --- a/UpdateLib/UpdateLib.Tests/Logging/LoggingTests.cs +++ b/UpdateLib/UpdateLib.Tests/Logging/LoggingTests.cs @@ -13,17 +13,23 @@ namespace UpdateLib.Tests.Logging [TestFixture] public class LoggingTests { + private Logger logger; + [SetUp] + public void Setup() + { + logger = new Logger(); + } [Test] public void ErrorLogLevelShouldNotLogWhenDebugLog() { - Logger.LogLevel = LoggingLevel.Error; + logger.LogLevel = LoggingLevel.Error; Mock writer = SetUpWriter(LoggingLevel.Debug); - Logger.Writers.Add(writer.Object); + logger.Writers.Add(writer.Object); - Logger.Debug(nameof(LoggingTests), "This is my log msg"); + logger.Debug(nameof(LoggingTests), "This is my log msg"); writer.Verify(mock => mock.Log(It.IsAny()), Times.Never); } @@ -31,12 +37,12 @@ public void ErrorLogLevelShouldNotLogWhenDebugLog() [Test] public void DebugLogLevelShouldLogErrorLog() { - Logger.LogLevel = LoggingLevel.Debug; + logger.LogLevel = LoggingLevel.Debug; Mock writer = SetUpWriter(LoggingLevel.Error); - Logger.Writers.Add(writer.Object); + logger.Writers.Add(writer.Object); - Logger.Error(nameof(LoggingTests), "This is my log msg"); + logger.Error(nameof(LoggingTests), "This is my log msg"); writer.Verify(mock => mock.Log(It.IsAny()), Times.Once); } @@ -44,7 +50,7 @@ public void DebugLogLevelShouldLogErrorLog() [Test] public void ErrorLogLevelShouldNotLogAnyLowerLevel() { - Logger.LogLevel = LoggingLevel.Error; + logger.LogLevel = LoggingLevel.Error; Mock info = SetUpWriter(LoggingLevel.Info); @@ -52,14 +58,14 @@ public void ErrorLogLevelShouldNotLogAnyLowerLevel() Mock debug = SetUpWriter(LoggingLevel.Debug); - Logger.Writers.Add(info.Object); - Logger.Writers.Add(warn.Object); - Logger.Writers.Add(debug.Object); + logger.Writers.Add(info.Object); + logger.Writers.Add(warn.Object); + logger.Writers.Add(debug.Object); - Logger.Error("", ""); - Logger.Warn("", ""); - Logger.Info("", ""); - Logger.Debug("", ""); + logger.Error("", ""); + logger.Warn("", ""); + logger.Info("", ""); + logger.Debug("", ""); info.Verify(mock => mock.Log(It.IsAny()), Times.Never); warn.Verify(mock => mock.Log(It.IsAny()), Times.Never); @@ -77,7 +83,7 @@ private Mock SetUpWriter(LoggingLevel level) [TearDown] public void CleanUp() { - Logger.Writers.Clear(); + logger.Writers.Clear(); } } diff --git a/UpdateLib/UpdateLib/Files/HashCacheEntry.cs b/UpdateLib/UpdateLib/Files/HashCacheEntry.cs index 8d0373a..df95904 100644 --- a/UpdateLib/UpdateLib/Files/HashCacheEntry.cs +++ b/UpdateLib/UpdateLib/Files/HashCacheEntry.cs @@ -44,7 +44,7 @@ public void Recalculate(long tick) Hash = HashUtil.GetHash(FilePath); Ticks = tick; - Logger.Debug(GetType().Name, $"Recalculated Time: {DateTime.FromBinary(Ticks).ToString()} Name: {FilePath} Hash: {Hash}"); + Updater.Instance.Logger.Debug(GetType().Name, $"Recalculated Time: {DateTime.FromBinary(Ticks).ToString()} Name: {FilePath} Hash: {Hash}"); } } catch (Exception ex) // file might no longer exist or is in use @@ -52,7 +52,7 @@ public void Recalculate(long tick) Hash = string.Empty; Ticks = -1; - Logger.Error(GetType().Name, ex); + Updater.Instance.Logger.Error(GetType().Name, ex); } } diff --git a/UpdateLib/UpdateLib/Logging/ILogger.cs b/UpdateLib/UpdateLib/Logging/ILogger.cs new file mode 100644 index 0000000..fba6f22 --- /dev/null +++ b/UpdateLib/UpdateLib/Logging/ILogger.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace MatthiWare.UpdateLib.Logging +{ + public interface ILogger + { + LoggingLevel LogLevel { get; set; } + ICollection Writers { get; } + void Log(string tag, string msg, LoggingLevel level); + void Debug(string tag, string msg); + void Info(string tag, string msg); + void Warn(string tag, string msg); + void Error(string tag, string msg); + void Error(string tag, Exception e); + } +} diff --git a/UpdateLib/UpdateLib/Logging/Logger.cs b/UpdateLib/UpdateLib/Logging/Logger.cs index 0f4876c..6df8013 100644 --- a/UpdateLib/UpdateLib/Logging/Logger.cs +++ b/UpdateLib/UpdateLib/Logging/Logger.cs @@ -5,43 +5,45 @@ namespace MatthiWare.UpdateLib.Logging { - public static class Logger + public class Logger : ILogger { - public static LoggingLevel LogLevel { get; set; } = LoggingLevel.Debug; + public LoggingLevel LogLevel { get; set; } = LoggingLevel.Debug; - public static List Writers { get; } = new List(); + public ICollection Writers { get; } = new List(); - public static void Log(string tag, string msg, LoggingLevel level) + private const string TEMPLATE = "[{0}][{1}][{2}]: {3}"; + + public void Log(string tag, string msg, LoggingLevel level) { if (level < LogLevel) return; Writers .Where(w => w.LoggingLevel >= LogLevel && level >= w.LoggingLevel) .ToList() - .ForEach(w => w.Log($"[{DateTime.Now.ToString()}][{level.ToString()}][{tag}]: {msg}")); + .ForEach(w => w.Log(string.Format(TEMPLATE, DateTime.Now, level, tag, msg))); } - public static void Debug(string tag, string msg) + public void Debug(string tag, string msg) { Log(tag, msg, LoggingLevel.Debug); } - public static void Info(string tag, string msg) + public void Info(string tag, string msg) { Log(tag, msg, LoggingLevel.Info); } - public static void Warn(string tag, string msg) + public void Warn(string tag, string msg) { Log(tag, msg, LoggingLevel.Warn); } - public static void Error(string tag, string msg) + public void Error(string tag, string msg) { Log(tag, msg, LoggingLevel.Error); } - public static void Error(string tag, Exception e) + public void Error(string tag, Exception e) { Error(tag, e.ToString()); } diff --git a/UpdateLib/UpdateLib/Tasks/AsyncTask.cs b/UpdateLib/UpdateLib/Tasks/AsyncTask.cs index 9d434df..5d52b5d 100644 --- a/UpdateLib/UpdateLib/Tasks/AsyncTask.cs +++ b/UpdateLib/UpdateLib/Tasks/AsyncTask.cs @@ -199,7 +199,7 @@ public AsyncTask Start() { LastException = ex; - Logger.Error(GetType().Name, ex); + Updater.Instance.Logger.Error(GetType().Name, ex); } finally { @@ -217,7 +217,7 @@ public AsyncTask Start() { #if DEBUG m_sw.Stop(); - Logger.Debug(GetType().Name, $"Completed in {m_sw.ElapsedMilliseconds}ms"); + Updater.Instance.Logger.Debug(GetType().Name, $"Completed in {m_sw.ElapsedMilliseconds}ms"); #endif worker.EndInvoke(r); @@ -263,7 +263,7 @@ protected void Enqueue(Delegate action, params object[] args) { LastException = e.Error?.InnerException ?? e.Error; - Logger.Error(GetType().Name, LastException); + Updater.Instance.Logger.Error(GetType().Name, LastException); } }; diff --git a/UpdateLib/UpdateLib/Tasks/CheckForUpdatesTask.cs b/UpdateLib/UpdateLib/Tasks/CheckForUpdatesTask.cs index 95877c3..7192c14 100644 --- a/UpdateLib/UpdateLib/Tasks/CheckForUpdatesTask.cs +++ b/UpdateLib/UpdateLib/Tasks/CheckForUpdatesTask.cs @@ -6,6 +6,7 @@ using MatthiWare.UpdateLib.UI; using static MatthiWare.UpdateLib.Tasks.CheckForUpdatesTask; using MatthiWare.UpdateLib.Logging; +using System.IO; namespace MatthiWare.UpdateLib.Tasks { @@ -30,21 +31,23 @@ protected override void DoWork() Result = new Data(); + Updater updater = Updater.Instance; + // Getting the file name from the url string localFile = GetLocalFileName(); - // downloading the file synchronous - wcDownloader.DownloadFile(Url, localFile); + if (IsUpdateFileInvalid(localFile)) + wcDownloader.DownloadFile(Url, localFile); // load the updatefile from disk Result.UpdateFile = UpdateFile.Load(localFile); Result.Version = Result.UpdateFile.VersionString; // lets wait for the Cache update to complete and get the task - HashCacheFile cache = Updater.Instance.GetCache(); + HashCacheFile cache = updater.GetCache(); // Wait for the clean up to complete - Updater.Instance.CleanUpTask.AwaitTask(); + updater.CleanUpTask.AwaitTask(); /* * Start a task to get all the files that need to be updated @@ -53,6 +56,19 @@ protected override void DoWork() Result.UpdateAvailable = CheckForUpdatedFiles(Result.UpdateFile, cache).AwaitTask().Result; } + private bool IsUpdateFileInvalid(string localFile) + { + if (!File.Exists(localFile)) + return true; + + DateTime time = File.GetLastWriteTime(localFile); + + if (time.Add(Updater.Instance.CacheInvalidationTime) < DateTime.Now) + return true; + + return false; + } + private CheckForUpdatedFilesTask CheckForUpdatedFiles(UpdateFile file, HashCacheFile cache) { CheckForUpdatedFilesTask task = new CheckForUpdatedFilesTask(file, cache, Updater.Instance.Converter); diff --git a/UpdateLib/UpdateLib/Tasks/CleanUpTask.cs b/UpdateLib/UpdateLib/Tasks/CleanUpTask.cs index 7555892..5c3d0b5 100644 --- a/UpdateLib/UpdateLib/Tasks/CleanUpTask.cs +++ b/UpdateLib/UpdateLib/Tasks/CleanUpTask.cs @@ -29,7 +29,7 @@ protected override void DoWork() } catch (Exception e) { - Logger.Error(GetType().Name, e); + Updater.Instance.Logger.Error(GetType().Name, e); } } } diff --git a/UpdateLib/UpdateLib/Tasks/DownloadManager.cs b/UpdateLib/UpdateLib/Tasks/DownloadManager.cs index 87689b9..cfe7898 100644 --- a/UpdateLib/UpdateLib/Tasks/DownloadManager.cs +++ b/UpdateLib/UpdateLib/Tasks/DownloadManager.cs @@ -58,7 +58,7 @@ private void Task_TaskCompleted(object sender, System.ComponentModel.AsyncComple succes = false; CancelOtherTasks(); - Logger.Error(GetType().Name, e.Error); + Updater.Instance.Logger.Error(GetType().Name, e.Error); } if (amountToDownload.Decrement() == 0) diff --git a/UpdateLib/UpdateLib/Tasks/DownloadTask.cs b/UpdateLib/UpdateLib/Tasks/DownloadTask.cs index d9e3074..b33d274 100644 --- a/UpdateLib/UpdateLib/Tasks/DownloadTask.cs +++ b/UpdateLib/UpdateLib/Tasks/DownloadTask.cs @@ -39,8 +39,8 @@ protected override void DoWork() string localFile = Updater.Instance.Converter.Replace(Entry.DestinationLocation); string remoteFile = string.Concat(Updater.Instance.RemoteBasePath, Entry.SourceLocation); - Logger.Debug(GetType().Name, $"LocalFile => {localFile}"); - Logger.Debug(GetType().Name, $"RemoteFile => {remoteFile}"); + Updater.Instance.Logger.Debug(GetType().Name, $"LocalFile => {localFile}"); + Updater.Instance.Logger.Debug(GetType().Name, $"RemoteFile => {remoteFile}"); if (File.Exists(localFile)) File.Move(localFile, $"{localFile}.old.tmp"); diff --git a/UpdateLib/UpdateLib/Tasks/UpdateCacheTask.cs b/UpdateLib/UpdateLib/Tasks/UpdateCacheTask.cs index d13c2e0..316d633 100644 --- a/UpdateLib/UpdateLib/Tasks/UpdateCacheTask.cs +++ b/UpdateLib/UpdateLib/Tasks/UpdateCacheTask.cs @@ -18,18 +18,18 @@ protected override void DoWork() } catch (Exception e) { - Logger.Error(GetType().Name, e); + Updater.Instance.Logger.Error(GetType().Name, e); Result = null; } DirectoryInfo dir = new DirectoryInfo("."); IEnumerable files = dir.GetFiles("*", SearchOption.AllDirectories).Where(f => !f.FullName.Contains(".old.tmp")); - - Logger.Debug(GetType().Name, $"found {files.Count()} files to recheck."); + + Updater.Instance.Logger.Debug(GetType().Name, $"found {files.Count()} files to recheck."); if (Result == null) // The file doesn't exist yet { - Logger.Warn(GetType().Name, $"{nameof(HashCacheFile)} doesn't exist. Creating.."); + Updater.Instance.Logger.Warn(GetType().Name, $"{nameof(HashCacheFile)} doesn't exist. Creating.."); Result = new HashCacheFile(); @@ -41,7 +41,7 @@ protected override void DoWork() } catch (Exception ex) // file might no longer exist or is in use { - Logger.Error(GetType().Name, ex); + Updater.Instance.Logger.Error(GetType().Name, ex); } } @@ -62,7 +62,7 @@ protected override void DoWork() } catch (Exception ex) // file might no longer exist or is in use { - Logger.Error(GetType().Name, ex); + Updater.Instance.Logger.Error(GetType().Name, ex); } continue; diff --git a/UpdateLib/UpdateLib/Tasks/WorkerScheduler.cs b/UpdateLib/UpdateLib/Tasks/WorkerScheduler.cs index 73fff4f..f46f5e7 100644 --- a/UpdateLib/UpdateLib/Tasks/WorkerScheduler.cs +++ b/UpdateLib/UpdateLib/Tasks/WorkerScheduler.cs @@ -78,7 +78,7 @@ private void Dispatcher() m_waitForAvailableWorker.WaitOne(); - Logger.Debug(GetType().Name, $"Current worker count: {m_currentWorkerCount.Increment()} | Current queue count: {m_taskQueue.Count}"); + Updater.Instance.Logger.Debug(GetType().Name, $"Current worker count: {m_currentWorkerCount.Increment()} | Current queue count: {m_taskQueue.Count}"); task.ConfigureAwait(false).Start(); } @@ -93,7 +93,7 @@ private void SetupTask(AsyncTask task) if (m_currentWorkerCount.Decrement() < MAX_WORKERS) m_waitForAvailableWorker.Set(); - Logger.Debug(GetType().Name, $"Current worker count: {m_currentWorkerCount}"); + Updater.Instance.Logger.Debug(GetType().Name, $"Current worker count: {m_currentWorkerCount}"); }; } diff --git a/UpdateLib/UpdateLib/UI/Components/UpdatePage.cs b/UpdateLib/UpdateLib/UI/Components/UpdatePage.cs index 1d40647..7403a54 100644 --- a/UpdateLib/UpdateLib/UI/Components/UpdatePage.cs +++ b/UpdateLib/UpdateLib/UI/Components/UpdatePage.cs @@ -110,7 +110,7 @@ private void Task_TaskCompleted(object sender, AsyncCompletedEventArgs e) if (e.Cancelled) { - Logger.Info(nameof(DownloadTask), $"Cancelled -> '{task.Entry.Name}'"); + Updater.Instance.Logger.Info(nameof(DownloadTask), $"Cancelled -> '{task.Entry.Name}'"); SetSubItemText(task.Item.SubItems[2], "Cancelled"); @@ -121,7 +121,7 @@ private void Task_TaskCompleted(object sender, AsyncCompletedEventArgs e) if (e.Error != null) { - Logger.Error(nameof(DownloadTask), e.Error); + Updater.Instance.Logger.Error(nameof(DownloadTask), e.Error); SetSubItemText(task.Item.SubItems[2], "Error"); diff --git a/UpdateLib/UpdateLib/UI/UpdaterForm.cs b/UpdateLib/UpdateLib/UI/UpdaterForm.cs index ff2a828..6352243 100644 --- a/UpdateLib/UpdateLib/UI/UpdaterForm.cs +++ b/UpdateLib/UpdateLib/UI/UpdaterForm.cs @@ -145,8 +145,6 @@ private void ExitUpdater() btnPrevious.Enabled = false; Close(); } - - } private void btnCancel_Click(object sender, EventArgs e) diff --git a/UpdateLib/UpdateLib/UpdateLib.csproj b/UpdateLib/UpdateLib/UpdateLib.csproj index 565a8e5..1f4747e 100644 --- a/UpdateLib/UpdateLib/UpdateLib.csproj +++ b/UpdateLib/UpdateLib/UpdateLib.csproj @@ -64,6 +64,7 @@ + diff --git a/UpdateLib/UpdateLib/Updater.cs b/UpdateLib/UpdateLib/Updater.cs index baa8adc..a5891e9 100644 --- a/UpdateLib/UpdateLib/Updater.cs +++ b/UpdateLib/UpdateLib/Updater.cs @@ -48,6 +48,8 @@ public static Updater Instance private string m_argUpdate = "--update"; private string m_argWait = "--wait"; private Lazy m_lazyPathVarConv = new Lazy(() => new PathVariableConverter()); + private TimeSpan m_cacheInvalidation = TimeSpan.FromMinutes(5); + private Lazy m_lazyLogger = new Lazy(() => new Logger()); #endregion @@ -76,6 +78,8 @@ public string UpdateURL } } + public ILogger Logger { get { return m_lazyLogger.Value; } } + /// /// Gets or sets the Updater Installation mode /// @@ -98,7 +102,7 @@ public string UpdateURL public string UpdateSilentlyCmdArg { get { return m_argUpdateSilent; } - set { m_argUpdateSilent = SetAndVerifyCmdArgument(value); } + set { SetAndVerifyCmdArgument(ref m_argUpdateSilent, value); } } /// @@ -108,7 +112,7 @@ public string UpdateSilentlyCmdArg public string StartUpdatingCmdArg { get { return m_argUpdate; } - set { m_argUpdate = SetAndVerifyCmdArgument(value); } + set { SetAndVerifyCmdArgument(ref m_argUpdate, value); } } /// @@ -118,7 +122,7 @@ public string StartUpdatingCmdArg public string WaitForProcessCmdArg { get { return m_argWait; } - set { m_argWait = SetAndVerifyCmdArgument(value); } + set { SetAndVerifyCmdArgument(ref m_argWait, value); } } /// @@ -159,6 +163,17 @@ public PathVariableConverter Converter /// public bool IsInitialized { get; private set; } + /// + /// Time before all the cached files become invalid + /// + public TimeSpan CacheInvalidationTime + { + get { return m_cacheInvalidation; } + set { m_cacheInvalidation = value; } + } + + public bool NeedsRestartBeforeUpdate { get; set; } = true; + /// /// The remote base path to use for downloading etc.. /// @@ -181,6 +196,27 @@ public Updater ConfigureUnsafeConnections(bool allow) return this; } + public Updater ConfigureLogger(Action logAction) + { + logAction(Logger); + + return this; + } + + public Updater ConfigureNeedsRestartBeforeUpdate(bool needsRestartBeforeUpdate) + { + NeedsRestartBeforeUpdate = needsRestartBeforeUpdate; + + return this; + } + + public Updater ConfigureCacheInvalidation(TimeSpan timeTillInvalidation) + { + CacheInvalidationTime = timeTillInvalidation; + + return this; + } + /// /// Configures the installation mode for the client /// @@ -376,6 +412,9 @@ public CheckForUpdatesTask CheckForUpdatesAsync(IWin32Window owner) if (result == DialogResult.Yes) { + if (!StartUpdating && NeedsRestartBeforeUpdate) + RestartApp(true, UpdateSilently, true, true); + if (UpdateSilently) { UpdateWithoutGUI(task.Result.UpdateFile); @@ -413,13 +452,13 @@ private void UpdateWithoutGUI(UpdateFile file) downloader.Update(); } - internal void RestartApp() + internal void RestartApp(bool update = false, bool silent = false, bool waitForPid = true, bool asAdmin = false) { List args = new List(Environment.GetCommandLineArgs()); for (int i = 0; i < args.Count; i++) { - if (args[i] == StartUpdatingCmdArg || args[i] == UpdateSilentlyCmdArg) + if ((!update && args[i] == StartUpdatingCmdArg) || (!silent && args[i] == UpdateSilentlyCmdArg)) { args[i] = string.Empty; } @@ -431,24 +470,38 @@ internal void RestartApp() } } - args.Add(instance.WaitForProcessCmdArg); - args.Add(Process.GetCurrentProcess().Id.ToString()); + if (waitForPid && !string.IsNullOrEmpty(instance.WaitForProcessCmdArg)) + { + args.Add(instance.WaitForProcessCmdArg); + args.Add(Process.GetCurrentProcess().Id.ToString()); + } + + if (update && !string.IsNullOrEmpty(instance.StartUpdatingCmdArg) && !args.Contains(instance.StartUpdatingCmdArg)) + args.Add(instance.StartUpdatingCmdArg); + + if (silent && !string.IsNullOrEmpty(instance.UpdateSilentlyCmdArg) && !args.Contains(instance.UpdateSilentlyCmdArg)) + args.Add(instance.UpdateSilentlyCmdArg); string arguments = args.Where(a => !string.IsNullOrEmpty(a)).Distinct().AppendAll(" "); ProcessStartInfo startInfo = new ProcessStartInfo(Assembly.GetEntryAssembly().Location, arguments); - startInfo.UseShellExecute = false; + + startInfo.UseShellExecute = true; + + if (asAdmin) + startInfo.Verb = "runas"; + Process.Start(startInfo); Environment.Exit(0); } - private string SetAndVerifyCmdArgument(string value) + private void SetAndVerifyCmdArgument(ref string reference, string value) { if (value.Contains(' ')) throw new ArgumentException("Command line argument can not contain spaces"); - return value; + reference = value; } } From 5707afdd0a1ce36a51cf7d64ae2efd84431e5b4e Mon Sep 17 00:00:00 2001 From: Matthias Beerens Date: Sun, 28 May 2017 11:19:22 +0200 Subject: [PATCH 11/16] Reworking update restart + option to configure if update needs admin rights --- UpdateLib/UpdateLib/Updater.cs | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/UpdateLib/UpdateLib/Updater.cs b/UpdateLib/UpdateLib/Updater.cs index a5891e9..f5cc3dc 100644 --- a/UpdateLib/UpdateLib/Updater.cs +++ b/UpdateLib/UpdateLib/Updater.cs @@ -50,7 +50,6 @@ public static Updater Instance private Lazy m_lazyPathVarConv = new Lazy(() => new PathVariableConverter()); private TimeSpan m_cacheInvalidation = TimeSpan.FromMinutes(5); private Lazy m_lazyLogger = new Lazy(() => new Logger()); - #endregion #region Events @@ -78,6 +77,11 @@ public string UpdateURL } } + public bool UpdateRequiresAdmin { get; set; } = true; + + /// + /// Gets the logger for the application. + /// public ILogger Logger { get { return m_lazyLogger.Value; } } /// @@ -229,6 +233,13 @@ public Updater ConfigureInstallationMode(InstallationMode mode) return this; } + public Updater ConfigureUpdateNeedsAdmin(bool needsAdmin) + { + UpdateRequiresAdmin = needsAdmin; + + return this; + } + /// /// Configures the update silently command switch /// @@ -402,18 +413,17 @@ public CheckForUpdatesTask CheckForUpdatesAsync(IWin32Window owner) DialogResult result = DialogResult.Yes; - if (!UpdateSilently) + if (!UpdateSilently && !StartUpdating) result = MessageDialog.Show( "Update available", $"Version {task.Result.Version} available", "Update now?\nPress yes to update or no to cancel.", SystemIcons.Question); - if (result == DialogResult.Yes) { if (!StartUpdating && NeedsRestartBeforeUpdate) - RestartApp(true, UpdateSilently, true, true); + RestartApp(true, UpdateSilently, true, UpdateRequiresAdmin); if (UpdateSilently) { From 616dab6a329684914e4cbe51b1fd3856edff2928 Mon Sep 17 00:00:00 2001 From: Matthias Beerens Date: Mon, 29 May 2017 11:17:06 +0200 Subject: [PATCH 12/16] Refactor --- UpdateLib/TestApp/Program.cs | 2 ++ UpdateLib/UpdateLib/Files/HashCacheFile.cs | 10 +--------- .../Logging/Writers/FileLogWriter.cs | 8 +------- .../UpdateLib/Properties/AssemblyInfo.cs | 4 ++-- UpdateLib/UpdateLib/Updater.cs | 19 ++++++++++++++++++- 5 files changed, 24 insertions(+), 19 deletions(-) diff --git a/UpdateLib/TestApp/Program.cs b/UpdateLib/TestApp/Program.cs index a828d0c..7c0ed56 100644 --- a/UpdateLib/TestApp/Program.cs +++ b/UpdateLib/TestApp/Program.cs @@ -27,6 +27,8 @@ static void Main() .ConfigureLogger((logger) => logger.Writers.Add(new ConsoleLogWriter())) .ConfigureLogger((logger) => logger.Writers.Add(new FileLogWriter())) .ConfigureUnsafeConnections(true) + .ConfigureCacheInvalidation(TimeSpan.FromSeconds(30)) + .ConfigureUpdateNeedsAdmin(false) .ConfigureInstallationMode(InstallationMode.Shared) .Initialize(); diff --git a/UpdateLib/UpdateLib/Files/HashCacheFile.cs b/UpdateLib/UpdateLib/Files/HashCacheFile.cs index 7bc03e4..a0a67d4 100644 --- a/UpdateLib/UpdateLib/Files/HashCacheFile.cs +++ b/UpdateLib/UpdateLib/Files/HashCacheFile.cs @@ -28,20 +28,12 @@ public HashCacheFile() private static string GetStoragePath() { string path = IOUtils.GetAppDataPath(); - string productName = GetProductName(); + string productName = Updater.ProductName; string name = Assembly.GetEntryAssembly().GetName().Name; return $@"{path}\{name}\{productName}\{CACHE_FOLDER_NAME}\{FILE_NAME}"; } - private static string GetProductName() - { - AssemblyProductAttribute attr = Attribute.GetCustomAttribute(Assembly.GetAssembly(typeof(HashCacheFile)), typeof(AssemblyProductAttribute)) as AssemblyProductAttribute; - return attr?.Product ?? ""; - } - - - public static HashCacheFile Load() { if (!File.Exists(storagePath.Value)) diff --git a/UpdateLib/UpdateLib/Logging/Writers/FileLogWriter.cs b/UpdateLib/UpdateLib/Logging/Writers/FileLogWriter.cs index 777237c..761f6e2 100644 --- a/UpdateLib/UpdateLib/Logging/Writers/FileLogWriter.cs +++ b/UpdateLib/UpdateLib/Logging/Writers/FileLogWriter.cs @@ -21,7 +21,7 @@ public class FileLogWriter : ILogWriter private static FileInfo GetLogFile() { string path = IOUtils.GetAppDataPath(); - string productName = GetProductName(); + string productName = Updater.ProductName; string name = Assembly.GetEntryAssembly().GetName().Name; FileInfo m_logFile = new FileInfo($@"{path}\{name}\{productName}\{LOG_FOLDER_NAME}\log_{DateTime.Now.ToString("yyyyMMdd")}.log"); @@ -32,12 +32,6 @@ private static FileInfo GetLogFile() return m_logFile; } - private static string GetProductName() - { - AssemblyProductAttribute attr = Attribute.GetCustomAttribute(Assembly.GetAssembly(typeof(FileLogWriter)), typeof(AssemblyProductAttribute)) as AssemblyProductAttribute; - return attr?.Product ?? ""; - } - public void Log(string text) { Action logAction = new Action(LogAsync); diff --git a/UpdateLib/UpdateLib/Properties/AssemblyInfo.cs b/UpdateLib/UpdateLib/Properties/AssemblyInfo.cs index bc68434..772279b 100644 --- a/UpdateLib/UpdateLib/Properties/AssemblyInfo.cs +++ b/UpdateLib/UpdateLib/Properties/AssemblyInfo.cs @@ -32,5 +32,5 @@ // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("0.1.1.0")] -[assembly: AssemblyFileVersion("0.1.1.0")] +[assembly: AssemblyVersion("0.3.0.0")] +[assembly: AssemblyFileVersion("0.3.0.0")] diff --git a/UpdateLib/UpdateLib/Updater.cs b/UpdateLib/UpdateLib/Updater.cs index f5cc3dc..8378590 100644 --- a/UpdateLib/UpdateLib/Updater.cs +++ b/UpdateLib/UpdateLib/Updater.cs @@ -50,6 +50,13 @@ public static Updater Instance private Lazy m_lazyPathVarConv = new Lazy(() => new PathVariableConverter()); private TimeSpan m_cacheInvalidation = TimeSpan.FromMinutes(5); private Lazy m_lazyLogger = new Lazy(() => new Logger()); + + private static Lazy m_lazyProductName = new Lazy(() => + { + AssemblyProductAttribute attr = Attribute.GetCustomAttribute(Assembly.GetAssembly(typeof(Updater)), typeof(AssemblyProductAttribute)) as AssemblyProductAttribute; + return attr?.Product ?? "UpdateLib"; + }); + #endregion #region Events @@ -63,6 +70,8 @@ public static Updater Instance #region Properties + internal static string ProductName { get { return m_lazyProductName.Value; } } + /// /// Gets or sets the url to update from /// @@ -407,6 +416,14 @@ public CheckForUpdatesTask CheckForUpdatesAsync(IWin32Window owner) { if (!task.Result.UpdateAvailable || e.Cancelled || e.Error != null) { + MessageDialog.Show( + owner, + $"{ProductName} Updater", + e.Cancelled ? "Cancelled" : "Error while updating", + e.Cancelled ? "Update got cancelled" : "Please check the logs for more information.", + e.Cancelled ? SystemIcons.Warning : SystemIcons.Error, + MessageBoxButtons.OK); + CheckForUpdatesCompleted?.Invoke(task, new CheckForUpdatesCompletedEventArgs(task.Result, e)); return; } @@ -415,6 +432,7 @@ public CheckForUpdatesTask CheckForUpdatesAsync(IWin32Window owner) if (!UpdateSilently && !StartUpdating) result = MessageDialog.Show( + owner, "Update available", $"Version {task.Result.Version} available", "Update now?\nPress yes to update or no to cancel.", @@ -513,6 +531,5 @@ private void SetAndVerifyCmdArgument(ref string reference, string value) reference = value; } - } } From d08161ca8a57787ef9033f30850609c8a9acb250 Mon Sep 17 00:00:00 2001 From: Matthias Beerens Date: Tue, 30 May 2017 19:55:27 +0200 Subject: [PATCH 13/16] Bugfixing/optimizations/refactoring --- UpdateLib/TestApp/Form1.cs | 55 ++++++++++--------- UpdateLib/TestApp/Program.cs | 40 +++++++++----- .../Files/HashCacheEntryTest.cs | 2 +- UpdateLib/UpdateLib/Files/HashCacheEntry.cs | 8 ++- UpdateLib/UpdateLib/Files/HashCacheFile.cs | 44 ++++++++++++--- .../Logging/Writers/FileLogWriter.cs | 5 +- .../UpdateLib/Tasks/CheckForUpdatesTask.cs | 18 +++--- UpdateLib/UpdateLib/Tasks/DownloadManager.cs | 5 +- UpdateLib/UpdateLib/Tasks/DownloadTask.cs | 5 ++ UpdateLib/UpdateLib/Tasks/UpdateCacheTask.cs | 2 +- UpdateLib/UpdateLib/UI/MessageDialog.cs | 3 + UpdateLib/UpdateLib/UI/UpdaterForm.cs | 4 +- UpdateLib/UpdateLib/Updater.cs | 28 +++++++--- UpdateLib/UpdateLib/Utils/IOUtils.cs | 17 +++++- 14 files changed, 156 insertions(+), 80 deletions(-) diff --git a/UpdateLib/TestApp/Form1.cs b/UpdateLib/TestApp/Form1.cs index ded7dea..9eaf402 100644 --- a/UpdateLib/TestApp/Form1.cs +++ b/UpdateLib/TestApp/Form1.cs @@ -28,39 +28,40 @@ private void Instance_CheckForUpdatesCompleted(object sender, CheckForUpdatesCom { this.InvokeOnUI(() => checkForUpdatesToolStripMenuItem.Enabled = true); - if (e.Cancelled || e.Error != null) - { - this.InvokeOnUI(() => MessageDialog.Show( - this, - "Updater", - e.Cancelled ? "Cancelled" : "Error", - e.Cancelled ? "Update got cancelled" : "Please check the logs for more information.", - e.Cancelled ? SystemIcons.Warning : SystemIcons.Error, - MessageBoxButtons.OK)); - - return; - } - - if (!e.UpdateAvailable) - { - this.InvokeOnUI(() => - MessageDialog.Show( - this, - "Updater", - "No update available!", - $"You already have the latest version ({e.LatestVersion}).", - SystemIcons.Information, - MessageBoxButtons.OK)); - - return; - } + //if (e.Cancelled || e.Error != null) + //{ + // this.InvokeOnUI(() => MessageDialog.Show( + // this, + // "Updater", + // e.Cancelled ? "Cancelled" : "Error", + // e.Cancelled ? "Update got cancelled" : "Please check the logs for more information.", + // e.Cancelled ? SystemIcons.Warning : SystemIcons.Error, + // MessageBoxButtons.OK)); + + // return; + //} + + //if (!e.UpdateAvailable) + //{ + // this.InvokeOnUI(() => + // MessageDialog.Show( + // this, + // "Updater", + // "No update available!", + // $"You already have the latest version ({e.LatestVersion}).", + // SystemIcons.Information, + // MessageBoxButtons.OK)); + + // return; + //} } private void checkForUpdatesToolStripMenuItem_Click(object sender, EventArgs e) { checkForUpdatesToolStripMenuItem.Enabled = false; - Updater.Instance.CheckForUpdatesAsync(); + AsyncTask task = Updater.Instance.CheckForUpdatesAsync(); + task.Cancel(); } private void Form1_Load(object sender, EventArgs e) diff --git a/UpdateLib/TestApp/Program.cs b/UpdateLib/TestApp/Program.cs index 7c0ed56..fe27d84 100644 --- a/UpdateLib/TestApp/Program.cs +++ b/UpdateLib/TestApp/Program.cs @@ -16,23 +16,33 @@ static class Program [STAThread] static void Main() { - // we still want our updater to have visual styles in case of update cmd argument switch - Application.EnableVisualStyles(); - Application.SetCompatibleTextRenderingDefault(false); + try + { - Updater.Instance - //.ConfigureUpdateUrl("https://raw.githubusercontent.com/MatthiWare/UpdateLib.TestApp.UpdateExample/master/Dev/updatefile.xml") - .ConfigureUpdateUrl("http://matthiware.dev/UpdateLib/Dev/updatefile.xml") - .ConfigureLogger((logger) => logger.LogLevel = LoggingLevel.Debug) - .ConfigureLogger((logger) => logger.Writers.Add(new ConsoleLogWriter())) - .ConfigureLogger((logger) => logger.Writers.Add(new FileLogWriter())) - .ConfigureUnsafeConnections(true) - .ConfigureCacheInvalidation(TimeSpan.FromSeconds(30)) - .ConfigureUpdateNeedsAdmin(false) - .ConfigureInstallationMode(InstallationMode.Shared) - .Initialize(); + // we still want our updater to have visual styles in case of update cmd argument switch + Application.EnableVisualStyles(); + Application.SetCompatibleTextRenderingDefault(false); - Application.Run(new Form1()); + Updater.Instance + //.ConfigureUpdateUrl("https://raw.githubusercontent.com/MatthiWare/UpdateLib.TestApp.UpdateExample/master/Dev/updatefile.xml") + .ConfigureUpdateUrl("http://matthiware.dev/UpdateLib/Dev/updatefile.xml") + .ConfigureLogger((logger) => logger.LogLevel = LoggingLevel.Debug) + .ConfigureLogger((logger) => logger.Writers.Add(new ConsoleLogWriter())) + .ConfigureLogger((logger) => logger.Writers.Add(new FileLogWriter())) + .ConfigureUnsafeConnections(true) + .ConfigureCacheInvalidation(TimeSpan.FromSeconds(30)) + .ConfigureUpdateNeedsAdmin(false) + .ConfigureInstallationMode(InstallationMode.Shared) + .Initialize(); + + Application.Run(new Form1()); + } + catch (Exception e) + { + Console.WriteLine(e.ToString()); + MessageBox.Show(e.ToString()); + } + } } } diff --git a/UpdateLib/UpdateLib.Tests/Files/HashCacheEntryTest.cs b/UpdateLib/UpdateLib.Tests/Files/HashCacheEntryTest.cs index 6055345..661c8c4 100644 --- a/UpdateLib/UpdateLib.Tests/Files/HashCacheEntryTest.cs +++ b/UpdateLib/UpdateLib.Tests/Files/HashCacheEntryTest.cs @@ -42,7 +42,7 @@ public void TestHashing() EditTempFile(); - entry.Recalculate(File.GetLastWriteTime(temp_file).Ticks); + entry.Recalculate(); Assert.AreNotEqual(ticks, entry.Ticks); Assert.AreNotEqual(hash, entry.Hash); diff --git a/UpdateLib/UpdateLib/Files/HashCacheEntry.cs b/UpdateLib/UpdateLib/Files/HashCacheEntry.cs index df95904..a7941e1 100644 --- a/UpdateLib/UpdateLib/Files/HashCacheEntry.cs +++ b/UpdateLib/UpdateLib/Files/HashCacheEntry.cs @@ -16,7 +16,7 @@ public class HashCacheEntry public string FilePath { get; set; } [XmlAttribute("Time")] - public long Ticks { get; set; } + public long Ticks { get; set; } = -1; public HashCacheEntry() { } @@ -35,11 +35,13 @@ public HashCacheEntry(string file) Ticks = File.GetLastWriteTime(FilePath).Ticks; } - public void Recalculate(long tick) + public void Recalculate() { try { - if (tick != Ticks) + long tick = File.GetLastWriteTime(FilePath).Ticks; + + if (Ticks == -1 || tick != Ticks) { Hash = HashUtil.GetHash(FilePath); Ticks = tick; diff --git a/UpdateLib/UpdateLib/Files/HashCacheFile.cs b/UpdateLib/UpdateLib/Files/HashCacheFile.cs index a0a67d4..60522bc 100644 --- a/UpdateLib/UpdateLib/Files/HashCacheFile.cs +++ b/UpdateLib/UpdateLib/Files/HashCacheFile.cs @@ -1,9 +1,11 @@ -using MatthiWare.UpdateLib.Utils; +using MatthiWare.UpdateLib.Security; +using MatthiWare.UpdateLib.Utils; using System; using System.Collections.Generic; using System.IO; using System.Reflection; using System.Xml.Serialization; +using System.Linq; namespace MatthiWare.UpdateLib.Files { @@ -17,29 +19,53 @@ public class HashCacheFile [XmlArrayItem("Entry")] public List Items { get; set; } - private static Lazy storagePath = new Lazy(GetStoragePath); + private readonly object sync = new object(); public HashCacheFile() { Items = new List(); } + public void AddOrUpdateEntry(string fullPath, string hash = "") + { + lock (sync) + { + long ticks = File.GetLastWriteTime(fullPath).Ticks; + hash = string.IsNullOrEmpty(hash) ? HashUtil.GetHash(fullPath) : hash; + + HashCacheEntry entry = Items.FirstOrDefault(f => f.FilePath == fullPath); + + if (entry == null) + { + entry = new HashCacheEntry(); + entry.FilePath = fullPath; + entry.Hash = hash; + entry.Ticks = ticks; + + Items.Add(entry); + } + else + { + entry.Ticks = ticks; + entry.Hash = hash; + } + } + } + #region Save/Load private static string GetStoragePath() { - string path = IOUtils.GetAppDataPath(); - string productName = Updater.ProductName; - string name = Assembly.GetEntryAssembly().GetName().Name; + string path = IOUtils.AppDataPath; - return $@"{path}\{name}\{productName}\{CACHE_FOLDER_NAME}\{FILE_NAME}"; + return $@"{path}\{CACHE_FOLDER_NAME}\{FILE_NAME}"; } public static HashCacheFile Load() { - if (!File.Exists(storagePath.Value)) + if (!File.Exists(GetStoragePath())) return null; - using (Stream stream = File.Open(storagePath.Value, FileMode.Open, FileAccess.Read)) + using (Stream stream = File.Open(GetStoragePath(), FileMode.Open, FileAccess.Read)) { XmlSerializer serializer = new XmlSerializer(typeof(HashCacheFile)); return (HashCacheFile)serializer.Deserialize(stream); @@ -48,7 +74,7 @@ public static HashCacheFile Load() public void Save() { - FileInfo fi = new FileInfo(storagePath.Value); + FileInfo fi = new FileInfo(GetStoragePath()); if (!fi.Directory.Exists) fi.Directory.Create(); diff --git a/UpdateLib/UpdateLib/Logging/Writers/FileLogWriter.cs b/UpdateLib/UpdateLib/Logging/Writers/FileLogWriter.cs index 761f6e2..1424935 100644 --- a/UpdateLib/UpdateLib/Logging/Writers/FileLogWriter.cs +++ b/UpdateLib/UpdateLib/Logging/Writers/FileLogWriter.cs @@ -20,11 +20,10 @@ public class FileLogWriter : ILogWriter private static FileInfo GetLogFile() { - string path = IOUtils.GetAppDataPath(); - string productName = Updater.ProductName; + string path = IOUtils.AppDataPath; string name = Assembly.GetEntryAssembly().GetName().Name; - FileInfo m_logFile = new FileInfo($@"{path}\{name}\{productName}\{LOG_FOLDER_NAME}\log_{DateTime.Now.ToString("yyyyMMdd")}.log"); + FileInfo m_logFile = new FileInfo($@"{path}\{LOG_FOLDER_NAME}\log_{DateTime.Now.ToString("yyyyMMdd")}.log"); if (!m_logFile.Directory.Exists) m_logFile.Directory.Create(); diff --git a/UpdateLib/UpdateLib/Tasks/CheckForUpdatesTask.cs b/UpdateLib/UpdateLib/Tasks/CheckForUpdatesTask.cs index 7192c14..72e167b 100644 --- a/UpdateLib/UpdateLib/Tasks/CheckForUpdatesTask.cs +++ b/UpdateLib/UpdateLib/Tasks/CheckForUpdatesTask.cs @@ -7,6 +7,7 @@ using static MatthiWare.UpdateLib.Tasks.CheckForUpdatesTask; using MatthiWare.UpdateLib.Logging; using System.IO; +using MatthiWare.UpdateLib.Utils; namespace MatthiWare.UpdateLib.Tasks { @@ -34,11 +35,14 @@ protected override void DoWork() Updater updater = Updater.Instance; // Getting the file name from the url - string localFile = GetLocalFileName(); + string localFile = $@"{IOUtils.AppDataPath}\Update.xml"; + + if (IsCancelled) + return; if (IsUpdateFileInvalid(localFile)) wcDownloader.DownloadFile(Url, localFile); - + // load the updatefile from disk Result.UpdateFile = UpdateFile.Load(localFile); Result.Version = Result.UpdateFile.VersionString; @@ -49,6 +53,9 @@ protected override void DoWork() // Wait for the clean up to complete updater.CleanUpTask.AwaitTask(); + if (IsCancelled) + return; + /* * Start a task to get all the files that need to be updated * Returns if there is anything to update @@ -76,13 +83,6 @@ private CheckForUpdatedFilesTask CheckForUpdatedFiles(UpdateFile file, HashCache return task; } - private string GetLocalFileName() - { - const char slash = '/'; - string[] tokens = Url.Split(slash); - return string.Concat(".", slash, tokens[tokens.Length - 1]); - } - public class Data { public string Version { get; set; } = string.Empty; diff --git a/UpdateLib/UpdateLib/Tasks/DownloadManager.cs b/UpdateLib/UpdateLib/Tasks/DownloadManager.cs index cfe7898..76d87be 100644 --- a/UpdateLib/UpdateLib/Tasks/DownloadManager.cs +++ b/UpdateLib/UpdateLib/Tasks/DownloadManager.cs @@ -62,7 +62,11 @@ private void Task_TaskCompleted(object sender, System.ComponentModel.AsyncComple } if (amountToDownload.Decrement() == 0) + { + Updater.Instance.GetCache().Save(); Updater.Instance.RestartApp(); + } + } private void CancelOtherTasks() @@ -71,6 +75,5 @@ private void CancelOtherTasks() task.Cancel(); } - } } diff --git a/UpdateLib/UpdateLib/Tasks/DownloadTask.cs b/UpdateLib/UpdateLib/Tasks/DownloadTask.cs index b33d274..b7cdfa6 100644 --- a/UpdateLib/UpdateLib/Tasks/DownloadTask.cs +++ b/UpdateLib/UpdateLib/Tasks/DownloadTask.cs @@ -34,6 +34,9 @@ public DownloadTask(FileEntry entry) protected override void DoWork() { + if (IsCancelled) + return; + wait = new ManualResetEvent(false); string localFile = Updater.Instance.Converter.Replace(Entry.DestinationLocation); @@ -55,6 +58,8 @@ protected override void DoWork() if (hash.Length != Entry.Hash.Length || hash != Entry.Hash) throw new InvalidHashException($"Calculated hash doesn't match provided hash for file: {localFile}"); + + Updater.Instance.GetCache().AddOrUpdateEntry(localFile, hash); } public override void Cancel() diff --git a/UpdateLib/UpdateLib/Tasks/UpdateCacheTask.cs b/UpdateLib/UpdateLib/Tasks/UpdateCacheTask.cs index 316d633..1936911 100644 --- a/UpdateLib/UpdateLib/Tasks/UpdateCacheTask.cs +++ b/UpdateLib/UpdateLib/Tasks/UpdateCacheTask.cs @@ -69,7 +69,7 @@ protected override void DoWork() } // check to see if the file has been modified since last cache check - entry.Recalculate(f.LastWriteTime.Ticks); + entry.Recalculate(); } Result.Save(); diff --git a/UpdateLib/UpdateLib/UI/MessageDialog.cs b/UpdateLib/UpdateLib/UI/MessageDialog.cs index cc93861..e14be28 100644 --- a/UpdateLib/UpdateLib/UI/MessageDialog.cs +++ b/UpdateLib/UpdateLib/UI/MessageDialog.cs @@ -49,7 +49,10 @@ public MessageDialog(string title, string header, string desc, Icon icon, Messag Description = desc; Text = title; DialogIcon = icon; + Icon = icon; + ShowIcon = true; + SetUpButtons(buttons); } diff --git a/UpdateLib/UpdateLib/UI/UpdaterForm.cs b/UpdateLib/UpdateLib/UI/UpdaterForm.cs index 6352243..d1650a5 100644 --- a/UpdateLib/UpdateLib/UI/UpdaterForm.cs +++ b/UpdateLib/UpdateLib/UI/UpdaterForm.cs @@ -131,14 +131,14 @@ private void btnNext_Click(object sender, EventArgs e) private void ExitUpdater() { + Updater.Instance.GetCache().Save(); + if (NeedsRestart) { Updater.Instance.RestartApp(); } else { - Updater.Instance.Initialize(); - pages.Clear(); pages.Add(new FinishPage(this)); SetContentPage(pages.CurrentPage); diff --git a/UpdateLib/UpdateLib/Updater.cs b/UpdateLib/UpdateLib/Updater.cs index 8378590..1815eb3 100644 --- a/UpdateLib/UpdateLib/Updater.cs +++ b/UpdateLib/UpdateLib/Updater.cs @@ -414,15 +414,26 @@ public CheckForUpdatesTask CheckForUpdatesAsync(IWin32Window owner) CheckForUpdatesTask task = new CheckForUpdatesTask(UpdateURL); task.TaskCompleted += (o, e) => { - if (!task.Result.UpdateAvailable || e.Cancelled || e.Error != null) + bool error = e.Error != null; + bool cancelled = e.Cancelled; + bool update = task.Result.UpdateAvailable; + + if (!update || cancelled || error) { - MessageDialog.Show( - owner, - $"{ProductName} Updater", - e.Cancelled ? "Cancelled" : "Error while updating", - e.Cancelled ? "Update got cancelled" : "Please check the logs for more information.", - e.Cancelled ? SystemIcons.Warning : SystemIcons.Error, - MessageBoxButtons.OK); + if (error) + Logger.Error(GetType().Name, e.Error); + + if (!update) + Logger.Info() + + if (!UpdateSilently) + MessageDialog.Show( + owner, + $"{ProductName} Updater", + error ? "Error while updating" : (cancelled ? "Cancelled" : "No Update available"), + error ? "Check the log files for more information!" : (cancelled ? "Update got cancelled" : $"You already have the latest version {task.Result.Version}"), + error ? SystemIcons.Error : (cancelled ? SystemIcons.Warning : SystemIcons.Information), + MessageBoxButtons.OK); CheckForUpdatesCompleted?.Invoke(task, new CheckForUpdatesCompletedEventArgs(task.Result, e)); return; @@ -456,6 +467,7 @@ public CheckForUpdatesTask CheckForUpdatesAsync(IWin32Window owner) CheckForUpdatesCompleted?.Invoke(task, new CheckForUpdatesCompletedEventArgs(task.Result, e)); }; + return (CheckForUpdatesTask)task.Start(); } diff --git a/UpdateLib/UpdateLib/Utils/IOUtils.cs b/UpdateLib/UpdateLib/Utils/IOUtils.cs index fb754cf..fe3b65c 100644 --- a/UpdateLib/UpdateLib/Utils/IOUtils.cs +++ b/UpdateLib/UpdateLib/Utils/IOUtils.cs @@ -1,12 +1,16 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Reflection; using System.Text; namespace MatthiWare.UpdateLib.Utils { public static class IOUtils { + private static Lazy m_getAppDataPath = new Lazy(GetAppDataPath); + + public static string AppDataPath { get { return m_getAppDataPath.Value; } } public static string GetRemoteBasePath(string url) { @@ -26,7 +30,16 @@ public static string GetRemoteBasePath(string url) return builder.ToString(); } - public static string GetAppDataPath() + private static string GetAppDataPath() + { + string path = GetPathPrefix(); + string productName = Updater.ProductName; + string name = Assembly.GetEntryAssembly().GetName().Name; + + return $@"{path}\{name}\{productName}"; + } + + private static string GetPathPrefix() { switch (Updater.Instance.InstallationMode) { @@ -36,6 +49,8 @@ public static string GetAppDataPath() default: return Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData); } + + } } From d995d25f69772979eac97c44eebb3596604f64ee Mon Sep 17 00:00:00 2001 From: Matthias Beerens Date: Tue, 30 May 2017 21:16:38 +0200 Subject: [PATCH 14/16] Fix compilation error --- UpdateLib/TestApp/Form1.cs | 2 +- UpdateLib/UpdateLib/Tasks/CheckForUpdatesTask.cs | 3 +++ UpdateLib/UpdateLib/Updater.cs | 5 ++++- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/UpdateLib/TestApp/Form1.cs b/UpdateLib/TestApp/Form1.cs index 9eaf402..2b1dec8 100644 --- a/UpdateLib/TestApp/Form1.cs +++ b/UpdateLib/TestApp/Form1.cs @@ -61,7 +61,7 @@ private void checkForUpdatesToolStripMenuItem_Click(object sender, EventArgs e) checkForUpdatesToolStripMenuItem.Enabled = false; AsyncTask task = Updater.Instance.CheckForUpdatesAsync(); - task.Cancel(); + //task.Cancel(); } private void Form1_Load(object sender, EventArgs e) diff --git a/UpdateLib/UpdateLib/Tasks/CheckForUpdatesTask.cs b/UpdateLib/UpdateLib/Tasks/CheckForUpdatesTask.cs index 72e167b..2d131d8 100644 --- a/UpdateLib/UpdateLib/Tasks/CheckForUpdatesTask.cs +++ b/UpdateLib/UpdateLib/Tasks/CheckForUpdatesTask.cs @@ -41,7 +41,10 @@ protected override void DoWork() return; if (IsUpdateFileInvalid(localFile)) + { + Updater.Instance.Logger.Warn(GetType().Name, "Cached update file validity expired, downloading new one.."); wcDownloader.DownloadFile(Url, localFile); + } // load the updatefile from disk Result.UpdateFile = UpdateFile.Load(localFile); diff --git a/UpdateLib/UpdateLib/Updater.cs b/UpdateLib/UpdateLib/Updater.cs index 1815eb3..bee1b41 100644 --- a/UpdateLib/UpdateLib/Updater.cs +++ b/UpdateLib/UpdateLib/Updater.cs @@ -424,7 +424,10 @@ public CheckForUpdatesTask CheckForUpdatesAsync(IWin32Window owner) Logger.Error(GetType().Name, e.Error); if (!update) - Logger.Info() + Logger.Info(GetType().Name, "No update available"); + + if (cancelled) + Logger.Info(GetType().Name, "Update cancalled"); if (!UpdateSilently) MessageDialog.Show( From ca1aeb880e881d31b543cba8adedd073d62dca76 Mon Sep 17 00:00:00 2001 From: Matthias Beerens Date: Tue, 30 May 2017 22:47:24 +0200 Subject: [PATCH 15/16] Extra comments --- UpdateLib/UpdateLib/Updater.cs | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/UpdateLib/UpdateLib/Updater.cs b/UpdateLib/UpdateLib/Updater.cs index bee1b41..acb11b2 100644 --- a/UpdateLib/UpdateLib/Updater.cs +++ b/UpdateLib/UpdateLib/Updater.cs @@ -185,6 +185,11 @@ public TimeSpan CacheInvalidationTime set { m_cacheInvalidation = value; } } + /// + /// Gets or sets if the updater needs to launch as a new instance. + /// True if you want cold-swapping, False if you want hot-swapping + /// + /// Hot-swapping might cause issues if the files are still in use. public bool NeedsRestartBeforeUpdate { get; set; } = true; /// @@ -209,6 +214,11 @@ public Updater ConfigureUnsafeConnections(bool allow) return this; } + /// + /// Configures the logger + /// + /// Action to perform on the logger + /// public Updater ConfigureLogger(Action logAction) { logAction(Logger); @@ -216,6 +226,12 @@ public Updater ConfigureLogger(Action logAction) return this; } + /// + /// Configures if the updater needs a restart before updating + /// + /// Disabling this feature will allow for hot-swapping of the files. + /// Restart updater in new instance + /// public Updater ConfigureNeedsRestartBeforeUpdate(bool needsRestartBeforeUpdate) { NeedsRestartBeforeUpdate = needsRestartBeforeUpdate; @@ -223,6 +239,11 @@ public Updater ConfigureNeedsRestartBeforeUpdate(bool needsRestartBeforeUpdate) return this; } + /// + /// Configures the time till the cache becomes invalid + /// + /// Specify the validity time + /// public Updater ConfigureCacheInvalidation(TimeSpan timeTillInvalidation) { CacheInvalidationTime = timeTillInvalidation; From 457f7e32c323d95383e5bb72e7229b98473f3ebd Mon Sep 17 00:00:00 2001 From: Matthias Beerens Date: Tue, 30 May 2017 22:51:11 +0200 Subject: [PATCH 16/16] Update update url --- UpdateLib/TestApp/Program.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/UpdateLib/TestApp/Program.cs b/UpdateLib/TestApp/Program.cs index fe27d84..c1358cc 100644 --- a/UpdateLib/TestApp/Program.cs +++ b/UpdateLib/TestApp/Program.cs @@ -24,8 +24,8 @@ static void Main() Application.SetCompatibleTextRenderingDefault(false); Updater.Instance - //.ConfigureUpdateUrl("https://raw.githubusercontent.com/MatthiWare/UpdateLib.TestApp.UpdateExample/master/Dev/updatefile.xml") - .ConfigureUpdateUrl("http://matthiware.dev/UpdateLib/Dev/updatefile.xml") + .ConfigureUpdateUrl("https://raw.githubusercontent.com/MatthiWare/UpdateLib.TestApp.UpdateExample/master/Dev/updatefile.xml") + //.ConfigureUpdateUrl("http://matthiware.dev/UpdateLib/Dev/updatefile.xml") .ConfigureLogger((logger) => logger.LogLevel = LoggingLevel.Debug) .ConfigureLogger((logger) => logger.Writers.Add(new ConsoleLogWriter())) .ConfigureLogger((logger) => logger.Writers.Add(new FileLogWriter()))