1
0
mirror of https://github.com/nicoverbruggen/phpmon.git synced 2025-08-07 03:50: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
extensions and/or run `composer global update`.
*/
public static func fixMyValet(completed: @escaping () -> Void) {
InternalSwitcher().performSwitch(to: PhpEnv.brewPhpAlias, completion: {
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.php)", sudo: Homebrew.Formulae.php.elevated)
await brew("services restart \(Homebrew.Formulae.nginx)", sudo: Homebrew.Formulae.nginx.elevated)
completed()
}
})
public static func fixMyValet() async {
await InternalSwitcher().performSwitch(to: PhpEnv.brewPhpAlias)
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.nginx)", sudo: Homebrew.Formulae.nginx.elevated)
}
}

View File

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

View File

@ -88,7 +88,7 @@ class PhpExtension {
if !isRunningTests {
// 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
MainMenu.shared.rebuild()
}

View File

@ -15,45 +15,43 @@ class InternalSwitcher: PhpSwitcher {
- unlinking the current version
- stopping the active services
- linking the new desired version
Please note that depending on which version is installed,
the version that is switched to may or may not be identical to `php`
(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...")
let versions = getVersionsToBeHandled(version)
let group = DispatchGroup()
PhpEnv.shared.availablePhpVersions.forEach { (available) in
group.enter()
Task {
await self.disableDefaultPhpFpmPool(available)
await self.stopPhpVersion(available)
group.leave()
}
}
group.notify(queue: .global(qos: .userInitiated)) {
Task {
Log.info("All versions have been unlinked!")
Log.info("Linking the new version!")
for formula in versions {
await self.startPhpVersion(formula, primary: (version == formula))
await withTaskGroup(of: String.self, body: { group in
for available in PhpEnv.shared.availablePhpVersions {
group.addTask {
await self.disableDefaultPhpFpmPool(available)
await self.stopPhpVersion(available)
return available
}
Log.info("Restarting nginx, just to be sure!")
await brew("services restart nginx", sudo: true)
Log.info("The new version(s) have been linked!")
completion()
}
}
var unlinked: [String] = []
for await version in group {
unlinked.append(version)
}
Log.info("These versions have been unlinked: \(unlinked)")
Log.info("Linking the new version \(version)!")
for formula in versions {
Log.info("Will start PHP \(version)... (primary: \(version == formula))")
await self.startPhpVersion(formula, primary: (version == formula))
}
Log.info("Restarting nginx, just to be sure!")
await brew("services restart nginx", sudo: true)
Log.info("The new version(s) have been linked!")
})
}
func getVersionsToBeHandled(_ primary: String) -> Set<String> {

View File

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

View File

@ -29,7 +29,9 @@ class FakeServicesManager: ServicesManager {
self.services = []
self.reapplyServices()
self.firstRunComplete = true
Task { @MainActor in
self.firstRunComplete = true
}
}
private func reapplyServices() {

View File

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

View File

@ -15,7 +15,10 @@ class ValetServicesManager: ServicesManager {
// Load the initial services state
Task {
await self.reloadServicesStatus()
firstRunComplete = true
Task { @MainActor in
firstRunComplete = true
}
}
}

View File

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

View File

@ -240,14 +240,11 @@ extension MainMenu {
Task(priority: .userInitiated) { [unowned self] in
updatePhpVersionInStatusBar()
rebuild()
PhpEnv.switcher.performSwitch(
to: version,
completion: {
PhpEnv.shared.currentInstall = ActivePhpInstallation()
App.shared.handlePhpConfigWatcher()
PhpEnv.shared.delegate?.switcherDidCompleteSwitch(to: version)
}
)
await PhpEnv.switcher.performSwitch(to: version)
PhpEnv.shared.currentInstall = ActivePhpInstallation()
App.shared.handlePhpConfigWatcher()
PhpEnv.shared.delegate?.switcherDidCompleteSwitch(to: version)
}
}
@ -259,8 +256,6 @@ extension MainMenu {
await MainMenu.shared.switchToPhp("8.1")
// 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 {
Task { @MainActor [self] in
@ -270,19 +265,13 @@ extension MainMenu {
PhpEnv.shared.delegate?.switcherDidStartSwitching(to: version)
}
return await withCheckedContinuation({ continuation in
updatePhpVersionInStatusBar()
rebuild()
PhpEnv.switcher.performSwitch(
to: version,
completion: {
PhpEnv.shared.currentInstall = ActivePhpInstallation()
App.shared.handlePhpConfigWatcher()
PhpEnv.shared.delegate?.switcherDidCompleteSwitch(to: version)
continuation.resume()
}
)
})
updatePhpVersionInStatusBar()
rebuild()
await PhpEnv.switcher.performSwitch(to: version)
PhpEnv.shared.currentInstall = ActivePhpInstallation()
App.shared.handlePhpConfigWatcher()
PhpEnv.shared.delegate?.switcherDidCompleteSwitch(to: version)
}
}

View File

@ -31,13 +31,13 @@ extension MainMenu {
return
}
Actions.fixMyValet {
Task { @MainActor in
if previousVersion == PhpEnv.brewPhpAlias {
self.presentAlertForSameVersion()
} else {
self.presentAlertForDifferentVersion(version: previousVersion)
}
Task { @MainActor in
await Actions.fixMyValet()
if previousVersion == PhpEnv.brewPhpAlias {
self.presentAlertForSameVersion()
} else {
self.presentAlertForDifferentVersion(version: previousVersion)
}
}
}

View File

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