1
0
mirror of https://github.com/nicoverbruggen/phpmon.git synced 2025-08-07 20:10:08 +02:00
Files
app/phpmon/Domain/App/Services/ValetServicesManager.swift

163 lines
5.5 KiB
Swift

//
// ValetServicesManager.swift
// PHP Monitor
//
// Created by Nico Verbruggen on 23/12/2022.
// Copyright © 2023 Nico Verbruggen. All rights reserved.
//
import Foundation
import Cocoa
import NVAlert
class ValetServicesManager: ServicesManager {
override init() {
super.init()
// Load the initial services state
Task {
await self.reloadServicesStatus()
Task { @MainActor in
firstRunComplete = true
}
}
}
/**
The last known state of all Homebrew services.
*/
var homebrewServices: [HomebrewService] = []
/**
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.
*/
override func reloadServicesStatus() 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
group.addTask {
let rootServiceNames = self.formulae
.filter { $0.elevated }
.map { $0.name }
let rootJson = await Shell
.pipe("sudo \(Paths.brew) services info --all --json")
.out.data(using: .utf8)!
return try! JSONDecoder()
.decode([HomebrewService].self, from: rootJson)
.filter({ return rootServiceNames.contains($0.name) })
}
// 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 Shell
.pipe("\(Paths.brew) services info --all --json")
.out.data(using: .utf8)!
return try! JSONDecoder()
.decode([HomebrewService].self, from: normalJson)
.filter({ return userServiceNames.contains($0.name) })
}
// Ensure that Homebrew services' output is stored
self.homebrewServices = []
for await services in group {
homebrewServices.append(contentsOf: services)
}
// Dispatch the update of the new service wrappers
Task { @MainActor in
// Ensure both commands complete (but run concurrently)
services = formulae.map { formula in
Service(
formula: formula,
service: homebrewServices.first(where: { service in
service.name == formula.name
})
)
}
// Broadcast that all services have been updated
self.broadcastServicesUpdated()
}
})
}
override func toggleService(named: String) async {
guard let wrapper = self[named] else {
return Log.err("The wrapper for '\(named)' is missing.")
}
// Normally, we allow starting and stopping
var action = wrapper.status == .active ? "stop" : "start"
// However, if we've encountered an error, attempt to restart
if wrapper.status == .error {
action = "restart"
}
// Run the command
await brew(
"services \(action) \(wrapper.formula.name)",
sudo: wrapper.formula.elevated
)
// Reload the services status to confirm this worked
await ServicesManager.shared.reloadServicesStatus()
Task {
await presentTroubleshootingForService(named: named)
}
}
@MainActor func presentTroubleshootingForService(named: String) {
let after = self.homebrewServices.first { service in
return service.name == named
}
guard let after else { return }
if after.status == "error" {
Log.err("The service '\(named)' is now reporting an error.")
guard let errorLogPath = after.error_log_path else {
return NVAlert().withInformation(
title: "alert.service_error.title".localized(named),
subtitle: "alert.service_error.subtitle.no_error_log".localized(named),
description: "alert.service_error.extra".localized
)
.withPrimary(text: "alert.service_error.button.close".localized)
.show()
}
NVAlert().withInformation(
title: "alert.service_error.title".localized(named),
subtitle: "alert.service_error.subtitle.error_log".localized(named),
description: "alert.service_error.extra".localized
)
.withPrimary(text: "alert.service_error.button.close".localized)
.withTertiary(text: "alert.service_error.button.show_log".localized, action: { alert in
let url = URL(fileURLWithPath: errorLogPath)
if errorLogPath.hasSuffix(".log") {
NSWorkspace.shared.open(url)
} else {
NSWorkspace.shared.activateFileViewerSelecting([url])
}
alert.close(with: .OK)
})
.show()
}
}
}