mirror of
https://github.com/nicoverbruggen/phpmon.git
synced 2025-12-21 11:10:08 +01:00
♻️ Make ValetServicesManager more crash-resistant
This commit is contained in:
@@ -3920,7 +3920,7 @@
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
CURRENT_PROJECT_VERSION = 1681;
|
||||
CURRENT_PROJECT_VERSION = 1682;
|
||||
DEAD_CODE_STRIPPING = YES;
|
||||
DEBUG = YES;
|
||||
ENABLE_APP_SANDBOX = NO;
|
||||
@@ -3964,7 +3964,7 @@
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
CURRENT_PROJECT_VERSION = 1681;
|
||||
CURRENT_PROJECT_VERSION = 1682;
|
||||
DEAD_CODE_STRIPPING = YES;
|
||||
DEBUG = NO;
|
||||
ENABLE_APP_SANDBOX = NO;
|
||||
@@ -4146,7 +4146,7 @@
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
CURRENT_PROJECT_VERSION = 1681;
|
||||
CURRENT_PROJECT_VERSION = 1682;
|
||||
DEAD_CODE_STRIPPING = YES;
|
||||
DEBUG = YES;
|
||||
ENABLE_APP_SANDBOX = NO;
|
||||
@@ -4339,7 +4339,7 @@
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
CURRENT_PROJECT_VERSION = 1681;
|
||||
CURRENT_PROJECT_VERSION = 1682;
|
||||
DEAD_CODE_STRIPPING = YES;
|
||||
DEBUG = NO;
|
||||
ENABLE_APP_SANDBOX = NO;
|
||||
|
||||
@@ -62,7 +62,7 @@ func grepContains(
|
||||
Attempts to introduce sleep for a particular duration. Use with caution.
|
||||
*/
|
||||
func delay(seconds: Double) async {
|
||||
try! await Task.sleep(nanoseconds: UInt64(seconds * 1_000_000_000))
|
||||
try? await Task.sleep(nanoseconds: UInt64(seconds * 1_000_000_000))
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -34,49 +34,45 @@ class ValetServicesManager: ServicesManager {
|
||||
This method allows us to reload the Homebrew services, but we run this command
|
||||
twice (once for user services, and once for root services). Please note that
|
||||
these two commands are executed concurrently.
|
||||
|
||||
If this fails, question marks will be displayed in the menu bar and we will
|
||||
try one more time to reload the services.
|
||||
*/
|
||||
override func reloadServicesStatus() async {
|
||||
await reloadServicesStatus(isRetry: false)
|
||||
}
|
||||
|
||||
private func reloadServicesStatus(isRetry: Bool) async {
|
||||
if !Valet.installed {
|
||||
return Log.info("Not reloading services because running in Standalone Mode.")
|
||||
}
|
||||
|
||||
await withTaskGroup(of: [HomebrewService].self, body: { group in
|
||||
// First, retrieve the status of the formulae that run as root
|
||||
// Retrieve the status of the formulae that run as root
|
||||
group.addTask {
|
||||
let rootServiceNames = self.formulae
|
||||
.filter { $0.elevated }
|
||||
.map { $0.name }
|
||||
|
||||
let rootJson = await self.container.shell
|
||||
.pipe("sudo \(self.container.paths.brew) services info --all --json")
|
||||
.out.data(using: .utf8)!
|
||||
|
||||
return try! JSONDecoder()
|
||||
.decode([HomebrewService].self, from: rootJson)
|
||||
.filter({ return rootServiceNames.contains($0.name) })
|
||||
await self.fetchHomebrewServices(elevated: true)
|
||||
}
|
||||
|
||||
// At the same time, retrieve the status of the formulae that run as user
|
||||
group.addTask {
|
||||
let userServiceNames = self.formulae
|
||||
.filter { !$0.elevated }
|
||||
.map { $0.name }
|
||||
|
||||
let normalJson = await self.container.shell
|
||||
.pipe("\(self.container.paths.brew) services info --all --json")
|
||||
.out.data(using: .utf8)!
|
||||
|
||||
return try! JSONDecoder()
|
||||
.decode([HomebrewService].self, from: normalJson)
|
||||
.filter({ return userServiceNames.contains($0.name) })
|
||||
await self.fetchHomebrewServices(elevated: false)
|
||||
}
|
||||
|
||||
// Ensure that Homebrew services' output is stored
|
||||
self.homebrewServices = []
|
||||
|
||||
for await services in group {
|
||||
homebrewServices.append(contentsOf: services)
|
||||
}
|
||||
|
||||
// If we didn't get any service data and this isn't a retry, try again
|
||||
if self.homebrewServices.isEmpty && !isRetry {
|
||||
Log.warn("Failed to retrieve any Homebrew services data. Retrying once in 2 seconds...")
|
||||
await delay(seconds: 2)
|
||||
await self.reloadServicesStatus(isRetry: true)
|
||||
return
|
||||
}
|
||||
|
||||
// Dispatch the update of the new service wrappers
|
||||
Task { @MainActor in
|
||||
// Ensure both commands complete (but run concurrently)
|
||||
@@ -95,6 +91,43 @@ class ValetServicesManager: ServicesManager {
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
Fetches Homebrew services information for either elevated (root) or user services.
|
||||
|
||||
- Parameter elevated: Whether to fetch services running as root (true) or user (false)
|
||||
- Returns: Array of HomebrewService objects, or empty array if fetching fails
|
||||
*/
|
||||
private func fetchHomebrewServices(elevated: Bool) async -> [HomebrewService] {
|
||||
// Check which formulae we are supposed to be looking for
|
||||
let serviceNames = self.formulae
|
||||
.filter { $0.elevated == elevated }
|
||||
.map { $0.name }
|
||||
|
||||
// Determine which command to run
|
||||
let command = elevated
|
||||
? "sudo \(self.container.paths.brew) services info --all --json"
|
||||
: "\(self.container.paths.brew) services info --all --json"
|
||||
|
||||
// Run and get the output of the command
|
||||
let output = await self.container.shell.pipe(command).out
|
||||
|
||||
// Attempt to parse the output
|
||||
guard let jsonData = output.data(using: .utf8) else {
|
||||
Log.err("Failed to convert \(elevated ? "root" : "user") services output to UTF-8 data. Output: \(output)")
|
||||
return []
|
||||
}
|
||||
|
||||
// Attempt to decode the JSON output. In certain situations the output may not be valid and this prevents a crash
|
||||
do {
|
||||
return try JSONDecoder()
|
||||
.decode([HomebrewService].self, from: jsonData)
|
||||
.filter { serviceNames.contains($0.name) }
|
||||
} catch {
|
||||
Log.err("Failed to decode \(elevated ? "root" : "user") services JSON: \(error). Output: \(output)")
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
override func toggleService(named: String) async {
|
||||
guard let wrapper = self[named] else {
|
||||
return Log.err("The wrapper for '\(named)' is missing.")
|
||||
|
||||
Reference in New Issue
Block a user