diff --git a/scripts/util/Platform.cs b/scripts/util/Platform.cs new file mode 100644 index 00000000..593d4669 --- /dev/null +++ b/scripts/util/Platform.cs @@ -0,0 +1,22 @@ +using System.IO; +using Godot; + +namespace util; + +public static class Platform +{ + /// + /// Path to the running .app bundle on macOS, the executable lives at Rhythia.app/Contents/MacOS/Rhythia + /// + public static string MacAppBundlePath => Path.GetFullPath(Path.Combine(OS.GetExecutablePath(), "../../..")); + + /// + /// Wraps a value in single quotes so it is passed to /bin/sh as one literal argument + /// + public static string ShellQuote(string value) => $"'{value.Replace("'", "'\\''")}'"; + + /// + /// Shell snippet that blocks until the current process has exited + /// + public static string WaitForExitScript => $"while kill -0 {OS.GetProcessId()} 2>/dev/null; do sleep 0.1; done"; +} diff --git a/scripts/util/Platform.cs.uid b/scripts/util/Platform.cs.uid new file mode 100644 index 00000000..8124b9d7 --- /dev/null +++ b/scripts/util/Platform.cs.uid @@ -0,0 +1 @@ +uid://d0b88m1ycctvg diff --git a/scripts/util/Releases.cs b/scripts/util/Releases.cs index aa0bfce0..a6089748 100644 --- a/scripts/util/Releases.cs +++ b/scripts/util/Releases.cs @@ -4,6 +4,7 @@ using Godot; using Octokit; using Updatum; +using util; public class Releases { @@ -50,6 +51,36 @@ public static async Task InstallUpdateAsync(UpdatumDownloadedAsset asset) return false; } + if (OS.GetName() == "macOS") + { + return installMacUpdate(asset); + } + return await MANAGER.InstallUpdateAsync(asset); } + + // Updatum finds the .app through AppContext.BaseDirectory, which Godot points at Contents/Resources, + // so it would sync the update inside the running bundle instead of replacing it + private static bool installMacUpdate(UpdatumDownloadedAsset asset) + { + if (asset == null || !asset.FileExists) + { + return false; + } + + string app = Platform.ShellQuote(Platform.MacAppBundlePath); + string zip = Platform.ShellQuote(asset.FilePath); + + string script = + $"{Platform.WaitForExitScript}; " + + $"tmp=$(mktemp -d) && ditto -x -k {zip} \"$tmp\" " + + "&& new=$(find \"$tmp\" -maxdepth 1 -name '*.app' | head -n 1) && [ -n \"$new\" ] " + + $"&& mv {app} \"$tmp/old.app\" && {{ ditto \"$new\" {app} || mv \"$tmp/old.app\" {app}; }}; " + + $"xattr -dr com.apple.quarantine {app}; rm -rf \"$tmp\"; open {app}"; + + OS.CreateProcess("/bin/sh", ["-c", script]); + ((SceneTree)Engine.GetMainLoop()).Quit(); + + return true; + } }