Make ProcessInfo mockable, to help unusual setups and Unity

This commit is contained in:
Caelan Sayler
2025-02-02 20:52:18 +00:00
committed by Caelan
parent 567c7cf5ec
commit e38b01eef2
9 changed files with 75 additions and 51 deletions

View File

@@ -46,6 +46,18 @@ namespace Velopack.Locators
/// home directory. /// home directory.
/// </summary> /// </summary>
public bool IsPortable { get; } public bool IsPortable { get; }
/// <summary>
/// The process for which the Velopack Locator has been constructed. This should usually be the current process path.
/// </summary>
public string ProcessExePath { get; }
/// <summary>
/// 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).
/// </summary>
public uint ProcessId { get; }
/// <summary> /// <summary>
/// Finds .nupkg files in the PackagesDir and returns a list of ReleaseEntryName objects. /// Finds .nupkg files in the PackagesDir and returns a list of ReleaseEntryName objects.

View File

@@ -32,7 +32,7 @@ namespace Velopack.Locators
/// <inheritdoc /> /// <inheritdoc />
public override string? Channel { get; } public override string? Channel { get; }
/// <inheritdoc /> /// <inheritdoc />
public override string? AppTempDir => CreateSubDirIfDoesNotExist(TempUtil.GetDefaultTempBaseDirectory(), AppId); public override string? AppTempDir => CreateSubDirIfDoesNotExist(TempUtil.GetDefaultTempBaseDirectory(), AppId);
@@ -52,23 +52,25 @@ namespace Velopack.Locators
/// Creates a new <see cref="OsxVelopackLocator"/> and auto-detects the /// Creates a new <see cref="OsxVelopackLocator"/> and auto-detects the
/// app information from metadata embedded in the .app. /// app information from metadata embedded in the .app.
/// </summary> /// </summary>
public LinuxVelopackLocator(ILogger logger) public LinuxVelopackLocator(string currentProcessPath, uint currentProcessId, ILogger logger)
: base(logger) : base(logger)
{ {
if (!VelopackRuntimeInfo.IsLinux) if (!VelopackRuntimeInfo.IsLinux)
throw new NotSupportedException("Cannot instantiate LinuxVelopackLocator on a non-linux system."); throw new NotSupportedException("Cannot instantiate LinuxVelopackLocator on a non-linux system.");
ProcessId = currentProcessId;
ProcessExePath = currentProcessPath;
Log.Info($"Initialising {nameof(LinuxVelopackLocator)}"); Log.Info($"Initialising {nameof(LinuxVelopackLocator)}");
// are we inside a mounted .AppImage? // are we inside a mounted .AppImage?
var ourPath = VelopackRuntimeInfo.EntryExePath; var ix = ProcessExePath.IndexOf("/usr/bin/", StringComparison.InvariantCultureIgnoreCase);
var ix = ourPath.IndexOf("/usr/bin/", StringComparison.InvariantCultureIgnoreCase);
if (ix <= 0) { 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; return;
} }
var rootDir = ourPath.Substring(0, ix); var rootDir = ProcessExePath.Substring(0, ix);
var contentsDir = Path.Combine(rootDir, "usr", "bin"); var contentsDir = Path.Combine(rootDir, "usr", "bin");
var updateExe = Path.Combine(contentsDir, "UpdateNix"); var updateExe = Path.Combine(contentsDir, "UpdateNix");
var metadataPath = Path.Combine(contentsDir, CoreUtil.SpecVersionFileName); var metadataPath = Path.Combine(contentsDir, CoreUtil.SpecVersionFileName);

View File

@@ -49,23 +49,25 @@ namespace Velopack.Locators
/// Creates a new <see cref="OsxVelopackLocator"/> and auto-detects the /// Creates a new <see cref="OsxVelopackLocator"/> and auto-detects the
/// app information from metadata embedded in the .app. /// app information from metadata embedded in the .app.
/// </summary> /// </summary>
public OsxVelopackLocator(ILogger logger) public OsxVelopackLocator(string currentProcessPath, uint currentProcessId, ILogger logger)
: base(logger) : base(logger)
{ {
if (!VelopackRuntimeInfo.IsOSX) if (!VelopackRuntimeInfo.IsOSX)
throw new NotSupportedException("Cannot instantiate OsxLocator on a non-osx system."); throw new NotSupportedException("Cannot instantiate OsxLocator on a non-osx system.");
ProcessId = currentProcessId;
ProcessExePath = currentProcessPath;
Log.Info($"Initialising {nameof(OsxVelopackLocator)}"); Log.Info($"Initialising {nameof(OsxVelopackLocator)}");
// are we inside a .app? // are we inside a .app?
var ourPath = VelopackRuntimeInfo.EntryExePath; var ix = ProcessExePath.IndexOf(".app/", StringComparison.InvariantCultureIgnoreCase);
var ix = ourPath.IndexOf(".app/", StringComparison.InvariantCultureIgnoreCase);
if (ix <= 0) { if (ix <= 0) {
Log.Warn($"Unable to locate .app root from '{ourPath}'"); Log.Warn($"Unable to locate .app root from '{ProcessExePath}'");
return; return;
} }
var appPath = ourPath.Substring(0, ix + 4); var appPath = ProcessExePath.Substring(0, ix + 4);
var contentsDir = Path.Combine(appPath, "Contents"); var contentsDir = Path.Combine(appPath, "Contents");
var macosDir = Path.Combine(contentsDir, "MacOS"); var macosDir = Path.Combine(contentsDir, "MacOS");
var updateExe = Path.Combine(macosDir, "UpdateMac"); var updateExe = Path.Combine(macosDir, "UpdateMac");

View File

@@ -1,5 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
@@ -27,14 +28,18 @@ namespace Velopack.Locators
if (_current != null) if (_current != null)
return _current; 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) if (VelopackRuntimeInfo.IsWindows)
return _current = new WindowsVelopackLocator(log); return _current = new WindowsVelopackLocator(processExePath, processId, log);
if (VelopackRuntimeInfo.IsOSX) if (VelopackRuntimeInfo.IsOSX)
return _current = new OsxVelopackLocator(log); return _current = new OsxVelopackLocator(processExePath, processId, log);
if (VelopackRuntimeInfo.IsLinux) 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."); throw new PlatformNotSupportedException($"OS platform '{VelopackRuntimeInfo.SystemOs.GetOsLongName()}' is not supported.");
} }
@@ -62,12 +67,18 @@ namespace Velopack.Locators
/// <inheritdoc/> /// <inheritdoc/>
public virtual bool IsPortable => false; public virtual bool IsPortable => false;
/// <inheritdoc/>
public uint ProcessId { get; protected set; }
/// <inheritdoc/>
public string ProcessExePath { get; protected set; }
/// <inheritdoc/> /// <inheritdoc/>
public virtual string? ThisExeRelativePath { public virtual string? ThisExeRelativePath {
get { get {
if (AppContentDir == null) return null; if (AppContentDir == null) return null;
var path = VelopackRuntimeInfo.EntryExePath; var path = ProcessExePath;
if (path.StartsWith(AppContentDir, StringComparison.OrdinalIgnoreCase)) { if (path.StartsWith(AppContentDir, StringComparison.OrdinalIgnoreCase)) {
return path.Substring(AppContentDir.Length + 1); return path.Substring(AppContentDir.Length + 1);
} else { } else {

View File

@@ -33,37 +33,32 @@ namespace Velopack.Locators
public override string? PackagesDir => CreateSubDirIfDoesNotExist(RootAppDir, "packages"); public override string? PackagesDir => CreateSubDirIfDoesNotExist(RootAppDir, "packages");
/// <inheritdoc /> /// <inheritdoc />
public override bool IsPortable => public override bool IsPortable => RootAppDir != null && File.Exists(Path.Combine(RootAppDir, ".portable"));
RootAppDir != null ? File.Exists(Path.Combine(RootAppDir, ".portable")) : false;
/// <inheritdoc /> /// <inheritdoc />
public override string? Channel { get; } public override string? Channel { get; }
/// <inheritdoc cref="WindowsVelopackLocator" /> /// <inheritdoc cref="WindowsVelopackLocator" />
public WindowsVelopackLocator(ILogger logger) : this(VelopackRuntimeInfo.EntryExePath, logger) public WindowsVelopackLocator(string currentProcessPath, uint currentProcessId, ILogger logger)
{
}
/// <summary>
/// Internal use only. Auto detect app details from the specified EXE path.
/// </summary>
internal WindowsVelopackLocator(string ourExePath, ILogger logger)
: base(logger) : base(logger)
{ {
if (!VelopackRuntimeInfo.IsWindows) if (!VelopackRuntimeInfo.IsWindows)
throw new NotSupportedException("Cannot instantiate WindowsLocator on a non-Windows system."); 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 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, // 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. // 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}" // 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. // directory which is NOT containing a sq.version, in which case we need to infer a lot of info.
ourExePath = Path.GetFullPath(ourExePath); ProcessExePath = Path.GetFullPath(ProcessExePath);
string myDirPath = Path.GetDirectoryName(ourExePath)!; string myDirPath = Path.GetDirectoryName(ProcessExePath)!;
var myDirName = Path.GetFileName(myDirPath); var myDirName = Path.GetFileName(myDirPath);
var possibleUpdateExe = Path.GetFullPath(Path.Combine(myDirPath, "..", "Update.exe")); 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)}"); Log.Info($"Initializing {nameof(WindowsVelopackLocator)}");
@@ -91,7 +86,7 @@ namespace Velopack.Locators
} }
} else if (ixCurrent > 0) { } else if (ixCurrent > 0) {
// this is an attempt to handle the case where we are running in a nested current directory. // 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 currentDir = Path.Combine(rootDir, "current");
var manifestFile = Path.Combine(currentDir, CoreUtil.SpecVersionFileName); var manifestFile = Path.Combine(currentDir, CoreUtil.SpecVersionFileName);
possibleUpdateExe = Path.GetFullPath(Path.Combine(rootDir, "Update.exe")); possibleUpdateExe = Path.GetFullPath(Path.Combine(rootDir, "Update.exe"));

View File

@@ -45,13 +45,13 @@ namespace Velopack
/// <param name="restartArgs">The arguments to pass to the application when it is restarted.</param> /// <param name="restartArgs">The arguments to pass to the application when it is restarted.</param>
public void WaitExitThenApplyUpdates(VelopackAsset? toApply, bool silent = false, bool restart = true, string[]? restartArgs = null) 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);
} }
/// <inheritdoc cref="WaitExitThenApplyUpdates"/> /// <inheritdoc cref="WaitExitThenApplyUpdates"/>
public async Task WaitExitThenApplyUpdatesAsync(VelopackAsset? toApply, bool silent = false, bool restart = true, string[]? restartArgs = null) 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);
} }
} }
} }

