1
0
mirror of https://github.com/nicoverbruggen/phpmon.git synced 2025-08-07 03:50:08 +02:00

Use concurrency for updater

This commit is contained in:
2023-02-02 19:22:52 +01:00
parent 1260022d51
commit 4fd48baf63
4 changed files with 136 additions and 89 deletions

View File

@ -579,6 +579,8 @@
C4C3643A28AE4FCE00C0770E /* StatusMenu+Items.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C3643828AE4FCE00C0770E /* StatusMenu+Items.swift */; }; C4C3643A28AE4FCE00C0770E /* StatusMenu+Items.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C3643828AE4FCE00C0770E /* StatusMenu+Items.swift */; };
C4C3ED412783497000AB15D8 /* MainMenu+Startup.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C3ED402783497000AB15D8 /* MainMenu+Startup.swift */; }; C4C3ED412783497000AB15D8 /* MainMenu+Startup.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C3ED402783497000AB15D8 /* MainMenu+Startup.swift */; };
C4C3ED4327834C5200AB15D8 /* CustomPrefs.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C3ED4227834C5200AB15D8 /* CustomPrefs.swift */; }; C4C3ED4327834C5200AB15D8 /* CustomPrefs.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C3ED4227834C5200AB15D8 /* CustomPrefs.swift */; };
C4C75F5A298C2D5700DFD82E /* LaunchControl.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C75F59298C2D5700DFD82E /* LaunchControl.swift */; };
C4C75F5C298C31C000DFD82E /* Alert.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C75F5B298C31C000DFD82E /* Alert.swift */; };
C4C8900328F0E28800CE5E97 /* FileSystemProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C8900228F0E28800CE5E97 /* FileSystemProtocol.swift */; }; C4C8900328F0E28800CE5E97 /* FileSystemProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C8900228F0E28800CE5E97 /* FileSystemProtocol.swift */; };
C4C8900528F0E3D100CE5E97 /* RealFileSystem.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C8900428F0E3D100CE5E97 /* RealFileSystem.swift */; }; C4C8900528F0E3D100CE5E97 /* RealFileSystem.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C8900428F0E3D100CE5E97 /* RealFileSystem.swift */; };
C4C8900728F0E3EF00CE5E97 /* ActiveFileSystem.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C8900628F0E3EF00CE5E97 /* ActiveFileSystem.swift */; }; C4C8900728F0E3EF00CE5E97 /* ActiveFileSystem.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C8900628F0E3EF00CE5E97 /* ActiveFileSystem.swift */; };
@ -908,6 +910,8 @@
C4C3643828AE4FCE00C0770E /* StatusMenu+Items.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "StatusMenu+Items.swift"; sourceTree = "<group>"; }; C4C3643828AE4FCE00C0770E /* StatusMenu+Items.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "StatusMenu+Items.swift"; sourceTree = "<group>"; };
C4C3ED402783497000AB15D8 /* MainMenu+Startup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MainMenu+Startup.swift"; sourceTree = "<group>"; }; C4C3ED402783497000AB15D8 /* MainMenu+Startup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MainMenu+Startup.swift"; sourceTree = "<group>"; };
C4C3ED4227834C5200AB15D8 /* CustomPrefs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomPrefs.swift; sourceTree = "<group>"; }; C4C3ED4227834C5200AB15D8 /* CustomPrefs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomPrefs.swift; sourceTree = "<group>"; };
C4C75F59298C2D5700DFD82E /* LaunchControl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LaunchControl.swift; sourceTree = "<group>"; };
C4C75F5B298C31C000DFD82E /* Alert.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Alert.swift; sourceTree = "<group>"; };
C4C8900228F0E28800CE5E97 /* FileSystemProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileSystemProtocol.swift; sourceTree = "<group>"; }; C4C8900228F0E28800CE5E97 /* FileSystemProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileSystemProtocol.swift; sourceTree = "<group>"; };
C4C8900428F0E3D100CE5E97 /* RealFileSystem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RealFileSystem.swift; sourceTree = "<group>"; }; C4C8900428F0E3D100CE5E97 /* RealFileSystem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RealFileSystem.swift; sourceTree = "<group>"; };
C4C8900628F0E3EF00CE5E97 /* ActiveFileSystem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActiveFileSystem.swift; sourceTree = "<group>"; }; C4C8900628F0E3EF00CE5E97 /* ActiveFileSystem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActiveFileSystem.swift; sourceTree = "<group>"; };
@ -1105,9 +1109,11 @@
C406A5F1298AD2CE00B5B85A /* phpmon-updater */ = { C406A5F1298AD2CE00B5B85A /* phpmon-updater */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
C406A601298AD50D00B5B85A /* Updater.swift */,
C46B2647298B324100084651 /* ReleaseManifest.swift */,
C406A5F2298AD2CE00B5B85A /* main.swift */, C406A5F2298AD2CE00B5B85A /* main.swift */,
C4C75F5B298C31C000DFD82E /* Alert.swift */,
C4C75F59298C2D5700DFD82E /* LaunchControl.swift */,
C46B2647298B324100084651 /* ReleaseManifest.swift */,
C406A601298AD50D00B5B85A /* Updater.swift */,
C406A5F6298AD2CF00B5B85A /* Assets.xcassets */, C406A5F6298AD2CF00B5B85A /* Assets.xcassets */,
C406A5FB298AD2CF00B5B85A /* phpmon-updater.entitlements */, C406A5FB298AD2CF00B5B85A /* phpmon-updater.entitlements */,
C46B264F298B3C2100084651 /* PHP Monitor Self-Updater.app */, C46B264F298B3C2100084651 /* PHP Monitor Self-Updater.app */,
@ -2064,7 +2070,9 @@
isa = PBXSourcesBuildPhase; isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
C4C75F5C298C31C000DFD82E /* Alert.swift in Sources */,
C406A602298AD50D00B5B85A /* Updater.swift in Sources */, C406A602298AD50D00B5B85A /* Updater.swift in Sources */,
C4C75F5A298C2D5700DFD82E /* LaunchControl.swift in Sources */,
C46B2649298B324100084651 /* ReleaseManifest.swift in Sources */, C46B2649298B324100084651 /* ReleaseManifest.swift in Sources */,
C41F3D08298AED0D0042ACBF /* System.swift in Sources */, C41F3D08298AED0D0042ACBF /* System.swift in Sources */,
C406A5F3298AD2CE00B5B85A /* main.swift in Sources */, C406A5F3298AD2CE00B5B85A /* main.swift in Sources */,
@ -2802,7 +2810,7 @@
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES; COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 10; CURRENT_PROJECT_VERSION = 20;
DEVELOPMENT_TEAM = 8M54J5J787; DEVELOPMENT_TEAM = 8M54J5J787;
ENABLE_HARDENED_RUNTIME = YES; ENABLE_HARDENED_RUNTIME = YES;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
@ -2834,7 +2842,7 @@
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES; COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 10; CURRENT_PROJECT_VERSION = 20;
DEVELOPMENT_TEAM = 8M54J5J787; DEVELOPMENT_TEAM = 8M54J5J787;
ENABLE_HARDENED_RUNTIME = YES; ENABLE_HARDENED_RUNTIME = YES;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
@ -2866,7 +2874,7 @@
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES; COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 10; CURRENT_PROJECT_VERSION = 20;
DEVELOPMENT_TEAM = 8M54J5J787; DEVELOPMENT_TEAM = 8M54J5J787;
ENABLE_HARDENED_RUNTIME = YES; ENABLE_HARDENED_RUNTIME = YES;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
@ -2898,7 +2906,7 @@
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES; COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 10; CURRENT_PROJECT_VERSION = 20;
DEVELOPMENT_TEAM = 8M54J5J787; DEVELOPMENT_TEAM = 8M54J5J787;
ENABLE_HARDENED_RUNTIME = YES; ENABLE_HARDENED_RUNTIME = YES;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;

View File

@ -0,0 +1,29 @@
//
// Alert.swift
// PHP Monitor Self-Updater
//
// Created by Nico Verbruggen on 02/02/2023.
// Copyright © 2023 Nico Verbruggen. All rights reserved.
//
import Foundation
import Cocoa
class Alert {
public static func show(description: String, shouldExit: Bool = true) async {
await withUnsafeContinuation { continuation in
DispatchQueue.main.async {
let alert = NSAlert()
alert.messageText = "The app could not be updated."
alert.informativeText = description
alert.addButton(withTitle: "OK")
alert.alertStyle = .critical
alert.runModal()
if shouldExit {
exit(0)
}
continuation.resume()
}
}
}
}

View File

@ -0,0 +1,52 @@
//
// LaunchControl.swift
// PHP Monitor Self-Updater
//
// Created by Nico Verbruggen on 02/02/2023.
// Copyright © 2023 Nico Verbruggen. All rights reserved.
//
import Foundation
import Cocoa
class LaunchControl {
public static func smartRestart(priority: [String]) async {
for appPath in priority {
if FileManager.default.fileExists(atPath: appPath) {
let app = await LaunchControl.startApplication(at: appPath)
if app != nil {
return
}
}
}
}
public static func terminateApplications(bundleIds: [String]) async {
let runningApplications = NSWorkspace.shared.runningApplications
// Look for these instances
let ids = [
"com.nicoverbruggen.phpmon.dev",
"com.nicoverbruggen.phpmon"
]
// Terminate all instances found
for id in ids {
if let phpmon = runningApplications.first(where: {
(application) in return application.bundleIdentifier == id
}) {
phpmon.terminate()
}
}
}
public static func startApplication(at path: String) async -> NSRunningApplication? {
await withCheckedContinuation { continuation in
let url = NSURL(fileURLWithPath: path, isDirectory: true) as URL
let configuration = NSWorkspace.OpenConfiguration()
NSWorkspace.shared.openApplication(at: url, configuration: configuration) { phpmon, error in
continuation.resume(returning: phpmon)
}
}
}
}

View File

@ -15,7 +15,12 @@ class Updater: NSObject, NSApplicationDelegate {
var manifest: ReleaseManifest! = nil var manifest: ReleaseManifest! = nil
func applicationDidFinishLaunching(_ aNotification: Notification) { func applicationDidFinishLaunching(_ aNotification: Notification) {
Task { await self.installUpdate() }
}
func installUpdate() async {
print("PHP MONITOR SELF-UPDATER by Nico Verbruggen") print("PHP MONITOR SELF-UPDATER by Nico Verbruggen")
print("===========================================")
self.updaterDirectory = "~/.config/phpmon/updater" self.updaterDirectory = "~/.config/phpmon/updater"
.replacingOccurrences(of: "~", with: NSHomeDirectory()) .replacingOccurrences(of: "~", with: NSHomeDirectory())
@ -24,32 +29,25 @@ class Updater: NSObject, NSApplicationDelegate {
self.manifestPath = "\(updaterDirectory)/update.json" self.manifestPath = "\(updaterDirectory)/update.json"
print("Checking manifest file at \(manifestPath)") // Fetch the manifest on the local filesystem
let manifest = await parseManifest()!
// Read out the correct information from the manifest JSON
do {
let manifestText = try String(contentsOfFile: manifestPath)
manifest = try JSONDecoder().decode(ReleaseManifest.self, from: manifestText.data(using: .utf8)!)
} catch {
print("Parsing the manifest failed (or the manifest file doesn't exist)")
showAlert(
title: "Key information about the update is missing",
description: "The app has not been updated. The self-updater only works in combination with PHP Monitor. Please try searching for updates again in PHP Monitor."
)
exit(0)
}
// Download the latest file // Download the latest file
let zipPath = download(manifest) let zipPath = await download(manifest)
// Terminate all instances of PHP Monitor first // Terminate all instances of PHP Monitor first
terminatePhpMon() await LaunchControl.terminateApplications(bundleIds: [
"com.nicoverbruggen.phpmon.dev",
"com.nicoverbruggen.phpmon"
])
// Install the app based on the zip // Install the app based on the zip
let appPath = extractAndInstall(zipPath: zipPath) let appPath = await extractAndInstall(zipPath: zipPath)
// Restart PHP Monitor, this will also close the updater // Restart PHP Monitor, this will also close the updater
restartPhpMon(at: appPath) _ = await LaunchControl.startApplication(at: appPath)
exit(1)
} }
func applicationWillTerminate(_ aNotification: Notification) { func applicationWillTerminate(_ aNotification: Notification) {
@ -60,7 +58,23 @@ class Updater: NSObject, NSApplicationDelegate {
return false return false
} }
private func download(_ manifest: ReleaseManifest) -> String { private func parseManifest() async -> ReleaseManifest? {
// Read out the correct information from the manifest JSON
print("Checking manifest file at \(manifestPath)...")
do {
let manifestText = try String(contentsOfFile: manifestPath)
manifest = try JSONDecoder().decode(ReleaseManifest.self, from: manifestText.data(using: .utf8)!)
return manifest
} catch {
print("Parsing the manifest failed (or the manifest file doesn't exist)!")
await Alert.show(description: "The manifest file for a potential update was not found. Please try searching for updates again in PHP Monitor.")
}
return nil
}
private func download(_ manifest: ReleaseManifest) async -> String {
// Remove all zips // Remove all zips
system_quiet("rm -rf \(updaterDirectory)/*.zip") system_quiet("rm -rf \(updaterDirectory)/*.zip")
@ -74,9 +88,7 @@ class Updater: NSObject, NSApplicationDelegate {
// Ensure the zip exists // Ensure the zip exists
if filename.isEmpty { if filename.isEmpty {
print("The update has not been downloaded. Sadly, that means that PHP Monitor cannot not updated!") print("The update has not been downloaded. Sadly, that means that PHP Monitor cannot not updated!")
showAlert(title: "The update was not downloaded.", await Alert.show(description: "PHP Monitor has not been updated. The update was not downloaded, or the file could not be written to disk. Please try again.")
description: "PHP Monitor has not been updated. You may not be connected to the internet or the server may be encountering issues, or the file could not be written to disk. Please try again later!")
exit(1)
} }
// Calculate the checksum for the downloaded file // Calculate the checksum for the downloaded file
@ -93,18 +105,14 @@ class Updater: NSObject, NSApplicationDelegate {
// Make sure the checksum matches before we do anything with the file // Make sure the checksum matches before we do anything with the file
if checksum != manifest.sha256 { if checksum != manifest.sha256 {
print("The checksums failed to match. Cancelling!") print("The checksums failed to match. Cancelling!")
showAlert( await Alert.show(description: "The downloaded update failed checksum validation. Please try again. If this issue persists, there may be an issue with the server and I do not recommend upgrading.")
title: "The downloaded update failed checksum validation",
description: "Please try again! If this issue persists, there may be an issue with the server and I do not recommend upgrading."
)
exit(0)
} }
// Return the path to the zip // Return the path to the zip
return "\(updaterDirectory)/\(filename)" return "\(updaterDirectory)/\(filename)"
} }
private func extractAndInstall(zipPath: String) -> String { private func extractAndInstall(zipPath: String) async -> String {
// Remove the directory that will contain the extracted update // Remove the directory that will contain the extracted update
system_quiet("rm -rf \(updaterDirectory)/extracted") system_quiet("rm -rf \(updaterDirectory)/extracted")
@ -114,11 +122,7 @@ class Updater: NSObject, NSApplicationDelegate {
// Make sure the updater directory exists // Make sure the updater directory exists
var isDirectory: ObjCBool = true var isDirectory: ObjCBool = true
if !FileManager.default.fileExists(atPath: "\(updaterDirectory)/extracted", isDirectory: &isDirectory) { if !FileManager.default.fileExists(atPath: "\(updaterDirectory)/extracted", isDirectory: &isDirectory) {
showAlert( await Alert.show(description: "The updater directory is missing. The automatic updater will quit. Make sure that ` ~/.config/phpmon/updater` is writeable.")
title: "The updater directory is missing",
description: "The automatic updater will quit. Make sure that ` ~/.config/phpmon/updater` is writeable."
)
exit(0)
} }
// Unzip the file // Unzip the file
@ -132,11 +136,7 @@ class Updater: NSObject, NSApplicationDelegate {
// Make sure the file was extracted // Make sure the file was extracted
if app.isEmpty { if app.isEmpty {
showAlert( await Alert.show(description: "The downloaded file could not be extracted. The automatic updater will quit. Make sure that ` ~/.config/phpmon/updater` is writeable.")
title: "The downloaded file could not be extracted",
description: "The automatic updater will quit. Make sure that ` ~/.config/phpmon/updater` is writeable."
)
exit(0)
} }
// Remove the original app // Remove the original app
@ -152,52 +152,10 @@ class Updater: NSObject, NSApplicationDelegate {
// Remove the manifest // Remove the manifest
system_quiet("rm \(manifestPath)") system_quiet("rm \(manifestPath)")
// Write a file that is only written when we upgraded successfully
system_quiet("touch \(updaterDirectory)/upgrade.success")
// Return the new location of the app // Return the new location of the app
return "/Applications/\(app)" return "/Applications/\(app)"
} }
private func terminatePhpMon() {
let runningApplications = NSWorkspace.shared.runningApplications
// Look for these instances
let ids = [
"com.nicoverbruggen.phpmon.dev",
"com.nicoverbruggen.phpmon"
]
// Terminate all instances found
for id in ids {
if let phpmon = runningApplications.first(where: {
(application) in return application.bundleIdentifier == id
}) {
phpmon.terminate()
}
}
}
private func smartRestartPhpMon() {
if FileManager.default.fileExists(atPath: "/Applications/PHP Monitor.app") {
restartPhpMon(at: "/Applications/PHP Monitor.app")
}
else if FileManager.default.fileExists(atPath: "/Applications/PHP Monitor DEV.app") {
restartPhpMon(at: "/Applications/PHP Monitor DEV.app")
}
}
private func restartPhpMon(at path: String) {
let url = NSURL(fileURLWithPath: path, isDirectory: true) as URL
let configuration = NSWorkspace.OpenConfiguration()
NSWorkspace.shared.openApplication(at: url, configuration: configuration) { phpmon, error in
exit(0)
}
}
private func showAlert(title: String, description: String) {
let alert = NSAlert()
alert.messageText = title
alert.informativeText = description
alert.addButton(withTitle: "OK")
alert.alertStyle = .critical
alert.runModal()
}
} }