1
0
mirror of https://github.com/nicoverbruggen/phpmon.git synced 2026-03-27 22:40:08 +01:00

Allow for suspension of FSNotifier (for /homebrew/bin)

This commit is contained in:
2025-11-29 15:07:40 +01:00
parent 4b41704fdf
commit 1898d50a9e
4 changed files with 132 additions and 59 deletions

View File

@@ -10,6 +10,20 @@ import Foundation
extension App {
/**
Performs a particular action while suspending the Homebrew watcher,
until the task is completed.
*/
public func withSuspendedHomebrewWatcher<T>(_ action: () async throws -> T) async rethrows -> T {
await suspendHomebrewWatcher()
defer { resumeHomebrewWatcher() }
return try await action()
}
/**
Prepares the `homebrew/bin` directory watcher. This allows PHP Monitor to quickly respond to
external `brew` changes executed by the user.
*/
public func prepareHomebrewWatchers() {
let notifier = FSNotifier(
for: URL(fileURLWithPath: container.paths.binPath),
@@ -21,6 +35,15 @@ extension App {
self.debouncers["homebrewBinaries"] = Debouncer()
}
private func suspendHomebrewWatcher() async {
watchers["homebrewBinaries"]?.suspend()
await debouncers["homebrewBinaries"]?.cancel()
}
private func resumeHomebrewWatcher() {
watchers["homebrewBinaries"]?.resume()
}
public func onHomebrewPhpModification() async {
if let debouncer = self.debouncers["homebrewBinaries"] {
await debouncer.debounce(for: 5.0) {

View File

@@ -22,6 +22,8 @@ class FSNotifier {
private var fileDescriptor: CInt = -1
private var dispatchSource: DispatchSourceFileSystemObject?
private var isSuspended = false
// MARK: Methods
init(for url: URL, eventMask: DispatchSource.FileSystemEvent, onChange: @escaping () -> Void) {
@@ -42,6 +44,10 @@ class FSNotifier {
dispatchSource?.setEventHandler(handler: {
self.queue.async {
// If our notifier is suspended, don't fire
guard !self.isSuspended else { return }
// If our notifier is not suspended, fire
Task { onChange() }
}
})
@@ -57,6 +63,20 @@ class FSNotifier {
dispatchSource?.resume()
}
func suspend() {
self.queue.async {
self.isSuspended = true
Log.perf("FSNotifier for \(self.url) has been suspended.")
}
}
func resume() {
self.queue.async {
self.isSuspended = false
Log.perf("FSNotifier for \(self.url) has been resumed.")
}
}
func terminate() {
dispatchSource?.cancel()
}

View File

@@ -10,8 +10,19 @@ import Foundation
import SwiftUI
extension PhpVersionManagerView {
// MARK: - Variables
var hasUpdates: Bool {
return self.formulae.phpVersions.contains { formula in
return formula.hasUpgrade
}
}
// MARK: - Executing Homebrew Commands
public func runCommand(_ command: ModifyPhpVersionCommand) async {
if App.shared.container.phpEnvs.isBusy {
if container.phpEnvs.isBusy {
self.presentErrorAlert(
title: "phpman.action_prevented_busy.title".localized,
description: "phpman.action_prevented_busy.desc".localized,
@@ -22,22 +33,25 @@ extension PhpVersionManagerView {
do {
self.setBusyStatus(true)
try await command.execute(shell: App.shared.container.shell) { progress in
Task { @MainActor in
self.status.title = progress.title
self.status.description = progress.description
self.status.busy = progress.value != 1
try await App.shared.withSuspendedHomebrewWatcher {
try await command.execute(shell: container.shell) { progress in
Task { @MainActor in
self.status.title = progress.title
self.status.description = progress.description
self.status.busy = progress.value != 1
// Whenever a key step is finished, refresh the PHP versions
if progress.value == 1 {
await self.handler.refreshPhpVersions(loadOutdated: false)
// Whenever a key step is finished, refresh the PHP versions
if progress.value == 1 {
await self.handler.refreshPhpVersions(loadOutdated: false)
}
}
}
// Finally, after completing the command, also refresh PHP versions
await self.handler.refreshPhpVersions(loadOutdated: false)
// and mark the app as no longer busy
self.setBusyStatus(false)
}
// Finally, after completing the command, also refresh PHP versions
await self.handler.refreshPhpVersions(loadOutdated: false)
// and mark the app as no longer busy
self.setBusyStatus(false)
} catch let error {
let error = error as! BrewCommandError
let messages = error.log.suffix(2).joined(separator: "\n")
@@ -80,9 +94,57 @@ extension PhpVersionManagerView {
))
}
public func uninstall(_ formula: BrewPhpFormula) async {
let command = RemovePhpVersionCommand(container, formula: formula.name)
do {
self.setBusyStatus(true)
try await App.shared.withSuspendedHomebrewWatcher {
try await command.execute(shell: container.shell) { progress in
Task { @MainActor in
self.status.title = progress.title
self.status.description = progress.description
self.status.busy = progress.value != 1
if progress.value == 1 {
await self.handler.refreshPhpVersions(loadOutdated: false)
self.setBusyStatus(false)
}
}
}
}
} catch {
self.setBusyStatus(false)
await self.handler.refreshPhpVersions(loadOutdated: false)
self.presentErrorAlert(
title: "phpman.failures.uninstall.title".localized,
description: "phpman.failures.uninstall.desc".localized(
"brew uninstall \(formula.name) --force"
),
button: "generic.ok".localized
)
}
}
// MARK: GUI
/**
Mark the PHP Version Manager, as well as the PHP environments as busy.
*/
public func setBusyStatus(_ busy: Bool) {
Task { @MainActor in
container.phpEnvs.isBusy = busy
self.status.busy = busy
}
}
/**
Ask the user to confirm the uninstall of a particular PHP version.
*/
public func confirmUninstall(_ formula: BrewPhpFormula) async {
// Disallow removal of the currently active versipn
if formula.installedVersion == App.shared.container.phpEnvs.currentInstall?.version.text {
// Disallow removal of the currently active version
if formula.installedVersion == container.phpEnvs.currentInstall?.version.text {
self.presentErrorAlert(
title: "phpman.uninstall_prevented.title".localized,
description: "phpman.uninstall_prevented.desc".localized,
@@ -105,42 +167,9 @@ extension PhpVersionManagerView {
)
}
public func uninstall(_ formula: BrewPhpFormula) async {
let command = RemovePhpVersionCommand(container, formula: formula.name)
do {
self.setBusyStatus(true)
try await command.execute(shell: App.shared.container.shell) { progress in
Task { @MainActor in
self.status.title = progress.title
self.status.description = progress.description
self.status.busy = progress.value != 1
if progress.value == 1 {
await self.handler.refreshPhpVersions(loadOutdated: false)
self.setBusyStatus(false)
}
}
}
} catch {
self.setBusyStatus(false)
self.presentErrorAlert(
title: "phpman.failures.uninstall.title".localized,
description: "phpman.failures.uninstall.desc".localized(
"brew uninstall \(formula.name) --force"
),
button: "generic.ok".localized
)
}
}
public func setBusyStatus(_ busy: Bool) {
Task { @MainActor in
App.shared.container.phpEnvs.isBusy = busy
self.status.busy = busy
}
}
/**
Present a generic error alert attached to the window.
*/
public func presentErrorAlert(
title: String,
description: String,
@@ -158,9 +187,4 @@ extension PhpVersionManagerView {
)
}
var hasUpdates: Bool {
return self.formulae.phpVersions.contains { formula in
return formula.hasUpgrade
}
}
}

View File

@@ -217,7 +217,9 @@ struct PhpVersionManagerView: View {
HStack {
if !formula.healthy {
Button("phpman.buttons.repair".localizedForSwiftUI, role: .destructive) {
Task { await self.repairAll() }
Task {
await self.repairAll()
}
}
}
@@ -227,11 +229,15 @@ struct PhpVersionManagerView: View {
})
} else if formula.isInstalled {
Button("phpman.buttons.uninstall".localizedForSwiftUI, role: .destructive) {
Task { await self.confirmUninstall(formula) }
Task {
await self.confirmUninstall(formula)
}
}
} else {
Button("phpman.buttons.install".localizedForSwiftUI) {
Task { await self.install(formula) }
Task {
await self.install(formula)
}
}.disabled(formula.hasUpgradedFormulaAlias || !formula.hasFormulaFile)
}
}