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; } |         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. | ||||||
|   | |||||||
| @@ -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,28 +51,23 @@ 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