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

👌 Async switcher (Swift concurrency)

This commit is contained in:
2023-01-07 12:53:27 +01:00
parent 61ecefb6e7
commit 71e1ed1b93
12 changed files with 72 additions and 85 deletions

View File

@@ -117,14 +117,10 @@ class Actions {
If this does not solve the issue, the user may need to install additional If this does not solve the issue, the user may need to install additional
extensions and/or run `composer global update`. extensions and/or run `composer global update`.
*/ */
public static func fixMyValet(completed: @escaping () -> Void) { public static func fixMyValet() async {
InternalSwitcher().performSwitch(to: PhpEnv.brewPhpAlias, completion: { await InternalSwitcher().performSwitch(to: PhpEnv.brewPhpAlias)
Task { // Restart all services asynchronously and fire callback upon completion
await brew("services restart \(Homebrew.Formulae.dnsmasq)", sudo: Homebrew.Formulae.dnsmasq.elevated) await brew("services restart \(Homebrew.Formulae.dnsmasq)", sudo: Homebrew.Formulae.dnsmasq.elevated)
await brew("services restart \(Homebrew.Formulae.php)", sudo: Homebrew.Formulae.php.elevated) await brew("services restart \(Homebrew.Formulae.php)", sudo: Homebrew.Formulae.php.elevated)
await brew("services restart \(Homebrew.Formulae.nginx)", sudo: Homebrew.Formulae.nginx.elevated) await brew("services restart \(Homebrew.Formulae.nginx)", sudo: Homebrew.Formulae.nginx.elevated)
completed()
}
})
} }
} }

View File

