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

@@ -47,6 +47,18 @@ namespace Velopack.Locators
/// </summary>
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>
/// Finds .nupkg files in the PackagesDir and returns a list of ReleaseEntryName objects.
/// </summary>

View File

@@ -52,23 +52,25 @@ namespace Velopack.Locators
/// Creates a new <see cref="OsxVelopackLocator"/> and auto-detects the
/// app information from metadata embedded in the .app.
/// </summary>
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);

View File

@@ -49,23 +49,25 @@ namespace Velopack.Locators
/// Creates a new <see cref="OsxVelopackLocator"/> and auto-detects the
/// app information from metadata embedded in the .app.
/// </summary>
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");

View File

@@ -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.");
}
@@ -63,11 +68,17 @@ namespace Velopack.Locators
/// <inheritdoc/>
public virtual bool IsPortable => false;
/// <inheritdoc/>
public uint ProcessId { get; protected set; }
/// <inheritdoc/>
public string ProcessExePath { get; protected set; }
/// <inheritdoc/>
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 {

View File

@@ -33,37 +33,32 @@ namespace Velopack.Locators
public override string? PackagesDir => CreateSubDirIfDoesNotExist(RootAppDir, "packages");
/// <inheritdoc />
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"));
/// <inheritdoc />
public override string? Channel { get; }
/// <inheritdoc cref="WindowsVelopackLocator" />
public WindowsVelopackLocator(ILogger logger) : this(VelopackRuntimeInfo.EntryExePath, logger)
{
}
/// <summary>
/// Internal use only. Auto detect app details from the specified EXE path.
/// </summary>
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"));

View File

@@ -45,13 +45,13 @@ namespace Velopack
/// <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)
{
UpdateExe.Apply(Locator, toApply, silent, VelopackRuntimeInfo.ProcessId, restart, restartArgs, Log);
UpdateExe.Apply(Locator, toApply, silent, Locator.ProcessId, restart, restartArgs, Log);
}
/// <inheritdoc cref="WaitExitThenApplyUpdates"/>
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.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 { }

View File

@@ -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 + ")");

View File

@@ -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
/// <summary> The current compiled Velopack ProductVersion. </summary>
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>
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