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,16 +52,10 @@ namespace Velopack.Locators
|
||||
bool IsPortable { get; }
|
||||
|
||||
/// <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>
|
||||
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>
|
||||
uint ProcessId { get; }
|
||||
IProcessImpl Process { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Finds .nupkg files in the PackagesDir and returns a list of ReleaseEntryName objects.
|
||||
|
||||
@@ -24,6 +24,9 @@ namespace Velopack.Locators
|
||||
/// <inheritdoc />
|
||||
public override string? UpdateExePath { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public override IProcessImpl Process { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
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>
|
||||
public string? AppImagePath => Environment.GetEnvironmentVariable("APPIMAGE");
|
||||
|
||||
/// <inheritdoc />
|
||||
public override uint ProcessId { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string ProcessExePath { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="OsxVelopackLocator"/> and auto-detects the
|
||||
/// app information from metadata embedded in the .app.
|
||||
/// </summary>
|
||||
public LinuxVelopackLocator(string currentProcessPath, uint currentProcessId, IVelopackLogger? customLog)
|
||||
public LinuxVelopackLocator(IProcessImpl? processImpl, IVelopackLogger? customLog)
|
||||
{
|
||||
if (!VelopackRuntimeInfo.IsLinux)
|
||||
throw new NotSupportedException($"Cannot instantiate {nameof(LinuxVelopackLocator)} on a non-linux system.");
|
||||
|
||||
ProcessId = currentProcessId;
|
||||
var ourPath = ProcessExePath = currentProcessPath;
|
||||
|
||||
var combinedLog = new CombinedVelopackLogger();
|
||||
combinedLog.Add(customLog);
|
||||
Log = combinedLog;
|
||||
|
||||
Process = processImpl ??= new DefaultProcessImpl(combinedLog);
|
||||
var ourPath = processImpl.GetCurrentProcessPath();
|
||||
var currentProcessId = processImpl.GetCurrentProcessId();
|
||||
|
||||
using var initLog = new CachedVelopackLogger(combinedLog);
|
||||
initLog.Info($"Initializing {nameof(LinuxVelopackLocator)}");
|
||||
var logFilePath = Path.Combine(Path.GetTempPath(), DefaultLoggingFileName);
|
||||
|
||||
@@ -24,6 +24,9 @@ namespace Velopack.Locators
|
||||
/// <inheritdoc />
|
||||
public override string? UpdateExePath { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public override IProcessImpl Process { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public override SemanticVersion? CurrentlyInstalledVersion { get; }
|
||||
|
||||
@@ -48,28 +51,23 @@ namespace Velopack.Locators
|
||||
/// <inheritdoc />
|
||||
public override string? Channel { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public override uint ProcessId { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string ProcessExePath { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="OsxVelopackLocator"/> and auto-detects the
|
||||
/// app information from metadata embedded in the .app.
|
||||
/// </summary>
|
||||
public OsxVelopackLocator(string currentProcessPath, uint currentProcessId, IVelopackLogger? customLog)
|
||||
public OsxVelopackLocator(IProcessImpl? processImpl, IVelopackLogger? customLog)
|
||||
{
|
||||
if (!VelopackRuntimeInfo.IsOSX)
|
||||
throw new NotSupportedException($"Cannot instantiate {nameof(OsxVelopackLocator)} on a non-osx system.");
|
||||
|
||||
ProcessId = currentProcessId;
|
||||
var ourPath = ProcessExePath = currentProcessPath;
|
||||
|
||||
var combinedLog = new CombinedVelopackLogger();
|
||||
combinedLog.Add(customLog);
|
||||
Log = combinedLog;
|
||||
|
||||
Process = processImpl ??= new DefaultProcessImpl(combinedLog);
|
||||
var ourPath = processImpl.GetCurrentProcessPath();
|
||||
var currentProcessId = processImpl.GetCurrentProcessId();
|
||||
|
||||
using var initLog = new CachedVelopackLogger(combinedLog);
|
||||
initLog.Info($"Initializing {nameof(OsxVelopackLocator)}");
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using NuGet.Versioning;
|
||||
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.
|
||||
/// </summary>
|
||||
[ExcludeFromCodeCoverage]
|
||||
public class TestVelopackLocator : VelopackLocator
|
||||
public class TestVelopackLocator : VelopackLocator, IProcessImpl
|
||||
{
|
||||
public override string? AppId {
|
||||
get {
|
||||
@@ -83,6 +84,8 @@ namespace Velopack.Locators
|
||||
|
||||
public override IVelopackLogger Log { get; }
|
||||
|
||||
public override IProcessImpl Process => this;
|
||||
|
||||
public override VelopackAsset? GetLatestLocalFullPackage()
|
||||
{
|
||||
if (_asset != null) {
|
||||
@@ -92,10 +95,6 @@ namespace Velopack.Locators
|
||||
return base.GetLatestLocalFullPackage();
|
||||
}
|
||||
|
||||
public override uint ProcessId => 0;
|
||||
|
||||
public override string ProcessExePath { get; }
|
||||
|
||||
private readonly string? _updatePath;
|
||||
private readonly SemanticVersion? _version;
|
||||
private readonly string? _packages;
|
||||
@@ -104,6 +103,7 @@ namespace Velopack.Locators
|
||||
private readonly string? _appContent;
|
||||
private readonly string? _channel;
|
||||
private readonly VelopackAsset? _asset;
|
||||
private readonly string? _processPath;
|
||||
|
||||
/// <inheritdoc cref="TestVelopackLocator" />
|
||||
public TestVelopackLocator(string appId, string version, string packagesDir, IVelopackLogger? logger = null)
|
||||
@@ -113,7 +113,8 @@ namespace Velopack.Locators
|
||||
|
||||
/// <inheritdoc cref="TestVelopackLocator" />
|
||||
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;
|
||||
_packages = packagesDir;
|
||||
@@ -123,8 +124,23 @@ namespace Velopack.Locators
|
||||
_appContent = appDir;
|
||||
_channel = channel;
|
||||
_asset = localPackage;
|
||||
ProcessExePath = processPath;
|
||||
_processPath = processPath;
|
||||
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>
|
||||
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)
|
||||
return _current = new WindowsVelopackLocator(processExePath, processId, logger);
|
||||
return _current = new WindowsVelopackLocator(processImpl, logger);
|
||||
|
||||
if (VelopackRuntimeInfo.IsOSX)
|
||||
return _current = new OsxVelopackLocator(processExePath, processId, logger);
|
||||
return _current = new OsxVelopackLocator(processImpl, logger);
|
||||
|
||||
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.");
|
||||
}
|
||||
@@ -64,9 +59,9 @@ namespace Velopack.Locators
|
||||
_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;
|
||||
}
|
||||
|
||||
@@ -94,20 +89,17 @@ namespace Velopack.Locators
|
||||
/// <inheritdoc/>
|
||||
public abstract IVelopackLogger Log { get; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public abstract uint ProcessId { get; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public abstract string ProcessExePath { get; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual bool IsPortable => false;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public abstract IProcessImpl Process { get; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual string? ThisExeRelativePath {
|
||||
get {
|
||||
if (AppContentDir == null) return null;
|
||||
var path = ProcessExePath;
|
||||
var path = Process.GetCurrentProcessPath();
|
||||
if (path.StartsWith(AppContentDir, StringComparison.OrdinalIgnoreCase)) {
|
||||
return path.Substring(AppContentDir.Length + 1);
|
||||
} else {
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.Versioning;
|
||||
using NuGet.Versioning;
|
||||
using Velopack.Logging;
|
||||
@@ -30,8 +32,10 @@ namespace Velopack.Locators
|
||||
/// <inheritdoc />
|
||||
public override SemanticVersion? CurrentlyInstalledVersion { get; }
|
||||
|
||||
private readonly Lazy<string?> _packagesDir;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string? PackagesDir => CreateSubDirIfDoesNotExist(RootAppDir, "packages");
|
||||
public override string? PackagesDir => _packagesDir.Value;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override IVelopackLogger Log { get; }
|
||||
@@ -39,30 +43,30 @@ namespace Velopack.Locators
|
||||
/// <inheritdoc />
|
||||
public override bool IsPortable => RootAppDir != null && File.Exists(Path.Combine(RootAppDir, ".portable"));
|
||||
|
||||
/// <inheritdoc />
|
||||
public override IProcessImpl Process { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string? Channel { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public override uint ProcessId { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string ProcessExePath { get; }
|
||||
|
||||
/// <inheritdoc cref="WindowsVelopackLocator" />
|
||||
public WindowsVelopackLocator(string currentProcessPath, uint currentProcessId, IVelopackLogger? customLog)
|
||||
public WindowsVelopackLocator(IProcessImpl? processImpl, IVelopackLogger? customLog)
|
||||
{
|
||||
if (!VelopackRuntimeInfo.IsWindows)
|
||||
throw new NotSupportedException($"Cannot instantiate {nameof(WindowsVelopackLocator)} on a non-Windows system.");
|
||||
|
||||
ProcessId = currentProcessId;
|
||||
var ourPath = ProcessExePath = currentProcessPath;
|
||||
_packagesDir = new(GetPackagesDir);
|
||||
|
||||
var combinedLog = new CombinedVelopackLogger();
|
||||
combinedLog.Add(customLog);
|
||||
Log = combinedLog;
|
||||
|
||||
Process = processImpl ??= new DefaultProcessImpl(combinedLog);
|
||||
var ourPath = processImpl.GetCurrentProcessPath();
|
||||
var currentProcessId = processImpl.GetCurrentProcessId();
|
||||
|
||||
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 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}"
|
||||
// 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)!;
|
||||
var myDirName = Path.GetFileName(myDirPath);
|
||||
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)) {
|
||||
try {
|
||||
var logFilePath = Path.Combine(RootAppDir, DefaultLoggingFileName);
|
||||
var fileLog = new FileVelopackLogger(logFilePath, currentProcessId);
|
||||
combinedLog.Add(fileLog);
|
||||
fileLogCreated = true;
|
||||
} catch (Exception ex2) {
|
||||
initLog.Error("Unable to create default file logger: " + ex2);
|
||||
//fileLogCreated = true;
|
||||
} catch (Exception ex) {
|
||||
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 (!fileLogCreated) {
|
||||
Exception? tempFileLogException = null;
|
||||
if (fileLogException is not null) {
|
||||
try {
|
||||
var logFileName = String.IsNullOrEmpty(AppId) ? DefaultLoggingFileName : $"velopack_{AppId}.log";
|
||||
var logFilePath = Path.Combine(Path.GetTempPath(), logFileName);
|
||||
var fileLog = new FileVelopackLogger(logFilePath, currentProcessId);
|
||||
combinedLog.Add(fileLog);
|
||||
} catch (Exception ex2) {
|
||||
initLog.Error("Unable to create temp folder file logger: " + ex2);
|
||||
} catch (Exception ex) {
|
||||
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) {
|
||||
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 {
|
||||
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.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Velopack.Locators;
|
||||
using Velopack.Logging;
|
||||
using Velopack.Util;
|
||||
@@ -19,40 +13,6 @@ namespace Velopack
|
||||
/// </summary>
|
||||
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>
|
||||
/// 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.
|
||||
@@ -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)
|
||||
{
|
||||
locator ??= VelopackLocator.Current;
|
||||
@@ -107,6 +81,9 @@ namespace Velopack
|
||||
|
||||
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) {
|
||||
args.Add("--");
|
||||
foreach (var a in restartArgs) {
|
||||
@@ -114,42 +91,10 @@ namespace Velopack
|
||||
}
|
||||
}
|
||||
|
||||
return StartUpdateExe(locator.Log, locator, args);
|
||||
}
|
||||
|
||||
/// <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>
|
||||
/// <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}).");
|
||||
}
|
||||
var updatePath = locator.UpdateExePath!;
|
||||
var workingDir = Path.GetDirectoryName(updatePath)!;
|
||||
locator.Process.StartProcess(updatePath, args, workingDir, false);
|
||||
locator.Log.Info("Update.exe [apply] executed successfully.");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -44,13 +44,8 @@ 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, 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);
|
||||
}
|
||||
|
||||
var locator = VelopackLocator.GetCurrentOrCreateDefault(_customLogger);
|
||||
var locator = VelopackLocator.GetCurrentOrCreateDefault(null, _customLogger);
|
||||
var log = locator.Log;
|
||||
|
||||
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}");
|
||||
if (!restarted && _autoApply) {
|
||||
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);
|
||||
} else {
|
||||
log.Info("Pre-condition failed, we will not restart to apply updates. (restarted: " + restarted + ", autoApply: " + _autoApply + ")");
|
||||
|
||||
Reference in New Issue
Block a user