mirror of
https://github.com/nicoverbruggen/phpmon.git
synced 2026-04-03 09:50:10 +02:00
✨ Allow for suspension of FSNotifier (for /homebrew/bin)
This commit is contained in:
@@ -10,6 +10,20 @@ import Foundation
|
|||||||
|
|
||||||
extension App {
|
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() {
|
public func prepareHomebrewWatchers() {
|
||||||
let notifier = FSNotifier(
|
let notifier = FSNotifier(
|
||||||
for: URL(fileURLWithPath: container.paths.binPath),
|
for: URL(fileURLWithPath: container.paths.binPath),
|
||||||
@@ -21,6 +35,15 @@ extension App {
|
|||||||
self.debouncers["homebrewBinaries"] = Debouncer()
|
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 {
|
public func onHomebrewPhpModification() async {
|
||||||
if let debouncer = self.debouncers["homebrewBinaries"] {
|
if let debouncer = self.debouncers["homebrewBinaries"] {
|
||||||
await debouncer.debounce(for: 5.0) {
|
await debouncer.debounce(for: 5.0) {
|
||||||
|
|||||||
@@ -22,6 +22,8 @@ class FSNotifier {
|
|||||||
private var fileDescriptor: CInt = -1
|
private var fileDescriptor: CInt = -1
|
||||||
private var dispatchSource: DispatchSourceFileSystemObject?
|
private var dispatchSource: DispatchSourceFileSystemObject?
|
||||||
|
|
||||||
|
private var isSuspended = false
|
||||||
|
|
||||||
// MARK: Methods
|
// MARK: Methods
|
||||||
|
|
||||||
init(for url: URL, eventMask: DispatchSource.FileSystemEvent, onChange: @escaping () -> Void) {
|
init(for url: URL, eventMask: DispatchSource.FileSystemEvent, onChange: @escaping () -> Void) {
|
||||||
@@ -42,6 +44,10 @@ class FSNotifier {
|
|||||||
|
|
||||||
dispatchSource?.setEventHandler(handler: {
|
dispatchSource?.setEventHandler(handler: {
|
||||||
self.queue.async {
|
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() }
|
Task { onChange() }
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -57,6 +63,20 @@ class FSNotifier {
|
|||||||
dispatchSource?.resume()
|
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() {
|
func terminate() {
|
||||||
dispatchSource?.cancel()
|
dispatchSource?.cancel()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,8 +10,19 @@ import Foundation
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
extension PhpVersionManagerView {
|
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 {
|
public func runCommand(_ command: ModifyPhpVersionCommand) async {
|
||||||
if App.shared.container.phpEnvs.isBusy {
|
if container.phpEnvs.isBusy {
|
||||||
self.presentErrorAlert(
|
self.presentErrorAlert(
|
||||||
title: "phpman.action_prevented_busy.title".localized,
|
title: "phpman.action_prevented_busy.title".localized,
|
||||||
description: "phpman.action_prevented_busy.desc".localized,
|
description: "phpman.action_prevented_busy.desc".localized,
|
||||||
@@ -22,7 +33,8 @@ extension PhpVersionManagerView {
|
|||||||
|
|
||||||
do {
|
do {
|
||||||
self.setBusyStatus(true)
|
self.setBusyStatus(true)
|
||||||
try await command.execute(shell: App.shared.container.shell) { progress in
|
try await App.shared.withSuspendedHomebrewWatcher {
|
||||||
|
try await command.execute(shell: container.shell) { progress in
|
||||||
Task { @MainActor in
|
Task { @MainActor in
|
||||||
self.status.title = progress.title
|
self.status.title = progress.title
|
||||||
self.status.description = progress.description
|
self.status.description = progress.description
|
||||||
@@ -36,8 +48,10 @@ extension PhpVersionManagerView {
|
|||||||
}
|
}
|
||||||
// Finally, after completing the command, also refresh PHP versions
|
// Finally, after completing the command, also refresh PHP versions
|
||||||
await self.handler.refreshPhpVersions(loadOutdated: false)
|
await self.handler.refreshPhpVersions(loadOutdated: false)
|
||||||
|
|
||||||
// and mark the app as no longer busy
|
// and mark the app as no longer busy
|
||||||
self.setBusyStatus(false)
|
self.setBusyStatus(false)
|
||||||
|
}
|
||||||
} catch let error {
|
} catch let error {
|
||||||
let error = error as! BrewCommandError
|
let error = error as! BrewCommandError
|
||||||
let messages = error.log.suffix(2).joined(separator: "\n")
|
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 {
|
public func confirmUninstall(_ formula: BrewPhpFormula) async {
|
||||||
// Disallow removal of the currently active versipn
|
// Disallow removal of the currently active version
|
||||||
if formula.installedVersion == App.shared.container.phpEnvs.currentInstall?.version.text {
|
if formula.installedVersion == container.phpEnvs.currentInstall?.version.text {
|
||||||
self.presentErrorAlert(
|
self.presentErrorAlert(
|
||||||
title: "phpman.uninstall_prevented.title".localized,
|
title: "phpman.uninstall_prevented.title".localized,
|
||||||
description: "phpman.uninstall_prevented.desc".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)
|
Present a generic error alert attached to the window.
|
||||||
|
*/
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public func presentErrorAlert(
|
public func presentErrorAlert(
|
||||||
title: String,
|
title: String,
|
||||||
description: String,
|
description: String,
|
||||||
@@ -158,9 +187,4 @@ extension PhpVersionManagerView {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
var hasUpdates: Bool {
|
|
||||||
return self.formulae.phpVersions.contains { formula in
|
|
||||||
return formula.hasUpgrade
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -217,7 +217,9 @@ struct PhpVersionManagerView: View {
|
|||||||
HStack {
|
HStack {
|
||||||
if !formula.healthy {
|
if !formula.healthy {
|
||||||
Button("phpman.buttons.repair".localizedForSwiftUI, role: .destructive) {
|
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 {
|
} else if formula.isInstalled {
|
||||||
Button("phpman.buttons.uninstall".localizedForSwiftUI, role: .destructive) {
|
Button("phpman.buttons.uninstall".localizedForSwiftUI, role: .destructive) {
|
||||||
Task { await self.confirmUninstall(formula) }
|
Task {
|
||||||
|
await self.confirmUninstall(formula)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Button("phpman.buttons.install".localizedForSwiftUI) {
|
Button("phpman.buttons.install".localizedForSwiftUI) {
|
||||||
Task { await self.install(formula) }
|
Task {
|
||||||
|
await self.install(formula)
|
||||||
|
}
|
||||||
}.disabled(formula.hasUpgradedFormulaAlias || !formula.hasFormulaFile)
|
}.disabled(formula.hasUpgradedFormulaAlias || !formula.hasFormulaFile)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user