@@ -42,8 +42,8 @@ class ActivePhpInstallation {
do { do {
try determineVersion() try determineVersion()
} catch { } catch {
// TODO: Throw up an alert if the PHP version cannot be parsed #warning("In future versions of PHP Monitor, this should not crash")
fatalError("Could not determine or parse PHP version") fatalError("Could not determine or parse PHP version; aborting")
} }
// Initialize the list of ini files that are loaded // Initialize the list of ini files that are loaded

View File

@@ -88,7 +88,7 @@ class PhpExtension {
if !isRunningTests { if !isRunningTests {
// When running unit tests, the MainMenu will not be available // When running unit tests, the MainMenu will not be available
// TODO: Fix this dependency issue, set up a notification mechanism // TODO: Investigate an alternate approach w/ notification or publishable
Task { @MainActor in Task { @MainActor in
MainMenu.shared.rebuild() MainMenu.shared.rebuild()
} }

View File

@@ -19,31 +19,31 @@ class InternalSwitcher: PhpSwitcher {
Please note that depending on which version is installed, Please note that depending on which version is installed,
the version that is switched to may or may not be identical to `php` the version that is switched to may or may not be identical to `php`
(without @version). (without @version).
TODO: Use `async` and use structured concurrency: https://www.hackingwithswift.com/swift/5.5/structured-concurrency
*/ */
func performSwitch(to version: String, completion: @escaping () -> Void) { func performSwitch(to version: String) async {
Log.info("Switching to \(version), unlinking all versions...") Log.info("Switching to \(version), unlinking all versions...")
let versions = getVersionsToBeHandled(version) let versions = getVersionsToBeHandled(version)
let group = DispatchGroup()
PhpEnv.shared.availablePhpVersions.forEach { (available) in await withTaskGroup(of: String.self, body: { group in
group.enter() for available in PhpEnv.shared.availablePhpVersions {
group.addTask {
Task {
await self.disableDefaultPhpFpmPool(available) await self.disableDefaultPhpFpmPool(available)
await self.stopPhpVersion(available) await self.stopPhpVersion(available)
group.leave() return available
} }
} }
group.notify(queue: .global(qos: .userInitiated)) { var unlinked: [String] = []
Task { for await version in group {
Log.info("All versions have been unlinked!") unlinked.append(version)
Log.info("Linking the new version!") }
Log.info("These versions have been unlinked: \(unlinked)")
Log.info("Linking the new version \(version)!")
for formula in versions { for formula in versions {
Log.info("Will start PHP \(version)... (primary: \(version == formula))")
await self.startPhpVersion(formula, primary: (version == formula)) await self.startPhpVersion(formula, primary: (version == formula))
} }
@@ -51,9 +51,7 @@ class InternalSwitcher: PhpSwitcher {
await brew("services restart nginx", sudo: true) await brew("services restart nginx", sudo: true)
Log.info("The new version(s) have been linked!") Log.info("The new version(s) have been linked!")
completion() })
}
}
} }
func getVersionsToBeHandled(_ primary: String) -> Set<String> { func getVersionsToBeHandled(_ primary: String) -> Set<String> {

View File

@@ -18,6 +18,6 @@ protocol PhpSwitcherDelegate: AnyObject {
protocol PhpSwitcher { protocol PhpSwitcher {
func performSwitch(to version: String, completion: @escaping () -> Void) func performSwitch(to version: String) async
} }

View File

@@ -29,8 +29,10 @@ class FakeServicesManager: ServicesManager {
self.services = [] self.services = []
self.reapplyServices() self.reapplyServices()
Task { @MainActor in
self.firstRunComplete = true self.firstRunComplete = true
} }
}
private func reapplyServices() { private func reapplyServices() {
let services = self.formulae.map { let services = self.formulae.map {

View File

@@ -15,7 +15,7 @@ class ServicesManager: ObservableObject {
@Published var services = [Service]() @Published var services = [Service]()
@Published var firstRunComplete: Bool = false @Published @MainActor var firstRunComplete: Bool = false
public static func useFake() { public static func useFake() {
ServicesManager.shared = FakeServicesManager.init( ServicesManager.shared = FakeServicesManager.init(

View File

@@ -15,9 +15,12 @@ class ValetServicesManager: ServicesManager {
// Load the initial services state // Load the initial services state
Task { Task {
await self.reloadServicesStatus() await self.reloadServicesStatus()
Task { @MainActor in
firstRunComplete = true firstRunComplete = true
} }
} }
}
/** /**
The last known state of all Homebrew services. The last known state of all Homebrew services.

View File

@@ -142,17 +142,14 @@ class Valet {
in use. This allows PHP Monitor to do different things when Valet 3.0 is enabled. in use. This allows PHP Monitor to do different things when Valet 3.0 is enabled.
*/ */
public func evaluateFeatureSupport() { public func evaluateFeatureSupport() {
let isVersion2 = version.isSameMajorVersionAs(try! VersionNumber.parse("2.0")) switch version.major {
let isVersion3 = version.isSameMajorVersionAs(try! VersionNumber.parse("3.0")) case 2:
let isVersion4 = version.isSameMajorVersionAs(try! VersionNumber.parse("4.0"))
if isVersion2 {
Log.info("You are running Valet v2. Support for site isolation is disabled.") Log.info("You are running Valet v2. Support for site isolation is disabled.")
} else if isVersion3 || isVersion4 { case 3, 4:
Log.info("You are running Valet v3 or v4. Support for site isolation is available.") Log.info("You are running Valet v\(version.major). Support for site isolation is available.")
self.features.append(.isolatedSites) self.features.append(.isolatedSites)
} else { default:
// TODO: Show an alert and notify that some features might not work #warning("An alert should be presented here")
Log.err("This version of Valet is not supported.") Log.err("This version of Valet is not supported.")
} }
} }

View File

@@ -240,15 +240,12 @@ extension MainMenu {
Task(priority: .userInitiated) { [unowned self] in Task(priority: .userInitiated) { [unowned self] in
updatePhpVersionInStatusBar() updatePhpVersionInStatusBar()
rebuild() rebuild()
PhpEnv.switcher.performSwitch( await PhpEnv.switcher.performSwitch(to: version)
to: version,
completion: {
PhpEnv.shared.currentInstall = ActivePhpInstallation() PhpEnv.shared.currentInstall = ActivePhpInstallation()
App.shared.handlePhpConfigWatcher() App.shared.handlePhpConfigWatcher()
PhpEnv.shared.delegate?.switcherDidCompleteSwitch(to: version) PhpEnv.shared.delegate?.switcherDidCompleteSwitch(to: version)
} }
)
}
} }
// MARK: - Async // MARK: - Async
@@ -259,8 +256,6 @@ extension MainMenu {
await MainMenu.shared.switchToPhp("8.1") await MainMenu.shared.switchToPhp("8.1")
// thing to do after the switch // thing to do after the switch
``` ```
Since this async function uses `withCheckedContinuation`
any code after will run only after the switcher is done.
*/ */
func switchToPhp(_ version: String) async { func switchToPhp(_ version: String) async {
Task { @MainActor [self] in Task { @MainActor [self] in
@@ -270,19 +265,13 @@ extension MainMenu {
PhpEnv.shared.delegate?.switcherDidStartSwitching(to: version) PhpEnv.shared.delegate?.switcherDidStartSwitching(to: version)
} }
return await withCheckedContinuation({ continuation in
updatePhpVersionInStatusBar() updatePhpVersionInStatusBar()
rebuild() rebuild()
PhpEnv.switcher.performSwitch( await PhpEnv.switcher.performSwitch(to: version)
to: version,
completion: {
PhpEnv.shared.currentInstall = ActivePhpInstallation() PhpEnv.shared.currentInstall = ActivePhpInstallation()
App.shared.handlePhpConfigWatcher() App.shared.handlePhpConfigWatcher()
PhpEnv.shared.delegate?.switcherDidCompleteSwitch(to: version) PhpEnv.shared.delegate?.switcherDidCompleteSwitch(to: version)
continuation.resume()
}
)
})
} }
} }

View File

@@ -31,8 +31,9 @@ extension MainMenu {
return return
} }
Actions.fixMyValet {
Task { @MainActor in Task { @MainActor in
await Actions.fixMyValet()
if previousVersion == PhpEnv.brewPhpAlias { if previousVersion == PhpEnv.brewPhpAlias {
self.presentAlertForSameVersion() self.presentAlertForSameVersion()
} else { } else {
@@ -40,7 +41,6 @@ extension MainMenu {
} }
} }
} }
}
@MainActor private func presentAlertForMissingFormula() { @MainActor private func presentAlertForMissingFormula() {
BetterAlert() BetterAlert()

View File

@@ -137,7 +137,9 @@ struct ServiceView: View {
? Color("IconColorGreen") ? Color("IconColorGreen")
: Color("IconColorRed") : Color("IconColorRed")
) )
}.frame(width: 25, height: 25) }
.focusable(false)
.frame(width: 25, height: 25)
} }
} }
}.frame(minWidth: 70) }.frame(minWidth: 70)