diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 06128d03..4bedd576 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -53,16 +53,9 @@ jobs:
with:
dotnet-version: 6.0.*
- name: Build SquirrelMac
- run: dotnet publish -v minimal -c Release -r osx.10.12-x64 --self-contained ./src/Squirrel.CommandLine.OSX/Squirrel.CommandLine.OSX.csproj -o ./publish
-# - name: Build SquirrelMac
-# run: |
-# dotnet publish -v minimal -c Release -r osx.10.12-x64 --self-contained ./src/Squirrel.CommandLine.OSX/Squirrel.CommandLine.OSX.csproj -o ./publish
-# mv ./publish/SquirrelMac ./publish/SquirrelMac-x64
-# dotnet publish -v minimal -c Release -r osx.11.0-arm64 --self-contained=true ./src/Squirrel.CommandLine.OSX/Squirrel.CommandLine.OSX.csproj -o ./publish
-# mkdir ./bundle
-# lipo -create -output ./bundle/SquirrelMac ./publish/SquirrelMac ./publish/SquirrelMac-x64
+ run: dotnet publish -v minimal --self-contained -c Release -r osx.10.12-x64 ./src/Squirrel.CommandLine.OSX/Squirrel.CommandLine.OSX.csproj -o ./publish
- name: Build UpdateMac
- run: dotnet publish -v minimal -c Release -r osx.10.12-x64 --self-contained ./src/Update.OSX/Update.OSX.csproj -o ./publish
+ run: dotnet publish -v minimal --self-contained -c Release -r osx.10.12-x64 ./src/Update.OSX/Update.OSX.csproj -o ./publish
- name: Upload MacOS Artifacts
uses: actions/upload-artifact@v3
with:
diff --git a/src/Squirrel/AppDesc.cs b/src/Squirrel/AppDesc.cs
new file mode 100644
index 00000000..7722722b
--- /dev/null
+++ b/src/Squirrel/AppDesc.cs
@@ -0,0 +1,404 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.IO;
+using System.Linq;
+using NuGet.Versioning;
+using Squirrel.NuGet;
+using Squirrel.SimpleSplat;
+
+namespace Squirrel
+{
+ ///
+ /// A base class describing where Squirrel can find key folders and files.
+ ///
+ public abstract class AppDesc : IEnableLogger
+ {
+ ///
+ /// Auto-detect the platform from the current operating system.
+ ///
+ public static AppDesc GetCurrentPlatform()
+ {
+ if (SquirrelRuntimeInfo.IsWindows)
+ return new AppDescWindows();
+
+ if (SquirrelRuntimeInfo.IsOSX)
+ return new AppDescOsx();
+
+ throw new NotSupportedException($"OS platform '{SquirrelRuntimeInfo.SystemOsName}' is not supported.");
+ }
+
+ ///
+ /// Instantiate base class .
+ ///
+ protected AppDesc()
+ {
+ }
+
+ /// The unique application Id. This is used in various app paths.
+ public abstract string AppId { get; }
+
+ ///
+ /// The root directory of the application. On Windows, this folder contains all
+ /// the application files, but that may not be the case on other operating systems.
+ ///
+ public abstract string RootAppDir { get; }
+
+ /// The directory in which nupkg files are stored for this application.
+ public abstract string PackagesDir { get; }
+
+ /// The temporary directory for this application.
+ public abstract string AppTempDir { get; }
+
+ /// True if the current binary is Update.exe within the specified application.
+ public abstract bool IsUpdateExe { get; }
+
+ /// The directory where new versions are stored, before they are applied.
+ public abstract string VersionStagingDir { get; }
+
+ ///
+ /// The directory where the current version of the application is stored.
+ /// This directory will be swapped out for a new version in .
+ ///
+ public abstract string CurrentVersionDir { get; }
+
+ /// The path to the current Update.exe or similar on other operating systems.
+ public abstract string UpdateExePath { get; }
+
+ /// The path to the RELEASES index detailing the local packages.
+ public virtual string ReleasesFilePath => Path.Combine(PackagesDir, "RELEASES");
+
+ /// The path to the .betaId file which contains a unique GUID for this user.
+ public virtual string BetaIdFilePath => Path.Combine(PackagesDir, ".betaId");
+
+ /// The currently installed version of the application.
+ public abstract SemanticVersion CurrentlyInstalledVersion { get; }
+
+ ///
+ /// Gets a
+ ///
+ /// The application version
+ /// The full path to the version staging directory
+ public virtual string GetVersionStagingPath(SemanticVersion version)
+ {
+ return Path.Combine(VersionStagingDir, "app-" + version);
+ }
+
+ internal List<(string PackagePath, SemanticVersion PackageVersion, bool IsDelta)> GetLocalPackages()
+ {
+ var query = from x in Directory.EnumerateFiles(PackagesDir, "*.nupkg")
+ let re = ReleaseEntry.ParseEntryFileName(x)
+ where re.Version != null
+ select (x, re.Version, re.IsDelta);
+ return query.ToList();
+ }
+
+ internal string UpdateAndRetrieveCurrentFolder(bool force)
+ {
+ try {
+ var releases = GetVersions();
+ var latestVer = releases.OrderByDescending(m => m.Version).First();
+ var currentVer = releases.FirstOrDefault(f => f.IsCurrent);
+
+ // if the latest ver is already current, or it does not support
+ // being in a current directory.
+ if (latestVer.IsCurrent) {
+ this.Log().Info($"Current directory already pointing to latest version.");
+ return latestVer.DirectoryPath;
+ }
+
+ if (force) {
+ this.Log().Info($"Killing running processes in '{RootAppDir}'.");
+ Utility.KillProcessesInDirectory(RootAppDir);
+ }
+
+ // 'current' does exist, and it's wrong, so lets get rid of it
+ if (currentVer != default) {
+ string legacyVersionDir = GetVersionStagingPath(currentVer.Version);
+ this.Log().Info($"Moving '{currentVer.DirectoryPath}' to '{legacyVersionDir}'.");
+ Utility.Retry(() => Directory.Move(currentVer.DirectoryPath, legacyVersionDir));
+ }
+
+ // this directory does not support being named 'current'
+ if (latestVer.Manifest == null) {
+ this.Log().Info($"Cannot promote {latestVer.Version} as current as it has no manifest");
+ return latestVer.DirectoryPath;
+ }
+
+ // 'current' doesn't exist right now, lets move the latest version
+ var latestDir = CurrentVersionDir;
+ this.Log().Info($"Moving '{latestVer.DirectoryPath}' to '{latestDir}'.");
+ Utility.Retry(() => Directory.Move(latestVer.DirectoryPath, latestDir));
+
+ this.Log().Info("Running app in: " + latestDir);
+ return latestDir;
+ } catch (Exception e) {
+ var releases = GetVersions();
+ string fallback = releases.OrderByDescending(m => m.Version).First().DirectoryPath;
+ var currentVer = releases.FirstOrDefault(f => f.IsCurrent);
+ if (currentVer != default && Directory.Exists(currentVer.DirectoryPath)) {
+ fallback = currentVer.DirectoryPath;
+ }
+
+ this.Log().WarnException("Unable to update 'current' directory", e);
+ this.Log().Info("Running app in: " + fallback);
+ return fallback;
+ }
+ }
+
+ ///
+ /// Given a base dir and a directory name, will create a new sub directory of that name.
+ /// Will return null if baseDir is null, or if baseDir does not exist.
+ ///
+ protected static string CreateSubDirIfDoesNotExist(string baseDir, string newDir)
+ {
+ if (String.IsNullOrEmpty(baseDir) || string.IsNullOrEmpty(newDir)) return null;
+ var infoBase = new DirectoryInfo(baseDir);
+ if (!infoBase.Exists) return null;
+ var info = new DirectoryInfo(Path.Combine(baseDir, newDir));
+ if (!info.Exists) info.Create();
+ return info.FullName;
+ }
+
+ ///
+ /// Starts Update.exe with the correct arguments to restart this process.
+ /// Update.exe will wait for this process to exit, and apply any pending version updates
+ /// before re-launching the latest version.
+ ///
+ public virtual Process StartRestartingProcess(string exeToStart = null, string arguments = null)
+ {
+ // NB: Here's how this method works:
+ //
+ // 1. We're going to pass the *name* of our EXE and the params to
+ // Update.exe
+ // 2. Update.exe is going to grab our PID (via getting its parent),
+ // then wait for us to exit.
+ // 3. Return control and new Process back to caller and allow them to Exit as desired.
+ // 4. After our process exits, Update.exe unblocks, then we launch the app again, possibly
+ // launching a different version than we started with (this is why
+ // we take the app's *name* rather than a full path)
+
+ exeToStart = exeToStart ?? Path.GetFileName(SquirrelRuntimeInfo.EntryExePath);
+
+ List args = new() {
+ "--forceLatest",
+ "--processStartAndWait",
+ exeToStart,
+ };
+
+ if (arguments != null) {
+ args.Add("-a");
+ args.Add(arguments);
+ }
+
+ return ProcessUtil.StartNonBlocking(UpdateExePath, args, Path.GetDirectoryName(UpdateExePath));
+ }
+
+ internal VersionDirInfo GetLatestVersion()
+ {
+ return GetLatestVersion(GetVersions());
+ }
+
+ internal VersionDirInfo GetLatestVersion(IEnumerable versions)
+ {
+ return versions.OrderByDescending(r => r.Version).FirstOrDefault();
+ }
+
+ internal VersionDirInfo GetVersionInfoFromDirectory(string d)
+ {
+ bool isCurrent = Utility.FullPathEquals(d, CurrentVersionDir);
+ var directoryName = Path.GetFileName(d);
+ bool isExecuting = Utility.IsFileInDirectory(SquirrelRuntimeInfo.EntryExePath, d);
+ var manifest = Utility.ReadManifestFromVersionDir(d);
+
+ if (manifest != null) {
+ return new(manifest, manifest.Version, d, isCurrent, isExecuting);
+ }
+
+ if (Utility.PathPartStartsWith(directoryName, "app-") && NuGetVersion.TryParse(directoryName.Substring(4), out var ver)) {
+ return new(null, ver, d, isCurrent, isExecuting);
+ }
+
+ return null;
+ }
+
+ internal record VersionDirInfo(IPackage Manifest, SemanticVersion Version, string DirectoryPath, bool IsCurrent, bool IsExecuting);
+
+ internal VersionDirInfo[] GetVersions()
+ {
+ return Directory.EnumerateDirectories(RootAppDir, "app-*", SearchOption.TopDirectoryOnly)
+ .Concat(Directory.EnumerateDirectories(VersionStagingDir, "app-*", SearchOption.TopDirectoryOnly))
+ .Concat(new[] { CurrentVersionDir })
+ .Select(Utility.NormalizePath)
+ .Distinct(SquirrelRuntimeInfo.PathStringComparer)
+ .Select(GetVersionInfoFromDirectory)
+ .Where(d => d != null)
+ .ToArray();
+ }
+ }
+
+ ///
+ /// An implementation for Windows which uses the Squirrel defaults and installs to
+ /// local app data.
+ ///
+ public class AppDescWindows : AppDesc
+ {
+ ///
+ public override string AppId { get; }
+
+ ///
+ public override string RootAppDir { get; }
+
+ ///
+ public override string UpdateExePath { get; }
+
+ ///
+ public override bool IsUpdateExe { get; }
+
+ ///
+ public override SemanticVersion CurrentlyInstalledVersion { get; }
+
+ ///
+ public override string PackagesDir => CreateSubDirIfDoesNotExist(RootAppDir, "packages");
+
+ ///
+ public override string AppTempDir => CreateSubDirIfDoesNotExist(PackagesDir, "SquirrelClowdTemp");
+
+
+ ///
+ public override string VersionStagingDir => CreateSubDirIfDoesNotExist(RootAppDir, "staging");
+
+ ///
+ public override string CurrentVersionDir => CreateSubDirIfDoesNotExist(RootAppDir, "current");
+
+ ///
+ /// Creates a new Platform and tries to auto-detect the application details from
+ /// the current context.
+ ///
+ public AppDescWindows()
+ {
+ if (!SquirrelRuntimeInfo.IsWindows)
+ throw new NotSupportedException("Cannot instantiate AppDescWindows on a non-Windows system.");
+
+ var ourPath = SquirrelRuntimeInfo.EntryExePath;
+ var myDir = Path.GetDirectoryName(ourPath);
+
+ // Am I update.exe at the application root?
+ if (ourPath != null &&
+ Path.GetFileName(ourPath).Equals("update.exe", StringComparison.InvariantCultureIgnoreCase) &&
+ ourPath.IndexOf("app-", StringComparison.InvariantCultureIgnoreCase) == -1 &&
+ ourPath.IndexOf("SquirrelClowdTemp", StringComparison.InvariantCultureIgnoreCase) == -1) {
+ UpdateExePath = ourPath;
+ RootAppDir = myDir;
+ var ver = GetLatestVersion();
+ if (ver != null) {
+ AppId = ver.Manifest?.Id ?? Path.GetFileName(myDir);
+ CurrentlyInstalledVersion = ver.Version;
+ IsUpdateExe = true;
+ }
+ }
+
+ // Am I running from within an app-* or current dir?
+ var info = GetVersionInfoFromDirectory(myDir);
+ if (info != null) {
+ var updateExe = Path.Combine(myDir, "..\\Update.exe");
+ var updateExe2 = Path.Combine(myDir, "..\\..\\Update.exe");
+ string updateLocation = null;
+
+ if (File.Exists(updateExe)) {
+ updateLocation = Path.GetFullPath(updateExe);
+ } else if (File.Exists(updateExe2)) {
+ updateLocation = Path.GetFullPath(updateExe2);
+ }
+
+ if (updateLocation != null) {
+ UpdateExePath = updateLocation;
+ RootAppDir = Path.GetDirectoryName(updateLocation);
+ AppId = info.Manifest?.Id ?? Path.GetFileName(Path.GetDirectoryName(updateLocation));
+ CurrentlyInstalledVersion = info.Version;
+ IsUpdateExe = false;
+ }
+ }
+ }
+
+ ///
+ /// Creates a new windows application platform at the specified app directory.
+ ///
+ /// The location of the application.
+ /// The unique ID of the application.
+ public AppDescWindows(string appDir, string appId)
+ {
+ AppId = appId;
+ RootAppDir = appDir;
+ var updateExe = Path.Combine(appDir, "Update.exe");
+ var ver = GetLatestVersion();
+
+ if (File.Exists(updateExe) && ver != null) {
+ UpdateExePath = updateExe;
+ CurrentlyInstalledVersion = ver.Version;
+ }
+ }
+ }
+
+ ///
+ /// The default for OSX. All application files will remain in the '.app'.
+ /// All additional files (log, etc) will be placed in a temporary directory.
+ ///
+ public class AppDescOsx : AppDesc
+ {
+ ///
+ public override string AppId { get; }
+
+ ///
+ public override string RootAppDir { get; }
+
+ ///
+ public override string UpdateExePath { get; }
+
+ ///
+ public override bool IsUpdateExe { get; }
+
+ ///
+ public override string CurrentVersionDir => RootAppDir;
+
+ ///
+ public override SemanticVersion CurrentlyInstalledVersion { get; }
+
+ ///
+ public override string AppTempDir => CreateSubDirIfDoesNotExist(Utility.GetDefaultTempBaseDirectory(), AppId);
+
+ ///
+ public override string PackagesDir => CreateSubDirIfDoesNotExist(AppTempDir, "packages");
+
+ ///
+ public override string VersionStagingDir => CreateSubDirIfDoesNotExist(AppTempDir, "staging");
+
+ ///
+ /// Creates a new and auto-detects the
+ /// app information from metadata embedded in the .app.
+ ///
+ public AppDescOsx()
+ {
+ if (!SquirrelRuntimeInfo.IsOSX)
+ throw new NotSupportedException("Cannot instantiate AppDescOsx on a non-osx system.");
+
+ // are we inside a .app?
+ var ourPath = SquirrelRuntimeInfo.EntryExePath;
+ var ix = ourPath.IndexOf(".app/", StringComparison.InvariantCultureIgnoreCase);
+ if (ix < 0) return;
+
+ var appPath = ourPath.Substring(0, ix + 5);
+ var contentsDir = Path.Combine(appPath, "Contents");
+ var updateExe = Path.Combine(contentsDir, "UpdateMac");
+ var info = GetVersionInfoFromDirectory(contentsDir);
+
+ if (File.Exists(updateExe) && info?.Manifest != null) {
+ AppId = info.Manifest.Id;
+ RootAppDir = appPath;
+ UpdateExePath = updateExe;
+ CurrentlyInstalledVersion = info.Version;
+ IsUpdateExe = Utility.FullPathEquals(updateExe, ourPath);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Squirrel/GithubUpdateManager.cs b/src/Squirrel/GithubUpdateManager.cs
deleted file mode 100644
index 627f7419..00000000
--- a/src/Squirrel/GithubUpdateManager.cs
+++ /dev/null
@@ -1,77 +0,0 @@
-using System;
-using System.ComponentModel;
-using System.Threading.Tasks;
-using Squirrel.Sources;
-
-namespace Squirrel
-{
- ///
- /// An implementation of UpdateManager which supports checking updates and
- /// downloading releases directly from GitHub releases. This class is just a shorthand
- /// for initialising with a
- /// as the first argument.
- ///
- [EditorBrowsable(EditorBrowsableState.Never)]
- [Obsolete("Use 'new UpdateManager(new GithubSource(...))' instead")]
- public class GithubUpdateManager : UpdateManager
- {
- ///
- ///
- /// The URL of the GitHub repository to download releases from
- /// (e.g. https://github.com/myuser/myrepo)
- ///
- ///
- /// The Id of your application should correspond with the
- /// appdata directory name, and the Id used with Squirrel releasify/pack.
- /// If left null/empty, will attempt to determine the current application Id
- /// from the installed app location.
- ///
- ///
- /// A custom file downloader, for using non-standard package sources or adding
- /// proxy configurations.
- ///
- ///
- /// Provide a custom location for the system LocalAppData, it will be used
- /// instead of .
- ///
- ///
- /// If true, the latest pre-release will be downloaded. If false, the latest
- /// stable release will be downloaded.
- ///
- ///
- /// The GitHub access token to use with the request to download releases.
- /// If left empty, the GitHub rate limit for unauthenticated requests allows
- /// for up to 60 requests per hour, limited by IP address.
- ///
- public GithubUpdateManager(
- string repoUrl,
- bool prerelease = false,
- string accessToken = null,
- string applicationIdOverride = null,
- string localAppDataDirectoryOverride = null,
- IFileDownloader urlDownloader = null)
- : base(new GithubSource(repoUrl, accessToken, prerelease, urlDownloader), applicationIdOverride, localAppDataDirectoryOverride)
- {
- }
- }
-
- public partial class UpdateManager
- {
- ///
- /// This function is obsolete and will be removed in a future version,
- /// see the class for a replacement.
- ///
- [EditorBrowsable(EditorBrowsableState.Never)]
- [Obsolete("Use 'new UpdateManager(new GithubSource(...))' instead")]
- public static Task GitHubUpdateManager(
- string repoUrl,
- string applicationName = null,
- string rootDirectory = null,
- IFileDownloader urlDownloader = null,
- bool prerelease = false,
- string accessToken = null)
- {
- return Task.FromResult(new UpdateManager(new GithubSource(repoUrl, accessToken, prerelease, urlDownloader), applicationName, rootDirectory));
- }
- }
-}
diff --git a/src/Squirrel/NuGet/ZipPackage.cs b/src/Squirrel/NuGet/ZipPackage.cs
index c3b14317..89a93335 100644
--- a/src/Squirrel/NuGet/ZipPackage.cs
+++ b/src/Squirrel/NuGet/ZipPackage.cs
@@ -27,7 +27,8 @@ namespace Squirrel.NuGet
public byte[] AppIconBytes { get; private set; }
public ZipPackage(string filePath) : this(File.OpenRead(filePath))
- { }
+ {
+ }
public ZipPackage(Stream zipStream, bool leaveOpen = false)
{
@@ -62,7 +63,7 @@ namespace Squirrel.NuGet
private ZipArchiveEntry GetManifestEntry(ZipArchive zip)
{
var manifest = zip.Entries
- .FirstOrDefault(f => f.Key.EndsWith(NugetUtil.ManifestExtension, StringComparison.OrdinalIgnoreCase));
+ .FirstOrDefault(f => f.Key.EndsWith(NugetUtil.ManifestExtension, StringComparison.OrdinalIgnoreCase));
if (manifest == null)
throw new InvalidDataException("Invalid nupkg. Does not contain required '.nuspec' manifest.");
@@ -73,11 +74,11 @@ namespace Squirrel.NuGet
private IEnumerable GetPackageFiles(ZipArchive zip)
{
return from entry in zip.Entries
- where !entry.IsDirectory
- let uri = new Uri(entry.Key, UriKind.Relative)
- let path = NugetUtil.GetPath(uri)
- where IsPackageFile(path)
- select new ZipPackageFile(uri);
+ where !entry.IsDirectory
+ let uri = new Uri(entry.Key, UriKind.Relative)
+ let path = NugetUtil.GetPath(uri)
+ where IsPackageFile(path)
+ select new ZipPackageFile(uri);
}
private string[] GetFrameworks(IEnumerable files)
@@ -90,8 +91,60 @@ namespace Squirrel.NuGet
.ToArray();
}
- [SupportedOSPlatform("windows")]
public static Task ExtractZipReleaseForInstall(string zipFilePath, string outFolder, string rootPackageFolder, Action progress)
+ {
+ if (SquirrelRuntimeInfo.IsWindows)
+ return ExtractZipReleaseForInstallWindows(zipFilePath, outFolder, rootPackageFolder, progress);
+
+ if (SquirrelRuntimeInfo.IsOSX)
+ return ExtractZipReleaseForInstallOSX(zipFilePath, outFolder, rootPackageFolder, progress);
+
+ throw new NotSupportedException("Platform not supported.");
+ }
+
+ private static readonly Regex libFolderPattern = new Regex(@"lib[\\\/][^\\\/]*[\\\/]", RegexOptions.CultureInvariant | RegexOptions.IgnoreCase | RegexOptions.Compiled);
+
+ [SupportedOSPlatform("macos")]
+ public static Task ExtractZipReleaseForInstallOSX(string zipFilePath, string outFolder, string rootPackageFolder, Action progress)
+ {
+ return Task.Run(() => {
+ using (var za = ZipArchive.Open(zipFilePath))
+ using (var reader = za.ExtractAllEntries()) {
+ var totalItems = za.Entries.Count;
+ var currentItem = 0;
+
+ while (reader.MoveToNextEntry()) {
+ // Report progress early since we might be need to continue for non-matches
+ currentItem++;
+ var percentage = (currentItem * 100d) / totalItems;
+ progress((int) percentage);
+
+ var parts = reader.Entry.Key.Split('\\', '/').Select(x => Uri.UnescapeDataString(x));
+ var decoded = String.Join(Path.DirectorySeparatorChar.ToString(), parts);
+
+ if (!libFolderPattern.IsMatch(decoded)) continue;
+ decoded = libFolderPattern.Replace(decoded, "", 1);
+
+ var fullTargetFile = Path.Combine(outFolder, decoded);
+ var fullTargetDir = Path.GetDirectoryName(fullTargetFile);
+ Directory.CreateDirectory(fullTargetDir);
+
+ Utility.Retry(() => {
+ if (reader.Entry.IsDirectory) {
+ Directory.CreateDirectory(fullTargetFile);
+ } else {
+ reader.WriteEntryToFile(fullTargetFile);
+ }
+ }, 5);
+ }
+ }
+
+ progress(100);
+ });
+ }
+
+ [SupportedOSPlatform("windows")]
+ public static Task ExtractZipReleaseForInstallWindows(string zipFilePath, string outFolder, string rootPackageFolder, Action progress)
{
var re = new Regex(@"lib[\\\/][^\\\/]*[\\\/]", RegexOptions.CultureInvariant | RegexOptions.IgnoreCase);
@@ -116,8 +169,8 @@ namespace Squirrel.NuGet
var parts = reader.Entry.Key.Split('\\', '/').Select(x => Uri.UnescapeDataString(x));
var decoded = String.Join(Path.DirectorySeparatorChar.ToString(), parts);
- if (!re.IsMatch(decoded)) continue;
- decoded = re.Replace(decoded, "", 1);
+ if (!libFolderPattern.IsMatch(decoded)) continue;
+ decoded = libFolderPattern.Replace(decoded, "", 1);
var fullTargetFile = Path.Combine(outFolder, decoded);
var fullTargetDir = Path.GetDirectoryName(fullTargetFile);
@@ -159,4 +212,4 @@ namespace Squirrel.NuGet
});
}
}
-}
+}
\ No newline at end of file
diff --git a/src/Squirrel/UpdateConfig.cs b/src/Squirrel/UpdateConfig.cs
deleted file mode 100644
index f6b17d1c..00000000
--- a/src/Squirrel/UpdateConfig.cs
+++ /dev/null
@@ -1,248 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.IO;
-using System.Linq;
-using NuGet.Versioning;
-using Squirrel.NuGet;
-using Squirrel.SimpleSplat;
-
-namespace Squirrel
-{
- /// Describes how the application will be installed / updated on the given system.
- public class UpdateConfig
- {
- /// The unique application Id. This is used in various app paths.
- public virtual string AppId { get; }
-
- ///
- /// The root directory of the application. On Windows, this folder contains all
- /// the application files, but that may not be the case on other operating systems.
- ///
- public virtual string RootAppDir { get; }
-
- /// The directory in which nupkg files are stored for this application.
- public virtual string PackagesDir { get; }
-
- /// The temporary directory for this application.
- public virtual string TempDir { get; }
-
- /// The directory where new versions are stored, before they are applied.
- public virtual string VersionStagingDir { get; }
-
- ///
- /// The directory where the current version of the application is stored.
- /// This directory will be swapped out for a new version in .
- ///
- public virtual string CurrentVersionDir { get; }
-
- /// The path to the current Update.exe or similar on other operating systems.
- public virtual string UpdateExePath { get; }
-
- /// The path to the RELEASES index detailing the local packages.
- public virtual string ReleasesFilePath => Path.Combine(PackagesDir, "RELEASES");
-
- /// The path to the .betaId file which contains a unique GUID for this user.
- public virtual string BetaIdFilePath => Path.Combine(PackagesDir, ".betaId");
-
- /// The currently installed version of the application.
- public virtual SemanticVersion CurrentlyInstalledVersion => GetCurrentlyInstalledVersion();
-
- private static IFullLogger Log() => SquirrelLocator.Current.GetService().GetLogger(typeof(UpdateConfig));
-
- /// Creates a new instance of UpdateConfig.
- public UpdateConfig(string applicationIdOverride, string localAppDataDirOverride)
- {
- UpdateExePath = GetUpdateExe();
- AppId = applicationIdOverride ?? GetInstalledApplicationName(UpdateExePath);
- if (AppId != null) {
- RootAppDir = Path.Combine(localAppDataDirOverride ?? GetLocalAppDataDirectory(), AppId);
- CurrentVersionDir = Path.Combine(RootAppDir, "current");
- PackagesDir = Path.Combine(RootAppDir, "packages");
- VersionStagingDir = Path.Combine(RootAppDir, "packages");
- TempDir = Path.Combine(PackagesDir, "Temp");
- }
- }
-
- internal List<(string PackagePath, SemanticVersion PackageVersion, bool IsDelta)> GetLocalPackages()
- {
- var query = from x in Directory.EnumerateFiles(PackagesDir, "*.nupkg")
- let re = ReleaseEntry.ParseEntryFileName(x)
- where re.Version != null
- select (x, re.Version, re.IsDelta);
- return query.ToList();
- }
-
- internal string GetVersionStagingPath(SemanticVersion version)
- {
- return Path.Combine(VersionStagingDir, "app-" + version);
- }
-
- internal string UpdateAndRetrieveCurrentFolder(bool force)
- {
- try {
- var releases = GetVersions();
- var latestVer = releases.OrderByDescending(m => m.Version).First();
- var currentVer = releases.FirstOrDefault(f => f.IsCurrent);
-
- // if the latest ver is already current, or it does not support
- // being in a current directory.
- if (latestVer.IsCurrent) {
- Log().Info($"Current directory already pointing to latest version.");
- return latestVer.DirectoryPath;
- }
-
- if (force) {
- Log().Info($"Killing running processes in '{RootAppDir}'.");
- Utility.KillProcessesInDirectory(RootAppDir);
- }
-
- // 'current' does exist, and it's wrong, so lets get rid of it
- if (currentVer != default) {
- string legacyVersionDir = GetVersionStagingPath(currentVer.Version);
- Log().Info($"Moving '{currentVer.DirectoryPath}' to '{legacyVersionDir}'.");
- Utility.Retry(() => Directory.Move(currentVer.DirectoryPath, legacyVersionDir));
- }
-
- // this directory does not support being named 'current'
- if (latestVer.Manifest == null) {
- Log().Info($"Cannot promote {latestVer.Version} as current as it has no manifest");
- return latestVer.DirectoryPath;
- }
-
- // 'current' doesn't exist right now, lets move the latest version
- var latestDir = CurrentVersionDir;
- Log().Info($"Moving '{latestVer.DirectoryPath}' to '{latestDir}'.");
- Utility.Retry(() => Directory.Move(latestVer.DirectoryPath, latestDir));
-
- Log().Info("Running app in: " + latestDir);
- return latestDir;
- } catch (Exception e) {
- var releases = GetVersions();
- string fallback = releases.OrderByDescending(m => m.Version).First().DirectoryPath;
- var currentVer = releases.FirstOrDefault(f => f.IsCurrent);
- if (currentVer != default && Directory.Exists(currentVer.DirectoryPath)) {
- fallback = currentVer.DirectoryPath;
- }
- Log().WarnException("Unable to update 'current' directory", e);
- Log().Info("Running app in: " + fallback);
- return fallback;
- }
- }
-
- internal VersionDirInfo GetLatestVersion()
- {
- return GetLatestVersion(GetVersions());
- }
-
- internal VersionDirInfo GetLatestVersion(IEnumerable versions)
- {
- return versions.OrderByDescending(r => r.Version).First();
- }
-
- internal VersionDirInfo GetVersionInfoFromDirectory(string d)
- {
- bool isCurrent = Utility.FullPathEquals(d, CurrentVersionDir);
- var directoryName = Path.GetFileName(d);
- bool isExecuting = Utility.IsFileInDirectory(SquirrelRuntimeInfo.EntryExePath, d);
- var manifest = Utility.ReadManifestFromVersionDir(d);
- if (manifest != null) {
- return new(manifest, manifest.Version, d, isCurrent, isExecuting);
- } else if (Utility.PathPartStartsWith(directoryName, "app-") && NuGetVersion.TryParse(directoryName.Substring(4), out var ver)) {
- return new(null, ver, d, isCurrent, isExecuting);
- }
- return null;
- }
-
- internal record VersionDirInfo(IPackage Manifest, SemanticVersion Version, string DirectoryPath, bool IsCurrent, bool IsExecuting);
- internal VersionDirInfo[] GetVersions()
- {
- return Directory.EnumerateDirectories(RootAppDir, "app-*", SearchOption.TopDirectoryOnly)
- .Concat(Directory.EnumerateDirectories(VersionStagingDir, "app-*", SearchOption.TopDirectoryOnly))
- .Concat(new[] { CurrentVersionDir })
- .Select(Utility.NormalizePath)
- .Distinct(SquirrelRuntimeInfo.PathStringComparer)
- .Select(GetVersionInfoFromDirectory)
- .Where(d => d != null)
- .ToArray();
- }
-
- private static string GetInstalledApplicationName(string updateExePath)
- {
- if (updateExePath == null)
- return null;
- var fi = new FileInfo(updateExePath);
- return fi.Directory.Name;
- }
-
- private static string GetUpdateExe()
- {
- var ourPath = SquirrelRuntimeInfo.EntryExePath;
-
- // Are we update.exe?
- if (ourPath != null &&
- Path.GetFileName(ourPath).Equals("update.exe", StringComparison.OrdinalIgnoreCase) &&
- ourPath.IndexOf("app-", StringComparison.OrdinalIgnoreCase) == -1 &&
- ourPath.IndexOf("SquirrelTemp", StringComparison.OrdinalIgnoreCase) == -1) {
- return Path.GetFullPath(ourPath);
- }
-
- var updateDotExe = Path.Combine(SquirrelRuntimeInfo.BaseDirectory, "..\\Update.exe");
- var target = new FileInfo(updateDotExe);
-
- if (!target.Exists)
- return null;
- return target.FullName;
- }
-
- private static string GetLocalAppDataDirectory()
- {
- // if we're installed and running as update.exe in the app folder, the app directory root is one folder up
- if (SquirrelRuntimeInfo.IsSingleFile && Path.GetFileName(SquirrelRuntimeInfo.EntryExePath).Equals("Update.exe", StringComparison.OrdinalIgnoreCase)) {
- var oneFolderUpFromAppFolder = Path.Combine(Path.GetDirectoryName(SquirrelRuntimeInfo.EntryExePath), "..");
- return Path.GetFullPath(oneFolderUpFromAppFolder);
- }
-
- // if update exists above us, we're running from within a version directory, and the appdata folder is two above us
- if (File.Exists(Path.Combine(SquirrelRuntimeInfo.BaseDirectory, "..", "Update.exe"))) {
- var twoFoldersUpFromAppFolder = Path.Combine(Path.GetDirectoryName(SquirrelRuntimeInfo.EntryExePath), "..\\..");
- return Path.GetFullPath(twoFoldersUpFromAppFolder);
- }
-
- // if neither of the above are true, we're probably not installed yet, so return the real appdata directory
- return Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
- }
-
- private SemanticVersion GetCurrentlyInstalledVersion(string executable = null)
- {
- if (String.IsNullOrEmpty(RootAppDir) || String.IsNullOrEmpty(UpdateExePath))
- return null;
-
- executable = Path.GetFullPath(executable ?? SquirrelRuntimeInfo.EntryExePath);
-
- // check if the application to check is in the correct application directory
- if (!Utility.IsFileInDirectory(executable, RootAppDir))
- return null;
-
- // check if Update.exe exists in the expected relative location
- var baseDir = Path.GetDirectoryName(executable);
- if (!File.Exists(Path.Combine(baseDir, "..\\Update.exe")))
- return null;
-
- // if a 'my version' file exists, use that instead.
- var manifest = Utility.ReadManifestFromVersionDir(baseDir);
- if (manifest != null) {
- return manifest.Version;
- }
-
- var exePathWithoutAppDir = executable.Substring(RootAppDir.Length);
- var appDirName = exePathWithoutAppDir.Split(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar)
- .FirstOrDefault(x => x.StartsWith("app-", StringComparison.OrdinalIgnoreCase));
-
- // check if we are inside an 'app-{ver}' directory and extract version
- if (appDirName == null)
- return null;
-
- return NuGetVersion.Parse(appDirName.Substring(4));
- }
- }
-}
diff --git a/src/Squirrel/UpdateManager.ApplyReleases.cs b/src/Squirrel/UpdateManager.ApplyReleases.cs
index 5bd9ccbe..2a755398 100644
--- a/src/Squirrel/UpdateManager.ApplyReleases.cs
+++ b/src/Squirrel/UpdateManager.ApplyReleases.cs
@@ -375,8 +375,8 @@ namespace Squirrel
Contract.Requires(deltaPackageZip != null);
Contract.Requires(!String.IsNullOrEmpty(outputFile) && !File.Exists(outputFile));
- using (Utility.GetTempDirectory(out var deltaPath, _config.TempDir))
- using (Utility.GetTempDirectory(out var workingPath, _config.TempDir)) {
+ using (Utility.GetTempDirectory(out var deltaPath, _config.AppTempDir))
+ using (Utility.GetTempDirectory(out var workingPath, _config.AppTempDir)) {
EasyZip.ExtractZipToDirectory(deltaPackageZip, deltaPath);
progress(25);
@@ -438,7 +438,7 @@ namespace Squirrel
var inputFile = Path.Combine(deltaPath, relativeFilePath);
var finalTarget = Path.Combine(workingDirectory, Regex.Replace(relativeFilePath, @"\.(bs)?diff$", ""));
- using var _d = Utility.GetTempFileName(out var tempTargetFile, _config.TempDir);
+ using var _d = Utility.GetTempFileName(out var tempTargetFile, _config.AppTempDir);
// NB: Zero-length diffs indicate the file hasn't actually changed
if (new FileInfo(inputFile).Length == 0) {
diff --git a/src/Squirrel/UpdateManager.cs b/src/Squirrel/UpdateManager.cs
index edaabc76..e1acd657 100644
--- a/src/Squirrel/UpdateManager.cs
+++ b/src/Squirrel/UpdateManager.cs
@@ -25,88 +25,63 @@ namespace Squirrel
///
public virtual string AppDirectory => _config.RootAppDir;
- /// The describes the structure of the application on disk (eg. file/folder locations).
- public UpdateConfig Config => _config;
+ /// The describes the structure of the application on disk (eg. file/folder locations).
+ public AppDesc Config => _config;
/// The responsible for retrieving updates from a package repository.
public IUpdateSource Source => _source;
private readonly IUpdateSource _source;
- private readonly UpdateConfig _config;
+ private readonly AppDesc _config;
private readonly object _lockobj = new object();
private IDisposable _updateLock;
private bool _disposed;
///
/// Create a new instance of to check for and install updates.
- /// Do not forget to dispose this class! This constructor is just a shortcut for
- /// , and will automatically create
- /// a or a depending on
- /// whether 'urlOrPath' is a filepath or a URL, respectively.
+ /// Do not forget to dispose this class!
///
///
- /// The URL where your update packages or stored, or a local package repository directory.
+ /// The URL or local directory that contains application update files (.nupkg and RELEASES)
///
- ///
- /// The Id of your application should correspond with the
- /// appdata directory name, and the Id used with Squirrel releasify/pack.
- /// If left null/empty, UpdateManger will attempt to determine the current application Id
- /// from the installed app location, or throw if the app is not currently installed during certain
- /// operations.
- ///
- ///
- /// Provide a custom location for the system LocalAppData, it will be used
- /// instead of .
- ///
- ///
- /// A custom file downloader, for using non-standard package sources or adding proxy configurations.
- ///
- public UpdateManager(
- string urlOrPath,
- string applicationIdOverride = null,
- string localAppDataDirectoryOverride = null,
- IFileDownloader urlDownloader = null)
- : this(CreateSource(urlOrPath, urlDownloader), applicationIdOverride, localAppDataDirectoryOverride)
- { }
+ public UpdateManager(string urlOrPath) : this(CreateSource(urlOrPath))
+ {
+ }
///
/// Create a new instance of to check for and install updates.
/// Do not forget to dispose this class!
///
- ///
+ ///
/// The source of your update packages. This can be a web server (),
/// a local directory (), a GitHub repository (),
/// or a custom location.
///
- ///
- /// The Id of your application should correspond with the
- /// appdata directory name, and the Id used with Squirrel releasify/pack.
- /// If left null/empty, UpdateManger will attempt to determine the current application Id
- /// from the installed app location, or throw if the app is not currently installed during certain
- /// operations.
- ///
- ///
- /// Provide a custom location for the system LocalAppData, it will be used
- /// instead of .
- ///
- public UpdateManager(
- IUpdateSource updateSource,
- string applicationIdOverride = null,
- string localAppDataDirectoryOverride = null)
- : this(updateSource, new UpdateConfig(applicationIdOverride, localAppDataDirectoryOverride))
- { }
+ public UpdateManager(IUpdateSource source) : this(source, null)
+ {
+ }
- public UpdateManager(
- string urlOrPath,
- UpdateConfig config,
- IFileDownloader urlDownloader = null)
- : this(CreateSource(urlOrPath, urlDownloader), config)
- { }
-
- public UpdateManager(IUpdateSource source, UpdateConfig config)
+ ///
+ /// Create a new instance of to check for and install updates.
+ /// Do not forget to dispose this class!
+ ///
+ ///
+ /// The source of your update packages. This can be a web server (),
+ /// a local directory (), a GitHub repository (),
+ /// or a custom location.
+ ///
+ ///
+ /// For configuring advanced / custom deployment scenarios. Should not be used unless
+ /// you know what you are doing.
+ ///
+ public UpdateManager(IUpdateSource source, AppDesc config)
{
_source = source;
- _config = config;
+ _config = config ?? AppDesc.GetCurrentPlatform();
+ }
+
+ internal UpdateManager(string urlOrPath, string appId) : this(CreateSource(urlOrPath), new AppDescWindows())
+ {
}
internal UpdateManager() { }
@@ -140,7 +115,7 @@ namespace Squirrel
bool ignoreDeltaUpdates = false;
- retry:
+ retry:
var updateInfo = default(UpdateInfo);
try {
@@ -166,17 +141,16 @@ namespace Squirrel
}
await this.ErrorIfThrows(() =>
- DownloadReleases(updateInfo.ReleasesToApply, x => progress(x / 3 + 33)),
+ DownloadReleases(updateInfo.ReleasesToApply, x => progress(x / 3 + 33)),
"Failed to download updates").ConfigureAwait(false);
await this.ErrorIfThrows(() =>
- ApplyReleases(updateInfo, x => progress(x / 3 + 66)),
+ ApplyReleases(updateInfo, x => progress(x / 3 + 66)),
"Failed to apply updates").ConfigureAwait(false);
if (SquirrelRuntimeInfo.IsWindows) {
await CreateUninstallerRegistryEntry().ConfigureAwait(false);
}
-
} catch {
if (ignoreDeltaUpdates == false) {
ignoreDeltaUpdates = true;
@@ -186,9 +160,7 @@ namespace Squirrel
throw;
}
- return updateInfo.ReleasesToApply.Any() ?
- updateInfo.ReleasesToApply.MaxBy(x => x.Version).Last() :
- default(ReleaseEntry);
+ return updateInfo.ReleasesToApply.Any() ? updateInfo.ReleasesToApply.MaxBy(x => x.Version).Last() : default(ReleaseEntry);
}
///
@@ -205,6 +177,7 @@ namespace Squirrel
if (disp != null) {
disp.Dispose();
}
+
_disposed = true;
GC.SuppressFinalize(this);
}
@@ -222,7 +195,7 @@ namespace Squirrel
/// however you'd like.
public void RestartApp(string exeToStart = null, string arguments = null)
{
- restartProcess(exeToStart, arguments);
+ AppDesc.GetCurrentPlatform().StartRestartingProcess(exeToStart, arguments);
// NB: We have to give update.exe some time to grab our PID
Thread.Sleep(500);
Environment.Exit(0);
@@ -236,9 +209,9 @@ namespace Squirrel
/// the current executable.
/// Arguments to start the exe with
/// The Update.exe process that is waiting for this process to exit
- public Process RestartAppWhenExited(string exeToStart = null, string arguments = null)
+ public static Process RestartAppWhenExited(string exeToStart = null, string arguments = null)
{
- var process = restartProcess(exeToStart, arguments);
+ var process = AppDesc.GetCurrentPlatform().StartRestartingProcess(exeToStart, arguments);
// NB: We have to give update.exe some time to grab our PID
Thread.Sleep(500);
return process;
@@ -252,63 +225,16 @@ namespace Squirrel
/// the current executable.
/// Arguments to start the exe with
/// The Update.exe process that is waiting for this process to exit
- public async Task RestartAppWhenExitedAsync(string exeToStart = null, string arguments = null)
+ public static async Task RestartAppWhenExitedAsync(string exeToStart = null, string arguments = null)
{
- var process = restartProcess(exeToStart, arguments);
+ var process = AppDesc.GetCurrentPlatform().StartRestartingProcess(exeToStart, arguments);
// NB: We have to give update.exe some time to grab our PID
await Task.Delay(500).ConfigureAwait(false);
return process;
}
- [SupportedOSPlatform("windows")]
- private Process restartProcess(string exeToStart = null, string arguments = null)
- {
- // NB: Here's how this method works:
- //
- // 1. We're going to pass the *name* of our EXE and the params to
- // Update.exe
- // 2. Update.exe is going to grab our PID (via getting its parent),
- // then wait for us to exit.
- // 3. Return control and new Process back to caller and allow them to Exit as desired.
- // 4. After our process exits, Update.exe unblocks, then we launch the app again, possibly
- // launching a different version than we started with (this is why
- // we take the app's *name* rather than a full path)
- exeToStart = exeToStart ?? Path.GetFileName(SquirrelRuntimeInfo.EntryExePath);
-
- List args = new() {
- "--forceLatest",
- "--processStartAndWait",
- exeToStart,
- };
-
- if (arguments != null) {
- args.Add("-a");
- args.Add(arguments);
- }
-
- return ProcessUtil.StartNonBlocking(_config.UpdateExePath, args, Path.GetDirectoryName(_config.UpdateExePath));
- }
-
- private static string GetLocalAppDataDirectory(string assemblyLocation = null)
- {
- // if we're installed and running as update.exe in the app folder, the app directory root is one folder up
- if (SquirrelRuntimeInfo.IsSingleFile && Path.GetFileName(SquirrelRuntimeInfo.EntryExePath).Equals("Update.exe", StringComparison.OrdinalIgnoreCase)) {
- var oneFolderUpFromAppFolder = Path.Combine(Path.GetDirectoryName(SquirrelRuntimeInfo.EntryExePath), "..");
- return Path.GetFullPath(oneFolderUpFromAppFolder);
- }
-
- // if update exists above us, we're running from within a version directory, and the appdata folder is two above us
- if (File.Exists(Path.Combine(SquirrelRuntimeInfo.BaseDirectory, "..", "Update.exe"))) {
- var twoFoldersUpFromAppFolder = Path.Combine(Path.GetDirectoryName(SquirrelRuntimeInfo.EntryExePath), "..\\..");
- return Path.GetFullPath(twoFoldersUpFromAppFolder);
- }
-
- // if neither of the above are true, we're probably not installed yet, so return the real appdata directory
- return Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
- }
-
- private static IUpdateSource CreateSource(string urlOrPath, IFileDownloader urlDownloader)
+ private static IUpdateSource CreateSource(string urlOrPath, IFileDownloader urlDownloader = null)
{
if (String.IsNullOrWhiteSpace(urlOrPath)) {
return null;
@@ -333,8 +259,7 @@ namespace Squirrel
IDisposable theLock;
try {
- theLock = ModeDetector.InUnitTestRunner() ?
- Disposable.Create(() => { }) : new SingleGlobalInstance(key, TimeSpan.FromMilliseconds(2000));
+ theLock = ModeDetector.InUnitTestRunner() ? Disposable.Create(() => { }) : new SingleGlobalInstance(key, TimeSpan.FromMilliseconds(2000));
} catch (TimeoutException) {
throw new TimeoutException("Couldn't acquire update lock, another instance may be running updates");
}
@@ -370,4 +295,4 @@ namespace Squirrel
return (int) totalPercentage;
}
}
-}
+}
\ No newline at end of file
diff --git a/src/Update.OSX/Program.cs b/src/Update.OSX/Program.cs
index a5ed6501..486ed917 100644
--- a/src/Update.OSX/Program.cs
+++ b/src/Update.OSX/Program.cs
@@ -1,5 +1,6 @@
using System;
using System.Diagnostics;
+using System.Runtime.InteropServices;
using System.Threading;
using Squirrel.SimpleSplat;
@@ -7,7 +8,6 @@ namespace Squirrel.Update
{
class Program : IEnableLogger
{
- static StartupOption _options;
static IFullLogger Log => SquirrelLocator.Current.GetService().GetLogger(typeof(Program));
public static int Main(string[] args)
@@ -16,19 +16,19 @@ namespace Squirrel.Update
try {
logger = new SetupLogLogger(Utility.GetDefaultTempBaseDirectory());
SquirrelLocator.CurrentMutable.Register(() => logger, typeof(ILogger));
- _options = new StartupOption(args);
+ var opt = new StartupOption(args);
- if (_options.updateAction == UpdateAction.Unset) {
- _options.WriteOptionDescriptions();
+ if (opt.updateAction == UpdateAction.Unset) {
+ opt.WriteOptionDescriptions();
return -1;
}
Log.Info("Starting Squirrel Updater: " + String.Join(" ", args));
Log.Info("Updater location is: " + SquirrelRuntimeInfo.EntryExePath);
- switch (_options.updateAction) {
- case UpdateAction.ApplyLatest:
- ApplyLatestVersion(_options.updateCurrentApp, _options.updateStagingDir, _options.restartApp);
+ switch (opt.updateAction) {
+ case UpdateAction.ProcessStart:
+ ProcessStart(opt.processStart, opt.processStartArgs, opt.shouldWait, opt.forceLatest);
break;
}
@@ -41,21 +41,27 @@ namespace Squirrel.Update
}
}
- static void ApplyLatestVersion(string currentDir, string stagingDir, bool restartApp)
+ static void ProcessStart(string exeName, string arguments, bool shouldWait, bool forceLatest)
{
- if (!Utility.FileHasExtension(currentDir, ".app")) {
- throw new ArgumentException("The current dir must end with '.app' on macos.");
- }
+ if (shouldWait) waitForParentToExit();
+
// todo https://stackoverflow.com/questions/51441576/how-to-run-app-as-sudo
// https://stackoverflow.com/questions/10283062/getting-sudo-to-ask-for-password-via-the-gui
- Process.Start("killall", $"`basename -a '{currentDir}'`")?.WaitForExit();
+ var desc = new AppDescOsx();
+ var currentDir = desc.UpdateAndRetrieveCurrentFolder(forceLatest);
- var config = new UpdateConfig(null, null);
- config.UpdateAndRetrieveCurrentFolder(false);
+ ProcessUtil.InvokeProcess("open", new[] { "-n", currentDir }, null, CancellationToken.None);
+ }
- if (restartApp)
- ProcessUtil.InvokeProcess("open", new[] { "-n", currentDir }, null, CancellationToken.None);
+ [DllImport("libSystem.dylib")]
+ private static extern int getppid();
+
+ static void waitForParentToExit()
+ {
+ var parentPid = getppid();
+ var proc = Process.GetProcessById(parentPid);
+ proc.WaitForExit();
}
}
}
\ No newline at end of file
diff --git a/src/Update.OSX/StartupOption.cs b/src/Update.OSX/StartupOption.cs
index c72a7380..ffd4b283 100644
--- a/src/Update.OSX/StartupOption.cs
+++ b/src/Update.OSX/StartupOption.cs
@@ -6,17 +6,18 @@ namespace Squirrel.Update
{
enum UpdateAction
{
- Unset = 0, ApplyLatest
+ Unset = 0, ProcessStart
}
internal class StartupOption
{
private readonly OptionSet optionSet;
internal UpdateAction updateAction { get; private set; } = default(UpdateAction);
- internal string updateCurrentApp { get; private set; }
- internal string updateStagingDir { get; private set; }
- internal bool restartApp { get; private set; }
-
+ internal string processStart { get; private set; } = default(string);
+ internal string processStartArgs { get; private set; } = default(string);
+ internal bool shouldWait { get; private set; } = false;
+ internal bool forceLatest { get; private set; } = false;
+
public StartupOption(string[] args)
{
optionSet = Parse(args);
@@ -32,18 +33,14 @@ namespace Squirrel.Update
#pragma warning restore CS0436 // Type conflicts with imported type
$"Usage: {exeName} command [OPTS]",
"",
- "Commands:", {
- "apply=", "Replace {0:CURRENT} .app with the latest in {1:STAGING}",
- (v1, v2) => {
- updateAction = UpdateAction.ApplyLatest;
- updateCurrentApp = v1;
- updateStagingDir = v2;
- }
- },
- { "restartApp", "Launch the app after applying the latest version", v => restartApp = true },
+ "Commands:",
+ { "processStart=", "Start an executable in the current version of the app package", v => { updateAction = UpdateAction.ProcessStart; processStart = v; }, true},
+ { "processStartAndWait=", "Start an executable in the current version of the app package", v => { updateAction = UpdateAction.ProcessStart; processStart = v; shouldWait = true; }, true},
"",
"Options:",
{ "h|?|help", "Display Help and exit", _ => { } },
+ { "forceLatest", "Force updates the current version folder", v => forceLatest = true},
+ { "a=|process-start-args=", "Arguments that will be used when starting executable", v => processStartArgs = v, true},
};
opts.Parse(args);
diff --git a/src/Update.Windows/Program.cs b/src/Update.Windows/Program.cs
index 754a9a43..f2dd7940 100644
--- a/src/Update.Windows/Program.cs
+++ b/src/Update.Windows/Program.cs
@@ -445,7 +445,7 @@ namespace Squirrel.Update
if (shouldWait) waitForParentToExit();
- var config = new UpdateConfig(null, null);
+ var config = new AppDescWindows();
var latestAppDir = config.UpdateAndRetrieveCurrentFolder(forceLatest);
// Check for the EXE name they want
@@ -586,7 +586,6 @@ namespace Squirrel.Update
return true;
}
-
static string getAppNameFromDirectory(string path = null)
{
path = path ?? SquirrelRuntimeInfo.BaseDirectory;
diff --git a/src/Update.Windows/StartupOption.cs b/src/Update.Windows/StartupOption.cs
index b127b373..7d289207 100644
--- a/src/Update.Windows/StartupOption.cs
+++ b/src/Update.Windows/StartupOption.cs
@@ -61,7 +61,7 @@ namespace Squirrel.Update
{ "s|silent", "Silent install", _ => silentInstall = true, true},
{ "processStart=", "Start an executable in the current version of the app package", v => { updateAction = UpdateAction.ProcessStart; processStart = v; }, true},
{ "processStartAndWait=", "Start an executable in the current version of the app package", v => { updateAction = UpdateAction.ProcessStart; processStart = v; shouldWait = true; }, true},
- { "forceLatest", "Force updates the current version junction", v => forceLatest = true},
+ { "forceLatest", "Force updates the current version folder", v => forceLatest = true},
{ "a=|process-start-args=", "Arguments that will be used when starting executable", v => processStartArgs = v, true},
{ "setup=", "Install the package at this location", v => { updateAction = UpdateAction.Setup; target = v; }, true },
{ "setupOffset=", "Offset where in setup package to start reading", v => { setupOffset = long.Parse(v); }, true },