View File

@@ -1,5 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Security.Cryptography; using System.Security.Cryptography;
@@ -77,9 +78,16 @@ namespace Velopack.Util
logger ??= NullLogger.Instance; logger ??= NullLogger.Instance;
logger.Debug($"Starting to delete: {path}"); logger.Debug($"Starting to delete: {path}");
string? currentExePath = null;
try {
currentExePath = Process.GetCurrentProcess().MainModule?.FileName;
} catch {
// ... ignore
}
try { try {
if (File.Exists(path)) { if (File.Exists(path)) {
DeleteFsiVeryHard(new FileInfo(path), logger); DeleteFsiVeryHard(new FileInfo(path), currentExePath, logger);
} else if (Directory.Exists(path)) { } else if (Directory.Exists(path)) {
if (renameFirst) { if (renameFirst) {
// if there are locked files in a directory, we will not attempt to delte it // if there are locked files in a directory, we will not attempt to delte it
@@ -88,7 +96,7 @@ namespace Velopack.Util
path = oldPath; path = oldPath;
} }
DeleteFsiTree(new DirectoryInfo(path), logger); DeleteFsiTree(new DirectoryInfo(path), currentExePath, logger);
} else { } else {
if (throwOnFailure) if (throwOnFailure)
logger?.Warn($"Cannot delete '{path}' if it does not exist."); 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 junction / symlink, don't iterate, just delete it.
if (fileSystemInfo.Attributes.HasFlag(FileAttributes.ReparsePoint)) { if (fileSystemInfo.Attributes.HasFlag(FileAttributes.ReparsePoint)) {
DeleteFsiVeryHard(fileSystemInfo, logger); DeleteFsiVeryHard(fileSystemInfo, currentExePath, logger);
return; return;
} }
@@ -115,7 +123,7 @@ namespace Velopack.Util
try { try {
if (fileSystemInfo is DirectoryInfo directoryInfo) { if (fileSystemInfo is DirectoryInfo directoryInfo) {
foreach (FileSystemInfo childInfo in directoryInfo.GetFileSystemInfos()) { foreach (FileSystemInfo childInfo in directoryInfo.GetFileSystemInfos()) {
DeleteFsiTree(childInfo, logger); DeleteFsiTree(childInfo, currentExePath, logger);
} }
} }
} catch (Exception ex) { } catch (Exception ex) {
@@ -124,14 +132,15 @@ namespace Velopack.Util
// finally, delete myself, we should try this even if deleting children failed // finally, delete myself, we should try this even if deleting children failed
// because Directory.Delete can also be recursive // 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 // don't try to delete the running process
if (PathUtil.FullPathEquals(fileSystemInfo.FullName, VelopackRuntimeInfo.EntryExePath)) if (currentExePath != null && PathUtil.FullPathEquals(fileSystemInfo.FullName, currentExePath)) {
return; return;
}
// try to remove "ReadOnly" attributes // try to remove "ReadOnly" attributes
try { fileSystemInfo.Attributes = FileAttributes.Normal; } catch { } try { fileSystemInfo.Attributes = FileAttributes.Normal; } catch { }

View File

@@ -224,7 +224,7 @@ namespace Velopack
log.Info($"Launching app is out-dated. Current: {myVersion}, Newest Local Available: {latestLocal.Version}"); log.Info($"Launching app is out-dated. Current: {myVersion}, Newest Local Available: {latestLocal.Version}");
if (!restarted && _autoApply) { if (!restarted && _autoApply) {
log.Info("Auto apply is true, so restarting to apply update..."); 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); Exit(0);
} else { } else {
log.Info("Pre-condition failed, we will not restart to apply updates. (restarted: " + restarted + ", autoApply: " + _autoApply + ")"); log.Info("Pre-condition failed, we will not restart to apply updates. (restarted: " + restarted + ", autoApply: " + _autoApply + ")");

View File

@@ -34,7 +34,9 @@ namespace System.Runtime.Versioning
namespace System.Runtime.CompilerServices namespace System.Runtime.CompilerServices
{ {
internal static class IsExternalInit { } internal static class IsExternalInit
{
}
} }
#endif #endif
@@ -88,12 +90,6 @@ namespace Velopack
/// <summary> The current compiled Velopack ProductVersion. </summary> /// <summary> The current compiled Velopack ProductVersion. </summary>
public static NuGetVersion VelopackProductVersion { get; } public static NuGetVersion VelopackProductVersion { get; }
/// <summary> The path on disk of the entry assembly. </summary>
public static string EntryExePath { get; }
/// <summary> The current executing process ID. </summary>
public static uint ProcessId { get; }
/// <summary> The current machine architecture, ignoring the current process / pe architecture. </summary> /// <summary> The current machine architecture, ignoring the current process / pe architecture. </summary>
public static RuntimeCpu SystemArch { get; private set; } public static RuntimeCpu SystemArch { get; private set; }
@@ -125,10 +121,6 @@ namespace Velopack
static VelopackRuntimeInfo() static VelopackRuntimeInfo()
{ {
var currentProcess = System.Diagnostics.Process.GetCurrentProcess();
EntryExePath = currentProcess.MainModule.FileName;
ProcessId = (uint)currentProcess.Id;
#if DEBUG #if DEBUG
InUnitTestRunner = CheckForUnitTestRunner(); InUnitTestRunner = CheckForUnitTestRunner();
#endif #endif
@@ -145,6 +137,7 @@ namespace Velopack
VelopackNugetVersion = NuGetVersion.Parse(VelopackNugetVersion.ToNormalizedString() + "-g" + VelopackNugetVersion.Metadata); VelopackNugetVersion = NuGetVersion.Parse(VelopackNugetVersion.ToNormalizedString() + "-g" + VelopackNugetVersion.Metadata);
} }
} }
VelopackDisplayVersion = VelopackNugetVersion.ToNormalizedString() + (VelopackNugetVersion.IsPrerelease ? " (prerelease)" : ""); VelopackDisplayVersion = VelopackNugetVersion.ToNormalizedString() + (VelopackNugetVersion.IsPrerelease ? " (prerelease)" : "");
#pragma warning restore CS0612 #pragma warning restore CS0612