From e38b01eef24e9122b080acd7a6a2ffe0616ad51a Mon Sep 17 00:00:00 2001 From: Caelan Sayler Date: Sun, 2 Feb 2025 20:52:18 +0000 Subject: [PATCH] Make ProcessInfo mockable, to help unusual setups and Unity --- src/lib-csharp/Locators/IVelopackLocator.cs | 12 +++++++++ .../Locators/LinuxVelopackLocator.cs | 14 ++++++----- src/lib-csharp/Locators/OsxVelopackLocator.cs | 12 +++++---- src/lib-csharp/Locators/VelopackLocator.cs | 19 +++++++++++--- .../Locators/WindowsVelopackLocator.cs | 23 +++++++---------- src/lib-csharp/UpdateManager.Helpers.cs | 4 +-- src/lib-csharp/Util/IoUtil.cs | 25 +++++++++++++------ src/lib-csharp/VelopackApp.cs | 2 +- src/lib-csharp/VelopackRuntimeInfo.cs | 15 +++-------- 9 files changed, 75 insertions(+), 51 deletions(-) diff --git a/src/lib-csharp/Locators/IVelopackLocator.cs b/src/lib-csharp/Locators/IVelopackLocator.cs index a5df831f..80408d1e 100644 --- a/src/lib-csharp/Locators/IVelopackLocator.cs +++ b/src/lib-csharp/Locators/IVelopackLocator.cs @@ -46,6 +46,18 @@ namespace Velopack.Locators /// home directory. /// public bool IsPortable { get; } + + /// + /// The process for which the Velopack Locator has been constructed. This should usually be the current process path. + /// + public string ProcessExePath { get; } + + /// + /// The process ID for which the Velopack Locator has been constructed. This should usually be the current process ID. + /// Setting this to zero will disable some features of Velopack (like the ability to wait for the process to exit + /// before installing updates). + /// + public uint ProcessId { get; } /// /// Finds .nupkg files in the PackagesDir and returns a list of ReleaseEntryName objects. diff --git a/src/lib-csharp/Locators/LinuxVelopackLocator.cs b/src/lib-csharp/Locators/LinuxVelopackLocator.cs index 91aa0999..6fddf4d0 100644 --- a/src/lib-csharp/Locators/LinuxVelopackLocator.cs +++ b/src/lib-csharp/Locators/LinuxVelopackLocator.cs @@ -32,7 +32,7 @@ namespace Velopack.Locators /// public override string? Channel { get; } - + /// public override string? AppTempDir => CreateSubDirIfDoesNotExist(TempUtil.GetDefaultTempBaseDirectory(), AppId); @@ -52,23 +52,25 @@ namespace Velopack.Locators /// Creates a new and auto-detects the /// app information from metadata embedded in the .app. /// - public LinuxVelopackLocator(ILogger logger) + public LinuxVelopackLocator(string currentProcessPath, uint currentProcessId, ILogger logger) : base(logger) { if (!VelopackRuntimeInfo.IsLinux) throw new NotSupportedException("Cannot instantiate LinuxVelopackLocator on a non-linux system."); + + ProcessId = currentProcessId; + ProcessExePath = currentProcessPath; Log.Info($"Initialising {nameof(LinuxVelopackLocator)}"); // are we inside a mounted .AppImage? - var ourPath = VelopackRuntimeInfo.EntryExePath; - var ix = ourPath.IndexOf("/usr/bin/", StringComparison.InvariantCultureIgnoreCase); + var ix = ProcessExePath.IndexOf("/usr/bin/", StringComparison.InvariantCultureIgnoreCase); if (ix <= 0) { - Log.Warn($"Unable to locate .AppImage root from '{ourPath}'. This warning indicates that the application is not running from a mounted .AppImage, for example during development."); + Log.Warn($"Unable to locate .AppImage root from '{ProcessExePath}'. This warning indicates that the application is not running from a mounted .AppImage, for example during development."); return; } - var rootDir = ourPath.Substring(0, ix); + var rootDir = ProcessExePath.Substring(0, ix); var contentsDir = Path.Combine(rootDir, "usr", "bin"); var updateExe = Path.Combine(contentsDir, "UpdateNix"); var metadataPath = Path.Combine(contentsDir, CoreUtil.SpecVersionFileName); diff --git a/src/lib-csharp/Locators/OsxVelopackLocator.cs b/src/lib-csharp/Locators/OsxVelopackLocator.cs index dbf9d8e6..e15db6bb 100644 --- a/src/lib-csharp/Locators/OsxVelopackLocator.cs +++ b/src/lib-csharp/Locators/OsxVelopackLocator.cs @@ -49,23 +49,25 @@ namespace Velopack.Locators /// Creates a new and auto-detects the /// app information from metadata embedded in the .app. /// - public OsxVelopackLocator(ILogger logger) + public OsxVelopackLocator(string currentProcessPath, uint currentProcessId, ILogger logger) : base(logger) { if (!VelopackRuntimeInfo.IsOSX) throw new NotSupportedException("Cannot instantiate OsxLocator on a non-osx system."); + + ProcessId = currentProcessId; + ProcessExePath = currentProcessPath; Log.Info($"Initialising {nameof(OsxVelopackLocator)}"); // are we inside a .app? - var ourPath = VelopackRuntimeInfo.EntryExePath; - var ix = ourPath.IndexOf(".app/", StringComparison.InvariantCultureIgnoreCase); + var ix = ProcessExePath.IndexOf(".app/", StringComparison.InvariantCultureIgnoreCase); if (ix <= 0) { - Log.Warn($"Unable to locate .app root from '{ourPath}'"); + Log.Warn($"Unable to locate .app root from '{ProcessExePath}'"); return; } - var appPath = ourPath.Substring(0, ix + 4); + var appPath = ProcessExePath.Substring(0, ix + 4); var contentsDir = Path.Combine(appPath, "Contents"); var macosDir = Path.Combine(contentsDir, "MacOS"); var updateExe = Path.Combine(macosDir, "UpdateMac"); diff --git a/src/lib-csharp/Locators/VelopackLocator.cs b/src/lib-csharp/Locators/VelopackLocator.cs index cc95b199..17cd3b67 100644 --- a/src/lib-csharp/Locators/VelopackLocator.cs +++ b/src/lib-csharp/Locators/VelopackLocator.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.IO; using System.Linq; using System.Text; @@ -27,14 +28,18 @@ namespace Velopack.Locators if (_current != null) return _current; + var process = Process.GetCurrentProcess(); + var processExePath = process.MainModule?.FileName ?? throw new InvalidOperationException("Could not determine process path."); + var processId = (uint)process.Id; + if (VelopackRuntimeInfo.IsWindows) - return _current = new WindowsVelopackLocator(log); + return _current = new WindowsVelopackLocator(processExePath, processId, log); if (VelopackRuntimeInfo.IsOSX) - return _current = new OsxVelopackLocator(log); + return _current = new OsxVelopackLocator(processExePath, processId, log); if (VelopackRuntimeInfo.IsLinux) - return _current = new LinuxVelopackLocator(log); + return _current = new LinuxVelopackLocator(processExePath, processId, log); throw new PlatformNotSupportedException($"OS platform '{VelopackRuntimeInfo.SystemOs.GetOsLongName()}' is not supported."); } @@ -62,12 +67,18 @@ namespace Velopack.Locators /// public virtual bool IsPortable => false; + + /// + public uint ProcessId { get; protected set; } + + /// + public string ProcessExePath { get; protected set; } /// public virtual string? ThisExeRelativePath { get { if (AppContentDir == null) return null; - var path = VelopackRuntimeInfo.EntryExePath; + var path = ProcessExePath; if (path.StartsWith(AppContentDir, StringComparison.OrdinalIgnoreCase)) { return path.Substring(AppContentDir.Length + 1); } else { diff --git a/src/lib-csharp/Locators/WindowsVelopackLocator.cs b/src/lib-csharp/Locators/WindowsVelopackLocator.cs index 9f171f7b..3b4c9b52 100644 --- a/src/lib-csharp/Locators/WindowsVelopackLocator.cs +++ b/src/lib-csharp/Locators/WindowsVelopackLocator.cs @@ -33,37 +33,32 @@ namespace Velopack.Locators public override string? PackagesDir => CreateSubDirIfDoesNotExist(RootAppDir, "packages"); /// - public override bool IsPortable => - RootAppDir != null ? File.Exists(Path.Combine(RootAppDir, ".portable")) : false; + public override bool IsPortable => RootAppDir != null && File.Exists(Path.Combine(RootAppDir, ".portable")); /// public override string? Channel { get; } /// - public WindowsVelopackLocator(ILogger logger) : this(VelopackRuntimeInfo.EntryExePath, logger) - { - } - - /// - /// Internal use only. Auto detect app details from the specified EXE path. - /// - internal WindowsVelopackLocator(string ourExePath, ILogger logger) + public WindowsVelopackLocator(string currentProcessPath, uint currentProcessId, ILogger logger) : base(logger) { if (!VelopackRuntimeInfo.IsWindows) throw new NotSupportedException("Cannot instantiate WindowsLocator on a non-Windows system."); + ProcessId = currentProcessId; + ProcessExePath = currentProcessPath; + // We try various approaches here. Firstly, if Update.exe is in the parent directory, // we use that. If it's not present, we search for a parent "current" or "app-{ver}" directory, // which could designate that this executable is running in a nested sub-directory. // There is some legacy code here, because it's possible that we're running in an "app-{ver}" // directory which is NOT containing a sq.version, in which case we need to infer a lot of info. - ourExePath = Path.GetFullPath(ourExePath); - string myDirPath = Path.GetDirectoryName(ourExePath)!; + ProcessExePath = Path.GetFullPath(ProcessExePath); + string myDirPath = Path.GetDirectoryName(ProcessExePath)!; var myDirName = Path.GetFileName(myDirPath); var possibleUpdateExe = Path.GetFullPath(Path.Combine(myDirPath, "..", "Update.exe")); - var ixCurrent = ourExePath.LastIndexOf("/current/", StringComparison.InvariantCultureIgnoreCase); + var ixCurrent = ProcessExePath.LastIndexOf("/current/", StringComparison.InvariantCultureIgnoreCase); Log.Info($"Initializing {nameof(WindowsVelopackLocator)}"); @@ -91,7 +86,7 @@ namespace Velopack.Locators } } else if (ixCurrent > 0) { // this is an attempt to handle the case where we are running in a nested current directory. - var rootDir = ourExePath.Substring(0, ixCurrent); + var rootDir = ProcessExePath.Substring(0, ixCurrent); var currentDir = Path.Combine(rootDir, "current"); var manifestFile = Path.Combine(currentDir, CoreUtil.SpecVersionFileName); possibleUpdateExe = Path.GetFullPath(Path.Combine(rootDir, "Update.exe")); diff --git a/src/lib-csharp/UpdateManager.Helpers.cs b/src/lib-csharp/UpdateManager.Helpers.cs index 03f9f4be..6c0b5319 100644 --- a/src/lib-csharp/UpdateManager.Helpers.cs +++ b/src/lib-csharp/UpdateManager.Helpers.cs @@ -45,13 +45,13 @@ namespace Velopack /// The arguments to pass to the application when it is restarted. public void WaitExitThenApplyUpdates(VelopackAsset? toApply, bool silent = false, bool restart = true, string[]? restartArgs = null) { - UpdateExe.Apply(Locator, toApply, silent, VelopackRuntimeInfo.ProcessId, restart, restartArgs, Log); + UpdateExe.Apply(Locator, toApply, silent, Locator.ProcessId, restart, restartArgs, Log); } /// public async Task WaitExitThenApplyUpdatesAsync(VelopackAsset? toApply, bool silent = false, bool restart = true, string[]? restartArgs = null) { - await UpdateExe.ApplyAsync(Locator, toApply, silent, VelopackRuntimeInfo.ProcessId, restart, restartArgs, Log).ConfigureAwait(false); + await UpdateExe.ApplyAsync(Locator, toApply, silent, Locator.ProcessId, restart, restartArgs, Log).ConfigureAwait(false); } } } diff --git a/src/lib-csharp/Util/IoUtil.cs b/src/lib-csharp/Util/IoUtil.cs index b2bae288..6cc463d8 100644 --- a/src/lib-csharp/Util/IoUtil.cs +++ b/src/lib-csharp/Util/IoUtil.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.IO; using System.Linq; using System.Security.Cryptography; @@ -77,9 +78,16 @@ namespace Velopack.Util logger ??= NullLogger.Instance; logger.Debug($"Starting to delete: {path}"); + string? currentExePath = null; + try { + currentExePath = Process.GetCurrentProcess().MainModule?.FileName; + } catch { + // ... ignore + } + try { if (File.Exists(path)) { - DeleteFsiVeryHard(new FileInfo(path), logger); + DeleteFsiVeryHard(new FileInfo(path), currentExePath, logger); } else if (Directory.Exists(path)) { if (renameFirst) { // if there are locked files in a directory, we will not attempt to delte it @@ -88,7 +96,7 @@ namespace Velopack.Util path = oldPath; } - DeleteFsiTree(new DirectoryInfo(path), logger); + DeleteFsiTree(new DirectoryInfo(path), currentExePath, logger); } else { if (throwOnFailure) logger?.Warn($"Cannot delete '{path}' if it does not exist."); @@ -103,11 +111,11 @@ namespace Velopack.Util } } - private static void DeleteFsiTree(FileSystemInfo fileSystemInfo, ILogger logger) + private static void DeleteFsiTree(FileSystemInfo fileSystemInfo, string? currentExePath, ILogger logger) { // if junction / symlink, don't iterate, just delete it. if (fileSystemInfo.Attributes.HasFlag(FileAttributes.ReparsePoint)) { - DeleteFsiVeryHard(fileSystemInfo, logger); + DeleteFsiVeryHard(fileSystemInfo, currentExePath, logger); return; } @@ -115,7 +123,7 @@ namespace Velopack.Util try { if (fileSystemInfo is DirectoryInfo directoryInfo) { foreach (FileSystemInfo childInfo in directoryInfo.GetFileSystemInfos()) { - DeleteFsiTree(childInfo, logger); + DeleteFsiTree(childInfo, currentExePath, logger); } } } catch (Exception ex) { @@ -124,14 +132,15 @@ namespace Velopack.Util // finally, delete myself, we should try this even if deleting children failed // because Directory.Delete can also be recursive - DeleteFsiVeryHard(fileSystemInfo, logger); + DeleteFsiVeryHard(fileSystemInfo, currentExePath, logger); } - private static void DeleteFsiVeryHard(FileSystemInfo fileSystemInfo, ILogger logger) + private static void DeleteFsiVeryHard(FileSystemInfo fileSystemInfo, string? currentExePath, ILogger logger) { // don't try to delete the running process - if (PathUtil.FullPathEquals(fileSystemInfo.FullName, VelopackRuntimeInfo.EntryExePath)) + if (currentExePath != null && PathUtil.FullPathEquals(fileSystemInfo.FullName, currentExePath)) { return; + } // try to remove "ReadOnly" attributes try { fileSystemInfo.Attributes = FileAttributes.Normal; } catch { } diff --git a/src/lib-csharp/VelopackApp.cs b/src/lib-csharp/VelopackApp.cs index 1450c6fa..995f08b9 100644 --- a/src/lib-csharp/VelopackApp.cs +++ b/src/lib-csharp/VelopackApp.cs @@ -224,7 +224,7 @@ namespace Velopack log.Info($"Launching app is out-dated. Current: {myVersion}, Newest Local Available: {latestLocal.Version}"); if (!restarted && _autoApply) { log.Info("Auto apply is true, so restarting to apply update..."); - UpdateExe.Apply(locator, latestLocal, false, VelopackRuntimeInfo.ProcessId, true, args, log); + UpdateExe.Apply(locator, latestLocal, false, locator.ProcessId, true, args, log); Exit(0); } else { log.Info("Pre-condition failed, we will not restart to apply updates. (restarted: " + restarted + ", autoApply: " + _autoApply + ")"); diff --git a/src/lib-csharp/VelopackRuntimeInfo.cs b/src/lib-csharp/VelopackRuntimeInfo.cs index d17c8b91..a9a89311 100644 --- a/src/lib-csharp/VelopackRuntimeInfo.cs +++ b/src/lib-csharp/VelopackRuntimeInfo.cs @@ -34,7 +34,9 @@ namespace System.Runtime.Versioning namespace System.Runtime.CompilerServices { - internal static class IsExternalInit { } + internal static class IsExternalInit + { + } } #endif @@ -88,12 +90,6 @@ namespace Velopack /// The current compiled Velopack ProductVersion. public static NuGetVersion VelopackProductVersion { get; } - /// The path on disk of the entry assembly. - public static string EntryExePath { get; } - - /// The current executing process ID. - public static uint ProcessId { get; } - /// The current machine architecture, ignoring the current process / pe architecture. public static RuntimeCpu SystemArch { get; private set; } @@ -125,10 +121,6 @@ namespace Velopack static VelopackRuntimeInfo() { - var currentProcess = System.Diagnostics.Process.GetCurrentProcess(); - EntryExePath = currentProcess.MainModule.FileName; - ProcessId = (uint)currentProcess.Id; - #if DEBUG InUnitTestRunner = CheckForUnitTestRunner(); #endif @@ -145,6 +137,7 @@ namespace Velopack VelopackNugetVersion = NuGetVersion.Parse(VelopackNugetVersion.ToNormalizedString() + "-g" + VelopackNugetVersion.Metadata); } } + VelopackDisplayVersion = VelopackNugetVersion.ToNormalizedString() + (VelopackNugetVersion.IsPrerelease ? " (prerelease)" : ""); #pragma warning restore CS0612