diff --git a/AutoUpdate/CKAN-autoupdate.csproj b/AutoUpdate/CKAN-autoupdate.csproj index 3f25bc10df..7ca711475a 100644 --- a/AutoUpdate/CKAN-autoupdate.csproj +++ b/AutoUpdate/CKAN-autoupdate.csproj @@ -38,6 +38,7 @@ + diff --git a/AutoUpdate/Main.cs b/AutoUpdate/Main.cs index 3b03e55530..04a41690b3 100644 --- a/AutoUpdate/Main.cs +++ b/AutoUpdate/Main.cs @@ -2,6 +2,7 @@ using System.Diagnostics; using System.IO; using System.Threading; +using System.Windows.Forms; /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * CKAN AUTO-UPDATE TOOL @@ -17,73 +18,103 @@ namespace AutoUpdater { - class Program + public class Program { - static void Main(string[] args) + public static int Main(string[] args) { + AppDomain.CurrentDomain.UnhandledException += UnhandledExceptionEventHandler; + if (args.Length != 4) { - return; + ReportError("Usage: AutoUpdater.exe pid oldPath newPath [no]launch"); + return ExitBADOPT; } - var pid = int.Parse(args[0]); - var local_path = args[1]; - var updated_path = args[2]; + // Yes, it's a global variable, but we're an ephemeral singleton so :shrug: + fromGui = (args[3] == "launch"); + + int pid = int.Parse(args[0]); + string local_path = args[1]; + string updated_path = args[2]; if (!File.Exists(updated_path)) { - return; + ReportError($"Downloaded ckan.exe not found at: {updated_path}"); + return ExitBADOPT; } - // wait for CKAN to close + // Wait for CKAN to close try { var process = Process.GetProcessById(pid); - if (!process.HasExited) { process.WaitForExit(); } } - catch (Exception) {} - - int retries = 8; + catch (ArgumentException) + { + // Process already exited, we're fine + } + catch (Exception exc) + { + ReportError($"Failed to wait for CKAN to close: {exc.Message}"); + return ExitERROR; + } - while (File.Exists(local_path)) + for (int retry = 0; retry < maxRetries && File.Exists(local_path); ++retry) { try { - // delete the old ckan.exe + // Delete the old ckan.exe File.Delete(local_path); } - catch (Exception) + catch (Exception exc) { - Thread.Sleep(1000); - } - - retries--; - if (retries == 0) - { - return; + if (retry == maxRetries - 1) + { + ReportError($"Failed to delete {local_path}: {exc.Message}"); + if (fromGui) + { + // Launch the old EXE that we can't delete + StartCKAN(local_path); + } + return ExitERROR; + } + else + { + // Double sleep every time, starting at 100 ms, ending at 25 sec + Thread.Sleep(100 * (int)Math.Pow(2, retry)); + } } } - // replace ckan.exe + // Replace ckan.exe File.Move(updated_path, local_path); MakeExecutable(local_path); - if (args[3] == "launch") + if (fromGui) { - //Start CKAN - if (IsOnMono()) - { - Process.Start("mono", String.Format("\"{0}\"", local_path)); - } - else - { - Process.Start(local_path); - } + StartCKAN(local_path); + } + return ExitOK; + } + + /// + /// Run the CKAN EXE at the given path + /// + /// Location of our CKAN EXE + private static void StartCKAN(string path) + { + // Start CKAN + if (IsOnMono()) + { + Process.Start("mono", String.Format("\"{0}\"", path)); + } + else + { + Process.Start(path); } } @@ -120,5 +151,36 @@ private static bool IsOnWindows() return platform != PlatformID.MacOSX && platform != PlatformID.Unix && platform != PlatformID.Xbox; } + + /// + /// Display unexpected exceptions to user + /// + /// Source of unhandled exception + /// Info about the exception + private static void UnhandledExceptionEventHandler(Object sender, UnhandledExceptionEventArgs e) + { + ReportError($"Unhandled exception:\r\n{e.ExceptionObject}"); + } + + /// + /// It's nice to tell the user when something goes wrong! + /// + /// Description of the problem that happened + private static void ReportError(string err) + { + Console.Error.WriteLine(err); + if (fromGui) + { + // Show a popup in case the console isn't open + MessageBox.Show(err, "Fatal AutoUpdater Error", MessageBoxButtons.OK); + } + } + + private const int maxRetries = 8; + private static bool fromGui = false; + + private const int ExitOK = 0; + private const int ExitBADOPT = 1; + private const int ExitERROR = 2; } }