mirror of
https://github.com/velopack/velopack.git
synced 2025-10-25 15:19:22 +00:00
Add new IProcessImpl abstraction to C# library
This commit is contained in:
69
src/lib-csharp/Locators/DefaultProcessImpl.cs
Normal file
69
src/lib-csharp/Locators/DefaultProcessImpl.cs
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.ComponentModel;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.IO;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using Velopack.Logging;
|
||||||
|
using Velopack.Util;
|
||||||
|
|
||||||
|
namespace Velopack.Locators
|
||||||
|
{
|
||||||
|
public class DefaultProcessImpl : IProcessImpl
|
||||||
|
{
|
||||||
|
[DllImport("user32", SetLastError = true)]
|
||||||
|
[return: MarshalAs(UnmanagedType.Bool)]
|
||||||
|
private static extern bool AllowSetForegroundWindow(int dwProcessId);
|
||||||
|
|
||||||
|
private Process? _currentProcess;
|
||||||
|
private readonly IVelopackLogger _logger;
|
||||||
|
|
||||||
|
public DefaultProcessImpl(IVelopackLogger logger)
|
||||||
|
{
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GetCurrentProcessPath()
|
||||||
|
{
|
||||||
|
_currentProcess ??= Process.GetCurrentProcess();
|
||||||
|
var fileName = _currentProcess.MainModule?.FileName ??
|
||||||
|
throw new InvalidOperationException($"Could not determine process path, please construct {nameof(IVelopackLocator)} manually.");
|
||||||
|
return Path.GetFullPath(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
public uint GetCurrentProcessId()
|
||||||
|
{
|
||||||
|
_currentProcess ??= Process.GetCurrentProcess();
|
||||||
|
return (uint) _currentProcess.Id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void StartProcess(string exePath, IEnumerable<string> args, string? workDir, bool showWindow)
|
||||||
|
{
|
||||||
|
var psi = new ProcessStartInfo() {
|
||||||
|
CreateNoWindow = true,
|
||||||
|
FileName = exePath,
|
||||||
|
WorkingDirectory = workDir,
|
||||||
|
};
|
||||||
|
|
||||||
|
psi.AppendArgumentListSafe(args, out var debugArgs);
|
||||||
|
_logger.Debug($"Running: {psi.FileName} {debugArgs}");
|
||||||
|
|
||||||
|
var p = Process.Start(psi);
|
||||||
|
if (p == null) {
|
||||||
|
throw new Exception("Failed to launch process.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (VelopackRuntimeInfo.IsWindows) {
|
||||||
|
try {
|
||||||
|
// this is an attempt to work around a bug where the restarted app fails to come to foreground.
|
||||||
|
if (!AllowSetForegroundWindow(p.Id))
|
||||||
|
throw new Win32Exception();
|
||||||
|
} catch (Exception ex) {
|
||||||
|
_logger.LogWarning(ex, "Failed to allow Update.exe to set foreground window.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
14
src/lib-csharp/Locators/IProcessImpl.cs
Normal file
14
src/lib-csharp/Locators/IProcessImpl.cs
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
|
||||||
|
|
||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Velopack.Locators
|
||||||
|
{
|
||||||
|
public interface IProcessImpl
|
||||||
|
{
|
||||||
|
public string GetCurrentProcessPath();
|
||||||
|
public uint GetCurrentProcessId();
|
||||||
|
public void StartProcess(string exePath, IEnumerable<string> args, string workDir, bool showWindow);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -52,17 +52,11 @@ namespace Velopack.Locators
|
|||||||
bool IsPortable { get; }
|
bool IsPortable { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The process for which the Velopack Locator has been constructed. This should usually be the current process path.
|
/// Provides an abstraction for dealing with the dotnet Process API. This is used to start processes and
|
||||||
|
/// get information about the current process.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
string ProcessExePath { get; }
|
IProcessImpl Process { 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>
|
|
||||||
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.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -24,6 +24,9 @@ namespace Velopack.Locators
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public override string? UpdateExePath { get; }
|
public override string? UpdateExePath { get; }
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override IProcessImpl Process { get; }
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public override SemanticVersion? CurrentlyInstalledVersion { get; }
|
public override SemanticVersion? CurrentlyInstalledVersion { get; }
|
||||||
|
|
||||||
@@ -51,28 +54,23 @@ namespace Velopack.Locators
|
|||||||
/// <summary> File path of the .AppImage which mounted and ran this application. </summary>
|
/// <summary> File path of the .AppImage which mounted and ran this application. </summary>
|
||||||
public string? AppImagePath => Environment.GetEnvironmentVariable("APPIMAGE");
|
public string? AppImagePath => Environment.GetEnvironmentVariable("APPIMAGE");
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public override uint ProcessId { get; }
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public override string ProcessExePath { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 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(string currentProcessPath, uint currentProcessId, IVelopackLogger? customLog)
|
public LinuxVelopackLocator(IProcessImpl? processImpl, IVelopackLogger? customLog)
|
||||||
{
|
{
|
||||||
if (!VelopackRuntimeInfo.IsLinux)
|
if (!VelopackRuntimeInfo.IsLinux)
|
||||||
throw new NotSupportedException($"Cannot instantiate {nameof(LinuxVelopackLocator)} on a non-linux system.");
|
throw new NotSupportedException($"Cannot instantiate {nameof(LinuxVelopackLocator)} on a non-linux system.");
|
||||||
|
|
||||||
ProcessId = currentProcessId;
|
|
||||||
var ourPath = ProcessExePath = currentProcessPath;
|
|
||||||
|
|
||||||
var combinedLog = new CombinedVelopackLogger();
|
var combinedLog = new CombinedVelopackLogger();
|
||||||
combinedLog.Add(customLog);
|
combinedLog.Add(customLog);
|
||||||
Log = combinedLog;
|
Log = combinedLog;
|
||||||
|
|
||||||
|
Process = processImpl ??= new DefaultProcessImpl(combinedLog);
|
||||||
|
var ourPath = processImpl.GetCurrentProcessPath();
|
||||||
|
var currentProcessId = processImpl.GetCurrentProcessId();
|
||||||
|
|
||||||
using var initLog = new CachedVelopackLogger(combinedLog);
|
using var initLog = new CachedVelopackLogger(combinedLog);
|
||||||
initLog.Info($"Initializing {nameof(LinuxVelopackLocator)}");
|
initLog.Info($"Initializing {nameof(LinuxVelopackLocator)}");
|
||||||
var logFilePath = Path.Combine(Path.GetTempPath(), DefaultLoggingFileName);
|
var logFilePath = Path.Combine(Path.GetTempPath(), DefaultLoggingFileName);
|
||||||
|
|||||||
@@ -24,6 +24,9 @@ namespace Velopack.Locators
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public override string? UpdateExePath { get; }
|
public override string? UpdateExePath { get; }
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override IProcessImpl Process { get; }
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public override SemanticVersion? CurrentlyInstalledVersion { get; }
|
public override SemanticVersion? CurrentlyInstalledVersion { get; }
|
||||||
|
|
||||||
@@ -48,27 +51,22 @@ namespace Velopack.Locators
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public override string? Channel { get; }
|
public override string? Channel { get; }
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public override uint ProcessId { get; }
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public override string ProcessExePath { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 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(string currentProcessPath, uint currentProcessId, IVelopackLogger? customLog)
|
public OsxVelopackLocator(IProcessImpl? processImpl, IVelopackLogger? customLog)
|
||||||
{
|
{
|
||||||
if (!VelopackRuntimeInfo.IsOSX)
|
if (!VelopackRuntimeInfo.IsOSX)
|
||||||
throw new NotSupportedException($"Cannot instantiate {nameof(OsxVelopackLocator)} on a non-osx system.");
|
throw new NotSupportedException($"Cannot instantiate {nameof(OsxVelopackLocator)} on a non-osx system.");
|
||||||
|
|
||||||
ProcessId = currentProcessId;
|
|
||||||
var ourPath = ProcessExePath = currentProcessPath;
|
|
||||||
|
|
||||||
var combinedLog = new CombinedVelopackLogger();
|
var combinedLog = new CombinedVelopackLogger();
|
||||||
combinedLog.Add(customLog);
|
combinedLog.Add(customLog);
|
||||||
Log = combinedLog;
|
Log = combinedLog;
|
||||||
|
|
||||||
|
Process = processImpl ??= new DefaultProcessImpl(combinedLog);
|
||||||
|
var ourPath = processImpl.GetCurrentProcessPath();
|
||||||
|
var currentProcessId = processImpl.GetCurrentProcessId();
|
||||||
|
|
||||||
using var initLog = new CachedVelopackLogger(combinedLog);
|
using var initLog = new CachedVelopackLogger(combinedLog);
|
||||||
initLog.Info($"Initializing {nameof(OsxVelopackLocator)}");
|
initLog.Info($"Initializing {nameof(OsxVelopackLocator)}");
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using NuGet.Versioning;
|
using NuGet.Versioning;
|
||||||
using Velopack.Logging;
|
using Velopack.Logging;
|
||||||
@@ -13,7 +14,7 @@ namespace Velopack.Locators
|
|||||||
/// having an installed application. This could be used in a CI/CD pipeline, or unit tests etc.
|
/// having an installed application. This could be used in a CI/CD pipeline, or unit tests etc.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[ExcludeFromCodeCoverage]
|
[ExcludeFromCodeCoverage]
|
||||||
public class TestVelopackLocator : VelopackLocator
|
public class TestVelopackLocator : VelopackLocator, IProcessImpl
|
||||||
{
|
{
|
||||||
public override string? AppId {
|
public override string? AppId {
|
||||||
get {
|
get {
|
||||||
@@ -83,6 +84,8 @@ namespace Velopack.Locators
|
|||||||
|
|
||||||
public override IVelopackLogger Log { get; }
|
public override IVelopackLogger Log { get; }
|
||||||
|
|
||||||
|
public override IProcessImpl Process => this;
|
||||||
|
|
||||||
public override VelopackAsset? GetLatestLocalFullPackage()
|
public override VelopackAsset? GetLatestLocalFullPackage()
|
||||||
{
|
{
|
||||||
if (_asset != null) {
|
if (_asset != null) {
|
||||||
@@ -92,10 +95,6 @@ namespace Velopack.Locators
|
|||||||
return base.GetLatestLocalFullPackage();
|
return base.GetLatestLocalFullPackage();
|
||||||
}
|
}
|
||||||
|
|
||||||
public override uint ProcessId => 0;
|
|
||||||
|
|
||||||
public override string ProcessExePath { get; }
|
|
||||||
|
|
||||||
private readonly string? _updatePath;
|
private readonly string? _updatePath;
|
||||||
private readonly SemanticVersion? _version;
|
private readonly SemanticVersion? _version;
|
||||||
private readonly string? _packages;
|
private readonly string? _packages;
|
||||||
@@ -104,6 +103,7 @@ namespace Velopack.Locators
|
|||||||
private readonly string? _appContent;
|
private readonly string? _appContent;
|
||||||
private readonly string? _channel;
|
private readonly string? _channel;
|
||||||
private readonly VelopackAsset? _asset;
|
private readonly VelopackAsset? _asset;
|
||||||
|
private readonly string? _processPath;
|
||||||
|
|
||||||
/// <inheritdoc cref="TestVelopackLocator" />
|
/// <inheritdoc cref="TestVelopackLocator" />
|
||||||
public TestVelopackLocator(string appId, string version, string packagesDir, IVelopackLogger? logger = null)
|
public TestVelopackLocator(string appId, string version, string packagesDir, IVelopackLogger? logger = null)
|
||||||
@@ -113,7 +113,8 @@ namespace Velopack.Locators
|
|||||||
|
|
||||||
/// <inheritdoc cref="TestVelopackLocator" />
|
/// <inheritdoc cref="TestVelopackLocator" />
|
||||||
public TestVelopackLocator(string appId, string version, string packagesDir, string? appDir,
|
public TestVelopackLocator(string appId, string version, string packagesDir, string? appDir,
|
||||||
string? rootDir, string? updateExe, string? channel = null, IVelopackLogger? logger = null, VelopackAsset? localPackage = null, string processPath = null!)
|
string? rootDir, string? updateExe, string? channel = null, IVelopackLogger? logger = null, VelopackAsset? localPackage = null,
|
||||||
|
string processPath = null!)
|
||||||
{
|
{
|
||||||
_id = appId;
|
_id = appId;
|
||||||
_packages = packagesDir;
|
_packages = packagesDir;
|
||||||
@@ -123,8 +124,23 @@ namespace Velopack.Locators
|
|||||||
_appContent = appDir;
|
_appContent = appDir;
|
||||||
_channel = channel;
|
_channel = channel;
|
||||||
_asset = localPackage;
|
_asset = localPackage;
|
||||||
ProcessExePath = processPath;
|
_processPath = processPath;
|
||||||
Log = logger ?? new NullVelopackLogger();
|
Log = logger ?? new NullVelopackLogger();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public string GetCurrentProcessPath()
|
||||||
|
{
|
||||||
|
return _processPath ?? throw new NotSupportedException("GetCurrentProcessPath is not supported in this test implementation.");
|
||||||
|
}
|
||||||
|
|
||||||
|
public uint GetCurrentProcessId()
|
||||||
|
{
|
||||||
|
return 0; // Not implemented in this test mock
|
||||||
|
}
|
||||||
|
|
||||||
|
public void StartProcess(string exePath, IEnumerable<string> args, string workDir, bool showWindow)
|
||||||
|
{
|
||||||
|
new DefaultProcessImpl(Log).StartProcess(exePath, args, workDir, showWindow);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -40,21 +40,16 @@ namespace Velopack.Locators
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary> Create a new default locator based on the current operating system. </summary>
|
/// <summary> Create a new default locator based on the current operating system. </summary>
|
||||||
public static IVelopackLocator CreateDefaultForPlatform(IVelopackLogger? logger = null)
|
public static IVelopackLocator CreateDefaultForPlatform(IProcessImpl? processImpl = null, IVelopackLogger? logger = null)
|
||||||
{
|
{
|
||||||
var process = Process.GetCurrentProcess();
|
|
||||||
var processExePath = process.MainModule?.FileName
|
|
||||||
?? throw new InvalidOperationException($"Could not determine process path, please construct {nameof(IVelopackLocator)} manually.");
|
|
||||||
var processId = (uint) process.Id;
|
|
||||||
|
|
||||||
if (VelopackRuntimeInfo.IsWindows)
|
if (VelopackRuntimeInfo.IsWindows)
|
||||||
return _current = new WindowsVelopackLocator(processExePath, processId, logger);
|
return _current = new WindowsVelopackLocator(processImpl, logger);
|
||||||
|
|
||||||
if (VelopackRuntimeInfo.IsOSX)
|
if (VelopackRuntimeInfo.IsOSX)
|
||||||
return _current = new OsxVelopackLocator(processExePath, processId, logger);
|
return _current = new OsxVelopackLocator(processImpl, logger);
|
||||||
|
|
||||||
if (VelopackRuntimeInfo.IsLinux)
|
if (VelopackRuntimeInfo.IsLinux)
|
||||||
return _current = new LinuxVelopackLocator(processExePath, processId, logger);
|
return _current = new LinuxVelopackLocator(processImpl, logger);
|
||||||
|
|
||||||
throw new PlatformNotSupportedException($"OS platform '{VelopackRuntimeInfo.SystemOs.GetOsLongName()}' is not supported.");
|
throw new PlatformNotSupportedException($"OS platform '{VelopackRuntimeInfo.SystemOs.GetOsLongName()}' is not supported.");
|
||||||
}
|
}
|
||||||
@@ -64,9 +59,9 @@ namespace Velopack.Locators
|
|||||||
_current = locator;
|
_current = locator;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static IVelopackLocator GetCurrentOrCreateDefault(IVelopackLogger? logger = null)
|
internal static IVelopackLocator GetCurrentOrCreateDefault(IProcessImpl? processImpl = null, IVelopackLogger? logger = null)
|
||||||
{
|
{
|
||||||
_current ??= CreateDefaultForPlatform(logger);
|
_current ??= CreateDefaultForPlatform(processImpl, logger);
|
||||||
return _current;
|
return _current;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -94,20 +89,17 @@ namespace Velopack.Locators
|
|||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public abstract IVelopackLogger Log { get; }
|
public abstract IVelopackLogger Log { get; }
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public abstract uint ProcessId { get; }
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public abstract string ProcessExePath { get; }
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public virtual bool IsPortable => false;
|
public virtual bool IsPortable => false;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public abstract IProcessImpl Process { get; }
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public virtual string? ThisExeRelativePath {
|
public virtual string? ThisExeRelativePath {
|
||||||
get {
|
get {
|
||||||
if (AppContentDir == null) return null;
|
if (AppContentDir == null) return null;
|
||||||
var path = ProcessExePath;
|
var path = Process.GetCurrentProcessPath();
|
||||||
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 {
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
using System.Runtime.Versioning;
|
using System.Runtime.Versioning;
|
||||||
using NuGet.Versioning;
|
using NuGet.Versioning;
|
||||||
using Velopack.Logging;
|
using Velopack.Logging;
|
||||||
@@ -30,8 +32,10 @@ namespace Velopack.Locators
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public override SemanticVersion? CurrentlyInstalledVersion { get; }
|
public override SemanticVersion? CurrentlyInstalledVersion { get; }
|
||||||
|
|
||||||
|
private readonly Lazy<string?> _packagesDir;
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public override string? PackagesDir => CreateSubDirIfDoesNotExist(RootAppDir, "packages");
|
public override string? PackagesDir => _packagesDir.Value;
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public override IVelopackLogger Log { get; }
|
public override IVelopackLogger Log { get; }
|
||||||
@@ -39,30 +43,30 @@ namespace Velopack.Locators
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public override bool IsPortable => RootAppDir != null && File.Exists(Path.Combine(RootAppDir, ".portable"));
|
public override bool IsPortable => RootAppDir != null && File.Exists(Path.Combine(RootAppDir, ".portable"));
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override IProcessImpl Process { get; }
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public override string? Channel { get; }
|
public override string? Channel { get; }
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public override uint ProcessId { get; }
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public override string ProcessExePath { get; }
|
|
||||||
|
|
||||||
/// <inheritdoc cref="WindowsVelopackLocator" />
|
/// <inheritdoc cref="WindowsVelopackLocator" />
|
||||||
public WindowsVelopackLocator(string currentProcessPath, uint currentProcessId, IVelopackLogger? customLog)
|
public WindowsVelopackLocator(IProcessImpl? processImpl, IVelopackLogger? customLog)
|
||||||
{
|
{
|
||||||
if (!VelopackRuntimeInfo.IsWindows)
|
if (!VelopackRuntimeInfo.IsWindows)
|
||||||
throw new NotSupportedException($"Cannot instantiate {nameof(WindowsVelopackLocator)} on a non-Windows system.");
|
throw new NotSupportedException($"Cannot instantiate {nameof(WindowsVelopackLocator)} on a non-Windows system.");
|
||||||
|
|
||||||
ProcessId = currentProcessId;
|
_packagesDir = new(GetPackagesDir);
|
||||||
var ourPath = ProcessExePath = currentProcessPath;
|
|
||||||
|
|
||||||
var combinedLog = new CombinedVelopackLogger();
|
var combinedLog = new CombinedVelopackLogger();
|
||||||
combinedLog.Add(customLog);
|
combinedLog.Add(customLog);
|
||||||
Log = combinedLog;
|
Log = combinedLog;
|
||||||
|
|
||||||
|
Process = processImpl ??= new DefaultProcessImpl(combinedLog);
|
||||||
|
var ourPath = processImpl.GetCurrentProcessPath();
|
||||||
|
var currentProcessId = processImpl.GetCurrentProcessId();
|
||||||
|
|
||||||
using var initLog = new CachedVelopackLogger(combinedLog);
|
using var initLog = new CachedVelopackLogger(combinedLog);
|
||||||
initLog.Info($"Initialising {nameof(WindowsVelopackLocator)}");
|
initLog.Info($"Initializing {nameof(WindowsVelopackLocator)}");
|
||||||
|
|
||||||
// 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,
|
||||||
@@ -70,7 +74,6 @@ namespace Velopack.Locators
|
|||||||
// 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.
|
||||||
|
|
||||||
ProcessExePath = Path.GetFullPath(ourPath);
|
|
||||||
string myDirPath = Path.GetDirectoryName(ourPath)!;
|
string myDirPath = Path.GetDirectoryName(ourPath)!;
|
||||||
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"));
|
||||||
@@ -120,36 +123,99 @@ namespace Velopack.Locators
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool fileLogCreated = false;
|
if (UpdateExePath != null
|
||||||
|
&& Path.GetDirectoryName(UpdateExePath) is { } updateExeDirectory
|
||||||
|
&& !PathUtil.IsDirectoryWritable(updateExeDirectory)) {
|
||||||
|
var tempTargetUpdateExe = Path.Combine(TempAppRootDirectory, "Update.exe");
|
||||||
|
if (File.Exists(UpdateExePath) && !File.Exists(tempTargetUpdateExe)) {
|
||||||
|
initLog.Warn("Application directory is not writable. Copying Update.exe to temp location: " + tempTargetUpdateExe);
|
||||||
|
Debugger.Launch();
|
||||||
|
Directory.CreateDirectory(TempAppRootDirectory);
|
||||||
|
File.Copy(UpdateExePath, tempTargetUpdateExe);
|
||||||
|
}
|
||||||
|
UpdateExePath = tempTargetUpdateExe;
|
||||||
|
}
|
||||||
|
|
||||||
|
//bool fileLogCreated = false;
|
||||||
|
Exception? fileLogException = null;
|
||||||
if (!String.IsNullOrEmpty(AppId) && !String.IsNullOrEmpty(RootAppDir)) {
|
if (!String.IsNullOrEmpty(AppId) && !String.IsNullOrEmpty(RootAppDir)) {
|
||||||
try {
|
try {
|
||||||
var logFilePath = Path.Combine(RootAppDir, DefaultLoggingFileName);
|
var logFilePath = Path.Combine(RootAppDir, DefaultLoggingFileName);
|
||||||
var fileLog = new FileVelopackLogger(logFilePath, currentProcessId);
|
var fileLog = new FileVelopackLogger(logFilePath, currentProcessId);
|
||||||
combinedLog.Add(fileLog);
|
combinedLog.Add(fileLog);
|
||||||
fileLogCreated = true;
|
//fileLogCreated = true;
|
||||||
} catch (Exception ex2) {
|
} catch (Exception ex) {
|
||||||
initLog.Error("Unable to create default file logger: " + ex2);
|
fileLogException = ex;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// if the RootAppDir was unwritable, or we don't know the app id, we could try to write to the temp folder instead.
|
// if the RootAppDir was unwritable, or we don't know the app id, we could try to write to the temp folder instead.
|
||||||
if (!fileLogCreated) {
|
Exception? tempFileLogException = null;
|
||||||
|
if (fileLogException is not null) {
|
||||||
try {
|
try {
|
||||||
var logFileName = String.IsNullOrEmpty(AppId) ? DefaultLoggingFileName : $"velopack_{AppId}.log";
|
var logFileName = String.IsNullOrEmpty(AppId) ? DefaultLoggingFileName : $"velopack_{AppId}.log";
|
||||||
var logFilePath = Path.Combine(Path.GetTempPath(), logFileName);
|
var logFilePath = Path.Combine(Path.GetTempPath(), logFileName);
|
||||||
var fileLog = new FileVelopackLogger(logFilePath, currentProcessId);
|
var fileLog = new FileVelopackLogger(logFilePath, currentProcessId);
|
||||||
combinedLog.Add(fileLog);
|
combinedLog.Add(fileLog);
|
||||||
} catch (Exception ex2) {
|
} catch (Exception ex) {
|
||||||
initLog.Error("Unable to create temp folder file logger: " + ex2);
|
tempFileLogException = ex;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (tempFileLogException is not null) {
|
||||||
|
//NB: fileLogException is not null here
|
||||||
|
initLog.Error("Unable to create file logger: " + new AggregateException(fileLogException!, tempFileLogException));
|
||||||
|
} else if (fileLogException is not null) {
|
||||||
|
initLog.Info("Unable to create file logger; using temp directory for log instead");
|
||||||
|
initLog.Trace($"File logger exception: {fileLogException}");
|
||||||
|
}
|
||||||
|
|
||||||
if (AppId == null) {
|
if (AppId == null) {
|
||||||
initLog.Warn(
|
initLog.Warn(
|
||||||
$"Failed to initialise {nameof(WindowsVelopackLocator)}. This could be because the program is not installed or packaged properly.");
|
$"Failed to initialize {nameof(WindowsVelopackLocator)}. This could be because the program is not installed or packaged properly.");
|
||||||
} else {
|
} else {
|
||||||
initLog.Info($"Initialised {nameof(WindowsVelopackLocator)} for {AppId} v{CurrentlyInstalledVersion}");
|
initLog.Info($"Initialized {nameof(WindowsVelopackLocator)} for {AppId} v{CurrentlyInstalledVersion}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private string? GetPackagesDir()
|
||||||
|
{
|
||||||
|
const string PackagesDirName = "packages";
|
||||||
|
|
||||||
|
string? writableRootDir = PossibleDirectories()
|
||||||
|
.FirstOrDefault(IsWritable);
|
||||||
|
|
||||||
|
if (writableRootDir == null) {
|
||||||
|
Log.Warn("Unable to find a writable root directory for package.");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.Trace("Using writable root directory: " + writableRootDir);
|
||||||
|
|
||||||
|
return CreateSubDirIfDoesNotExist(writableRootDir, PackagesDirName);
|
||||||
|
|
||||||
|
static bool IsWritable(string? directoryPath)
|
||||||
|
{
|
||||||
|
if (directoryPath == null) return false;
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (!Directory.Exists(directoryPath)) {
|
||||||
|
Directory.CreateDirectory(directoryPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
return PathUtil.IsDirectoryWritable(directoryPath);
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
IEnumerable<string?> PossibleDirectories()
|
||||||
|
{
|
||||||
|
yield return RootAppDir;
|
||||||
|
yield return TempAppRootDirectory;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private string TempAppRootDirectory => Path.Combine(Path.GetTempPath(), "velopack_" + AppId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,11 +1,5 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.ComponentModel;
|
|
||||||
using System.Diagnostics;
|
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Velopack.Locators;
|
using Velopack.Locators;
|
||||||
using Velopack.Logging;
|
using Velopack.Logging;
|
||||||
using Velopack.Util;
|
using Velopack.Util;
|
||||||
@@ -19,40 +13,6 @@ namespace Velopack
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static class UpdateExe
|
public static class UpdateExe
|
||||||
{
|
{
|
||||||
[DllImport("user32", SetLastError = true)]
|
|
||||||
[return: MarshalAs(UnmanagedType.Bool)]
|
|
||||||
private static extern bool AllowSetForegroundWindow(int dwProcessId);
|
|
||||||
|
|
||||||
private static Process StartUpdateExe(IVelopackLogger logger, IVelopackLocator locator, IEnumerable<string> args)
|
|
||||||
{
|
|
||||||
var psi = new ProcessStartInfo() {
|
|
||||||
CreateNoWindow = true,
|
|
||||||
FileName = locator.UpdateExePath!,
|
|
||||||
WorkingDirectory = Path.GetDirectoryName(locator.UpdateExePath)!,
|
|
||||||
};
|
|
||||||
|
|
||||||
psi.AppendArgumentListSafe(args, out var debugArgs);
|
|
||||||
logger.Debug($"Running: {psi.FileName} {debugArgs}");
|
|
||||||
|
|
||||||
var p = Process.Start(psi);
|
|
||||||
if (p == null) {
|
|
||||||
throw new Exception("Failed to launch Update.exe process.");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (VelopackRuntimeInfo.IsWindows) {
|
|
||||||
try {
|
|
||||||
// this is an attempt to work around a bug where the restarted app fails to come to foreground.
|
|
||||||
if (!AllowSetForegroundWindow(p.Id))
|
|
||||||
throw new Win32Exception();
|
|
||||||
} catch (Exception ex) {
|
|
||||||
logger.LogWarning(ex, "Failed to allow Update.exe to set foreground window.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.Info("Update.exe executed successfully.");
|
|
||||||
return p;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Runs Update.exe in the current working directory with the 'start' command which will simply start the application.
|
/// Runs Update.exe in the current working directory with the 'start' command which will simply start the application.
|
||||||
/// Combined with the `waitForExit` parameter, this can be used to gracefully restart the application.
|
/// Combined with the `waitForExit` parameter, this can be used to gracefully restart the application.
|
||||||
@@ -79,10 +39,24 @@ namespace Velopack
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
StartUpdateExe(locator.Log, locator, args);
|
var updatePath = locator.UpdateExePath!;
|
||||||
|
var workingDir = Path.GetDirectoryName(updatePath)!;
|
||||||
|
locator.Process.StartProcess(updatePath, args, workingDir, false);
|
||||||
|
locator.Log.Info("Update.exe [start] executed successfully.");
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Process ApplyImpl(IVelopackLocator? locator, VelopackAsset? toApply, bool silent, uint waitPid, bool restart,
|
/// <summary>
|
||||||
|
/// Runs Update.exe in the current working directory to apply updates, optionally restarting the application.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="silent">If true, no dialogs will be shown during the update process. This could result
|
||||||
|
/// in an update failing to install, such as when we need to ask the user for permission to install
|
||||||
|
/// a new framework dependency.</param>
|
||||||
|
/// <param name="restart">If true, restarts the application after updates are applied (or if they failed)</param>
|
||||||
|
/// <param name="locator">The locator to use to find the path to Update.exe and the packages directory.</param>
|
||||||
|
/// <param name="toApply">The update package you wish to apply, can be left null.</param>
|
||||||
|
/// <param name="waitPid">Optionally wait for the specified process to exit before continuing.</param>
|
||||||
|
/// <param name="restartArgs">The arguments to pass to the application when it is restarted.</param>
|
||||||
|
public static void Apply(IVelopackLocator? locator, VelopackAsset? toApply, bool silent, uint waitPid, bool restart,
|
||||||
string[]? restartArgs = null)
|
string[]? restartArgs = null)
|
||||||
{
|
{
|
||||||
locator ??= VelopackLocator.Current;
|
locator ??= VelopackLocator.Current;
|
||||||
@@ -107,6 +81,9 @@ namespace Velopack
|
|||||||
|
|
||||||
if (!restart) args.Add("--norestart"); // restarting is now the default Update.exe behavior
|
if (!restart) args.Add("--norestart"); // restarting is now the default Update.exe behavior
|
||||||
|
|
||||||
|
args.Add("--root");
|
||||||
|
args.Add(locator.RootAppDir!);
|
||||||
|
|
||||||
if (restart && restartArgs != null && restartArgs.Length > 0) {
|
if (restart && restartArgs != null && restartArgs.Length > 0) {
|
||||||
args.Add("--");
|
args.Add("--");
|
||||||
foreach (var a in restartArgs) {
|
foreach (var a in restartArgs) {
|
||||||
@@ -114,42 +91,10 @@ namespace Velopack
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return StartUpdateExe(locator.Log, locator, args);
|
var updatePath = locator.UpdateExePath!;
|
||||||
}
|
var workingDir = Path.GetDirectoryName(updatePath)!;
|
||||||
|
locator.Process.StartProcess(updatePath, args, workingDir, false);
|
||||||
/// <summary>
|
locator.Log.Info("Update.exe [apply] executed successfully.");
|
||||||
/// Runs Update.exe in the current working directory to apply updates, optionally restarting the application.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="silent">If true, no dialogs will be shown during the update process. This could result
|
|
||||||
/// in an update failing to install, such as when we need to ask the user for permission to install
|
|
||||||
/// a new framework dependency.</param>
|
|
||||||
/// <param name="restart">If true, restarts the application after updates are applied (or if they failed)</param>
|
|
||||||
/// <param name="locator">The locator to use to find the path to Update.exe and the packages directory.</param>
|
|
||||||
/// <param name="toApply">The update package you wish to apply, can be left null.</param>
|
|
||||||
/// <param name="waitPid">Optionally wait for the specified process to exit before continuing.</param>
|
|
||||||
/// <param name="restartArgs">The arguments to pass to the application when it is restarted.</param>
|
|
||||||
/// <exception cref="Exception">Thrown if Update.exe does not initialize properly.</exception>
|
|
||||||
public static void Apply(IVelopackLocator? locator, VelopackAsset? toApply, bool silent, uint waitPid, bool restart,
|
|
||||||
string[]? restartArgs = null)
|
|
||||||
{
|
|
||||||
var process = ApplyImpl(locator, toApply, silent, waitPid, restart, restartArgs);
|
|
||||||
Thread.Sleep(500);
|
|
||||||
|
|
||||||
if (process.HasExited) {
|
|
||||||
throw new Exception($"Update.exe process exited too soon ({process.ExitCode}).");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc cref="Apply"/>
|
|
||||||
public static async Task ApplyAsync(IVelopackLocator? locator, VelopackAsset? toApply, bool silent, uint waitPid, bool restart,
|
|
||||||
string[]? restartArgs = null)
|
|
||||||
{
|
|
||||||
var process = ApplyImpl(locator, toApply, silent, waitPid, restart, restartArgs);
|
|
||||||
await Task.Delay(500).ConfigureAwait(false);
|
|
||||||
|
|
||||||
if (process.HasExited) {
|
|
||||||
throw new Exception($"Update.exe process exited too soon ({process.ExitCode}).");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -44,13 +44,8 @@ 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, Locator.ProcessId, restart, restartArgs);
|
UpdateExe.Apply(Locator, toApply, silent, Locator.Process.GetCurrentProcessId(), restart, restartArgs);
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc cref="WaitExitThenApplyUpdates"/>
|
|
||||||
public async Task WaitExitThenApplyUpdatesAsync(VelopackAsset? toApply, bool silent = false, bool restart = true, string[]? restartArgs = null)
|
|
||||||
{
|
|
||||||
await UpdateExe.ApplyAsync(Locator, toApply, silent, Locator.ProcessId, restart, restartArgs).ConfigureAwait(false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -172,7 +172,7 @@ namespace Velopack
|
|||||||
VelopackLocator.SetCurrentLocator(_customLocator);
|
VelopackLocator.SetCurrentLocator(_customLocator);
|
||||||
}
|
}
|
||||||
|
|
||||||
var locator = VelopackLocator.GetCurrentOrCreateDefault(_customLogger);
|
var locator = VelopackLocator.GetCurrentOrCreateDefault(null, _customLogger);
|
||||||
var log = locator.Log;
|
var log = locator.Log;
|
||||||
|
|
||||||
log.Info($"Starting VelopackApp.Run (library version {VelopackRuntimeInfo.VelopackNugetVersion}).");
|
log.Info($"Starting VelopackApp.Run (library version {VelopackRuntimeInfo.VelopackNugetVersion}).");
|
||||||
@@ -231,7 +231,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, locator.ProcessId, true, args);
|
UpdateExe.Apply(locator, latestLocal, false, locator.Process.GetCurrentProcessId(), true, args);
|
||||||
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 + ")");
|
||||||
|
|||||||
Reference in New Issue
Block a user