diff --git a/PHP Monitor.xcodeproj/project.pbxproj b/PHP Monitor.xcodeproj/project.pbxproj index 48cfe50..695fdcb 100644 --- a/PHP Monitor.xcodeproj/project.pbxproj +++ b/PHP Monitor.xcodeproj/project.pbxproj @@ -85,6 +85,8 @@ C44CCD4527AFE94900CE40E5 /* HomebrewPermissionError.swift in Sources */ = {isa = PBXBuildFile; fileRef = C44CCD4427AFE94900CE40E5 /* HomebrewPermissionError.swift */; }; C44CCD4627AFE94900CE40E5 /* HomebrewPermissionError.swift in Sources */ = {isa = PBXBuildFile; fileRef = C44CCD4427AFE94900CE40E5 /* HomebrewPermissionError.swift */; }; C44CCD4727AFE94900CE40E5 /* HomebrewPermissionError.swift in Sources */ = {isa = PBXBuildFile; fileRef = C44CCD4427AFE94900CE40E5 /* HomebrewPermissionError.swift */; }; + C44CCD4927AFF3B700CE40E5 /* MainMenu+Async.swift in Sources */ = {isa = PBXBuildFile; fileRef = C44CCD4827AFF3B700CE40E5 /* MainMenu+Async.swift */; }; + C44CCD4A27AFF3BC00CE40E5 /* MainMenu+Async.swift in Sources */ = {isa = PBXBuildFile; fileRef = C44CCD4827AFF3B700CE40E5 /* MainMenu+Async.swift */; }; C464ADAC275A7A3F003FCD53 /* SiteListWC.swift in Sources */ = {isa = PBXBuildFile; fileRef = C464ADAB275A7A3F003FCD53 /* SiteListWC.swift */; }; C464ADAD275A7A3F003FCD53 /* SiteListWC.swift in Sources */ = {isa = PBXBuildFile; fileRef = C464ADAB275A7A3F003FCD53 /* SiteListWC.swift */; }; C464ADAF275A7A69003FCD53 /* SiteListVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = C464ADAE275A7A69003FCD53 /* SiteListVC.swift */; }; @@ -272,6 +274,7 @@ C44C1990276E44CB0072762D /* ProgressWindow.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = ProgressWindow.storyboard; sourceTree = ""; }; C44CCD3F27AFE2FC00CE40E5 /* AlertableError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlertableError.swift; sourceTree = ""; }; C44CCD4427AFE94900CE40E5 /* HomebrewPermissionError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomebrewPermissionError.swift; sourceTree = ""; }; + C44CCD4827AFF3B700CE40E5 /* MainMenu+Async.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MainMenu+Async.swift"; sourceTree = ""; }; C464ADAB275A7A3F003FCD53 /* SiteListWC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SiteListWC.swift; sourceTree = ""; }; C464ADAE275A7A69003FCD53 /* SiteListVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SiteListVC.swift; sourceTree = ""; }; C464ADB1275A87CA003FCD53 /* SiteListCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SiteListCell.swift; sourceTree = ""; }; @@ -551,6 +554,7 @@ isa = PBXGroup; children = ( C4811D2922D70F9A00B5F6B3 /* MainMenu.swift */, + C44CCD4827AFF3B700CE40E5 /* MainMenu+Async.swift */, C4C3ED402783497000AB15D8 /* MainMenu+Startup.swift */, C47331A1247093B7009A0597 /* StatusMenu.swift */, C48D0C9525CC80B100CC7490 /* HeaderView.swift */, @@ -957,6 +961,7 @@ C42759672627662800093CAE /* NSMenuExtension.swift in Sources */, C464ADAF275A7A69003FCD53 /* SiteListVC.swift in Sources */, C4EC1E6D279DF87A0010F296 /* Async.swift in Sources */, + C44CCD4927AFF3B700CE40E5 /* MainMenu+Async.swift in Sources */, C4EC1E73279DFCF40010F296 /* Events.swift in Sources */, C4B5853E2770FE3900DA4FBE /* Paths.swift in Sources */, C41C1B4B22B019FF00E7CF16 /* ActivePhpInstallation.swift in Sources */, @@ -1053,6 +1058,7 @@ C40B24F127A3106D0018C7D2 /* ServicesView.swift in Sources */, C4F780C525D80B75000DBC97 /* MenuBarImageGenerator.swift in Sources */, C4F780B725D80B5D000DBC97 /* App.swift in Sources */, + C44CCD4A27AFF3BC00CE40E5 /* MainMenu+Async.swift in Sources */, C48D6C71279CD2AC00F26D7E /* PhpVersionNumber.swift in Sources */, C4F780C925D80B75000DBC97 /* StringExtension.swift in Sources */, C4B5853F2770FE3900DA4FBE /* Paths.swift in Sources */, diff --git a/phpmon/Domain/Menu/MainMenu+Async.swift b/phpmon/Domain/Menu/MainMenu+Async.swift new file mode 100644 index 0000000..9933b13 --- /dev/null +++ b/phpmon/Domain/Menu/MainMenu+Async.swift @@ -0,0 +1,92 @@ +// +// MainMenu+Async.swift +// PHP Monitor +// +// Created by Nico Verbruggen on 06/02/2022. +// Copyright © 2022 Nico Verbruggen. All rights reserved. +// + +import Foundation + +extension MainMenu { + + // MARK: - Nicer callbacks + + enum AsyncBehaviour { + case setsBusyUI + case reloadsPhpInstallation + case updatesMenuBarContents + case broadcastServicesUpdate + } + + /** + Attempts asynchronous execution of a callback that may throw an Error. + While the callback is being executed, the UI will be marked as busy. + + - Parameter execute: Callback of the work that needs to happen. + - Parameter success: Callback that is fired when all was OK. + - Parameter failure: Callback that is fired when an Error was thrown. + - Parameter behaviours: Various behaviours that can be tweaked, but usually best left to the default. + */ + func asyncExecution( + _ execute: @escaping () throws -> Void, + success: @escaping () -> Void = {}, + failure: @escaping (Error) -> Void = { _ in }, + behaviours: [AsyncBehaviour] = [ + .setsBusyUI, + .reloadsPhpInstallation, + .updatesMenuBarContents, + .broadcastServicesUpdate + ] + ) { + if behaviours.contains(.reloadsPhpInstallation) { + PhpEnv.shared.isBusy = true + } + if behaviours.contains(.setsBusyUI) { + setBusyImage() + } + DispatchQueue.global(qos: .userInitiated).async { [unowned self] in + var error: Error? = nil + + do { try execute() } catch let e { error = e } + + if behaviours.contains(.setsBusyUI) { + PhpEnv.shared.isBusy = false + } + + DispatchQueue.main.async { [self] in + if behaviours.contains(.reloadsPhpInstallation) { + PhpEnv.shared.currentInstall = ActivePhpInstallation() + } + + if behaviours.contains(.updatesMenuBarContents) { + // Refresh the entire menu bar menu's contents + updatePhpVersionInStatusBar() + } else { + // We do still need to refresh the icon based on the busy state + if behaviours.contains(.setsBusyUI) { + refreshIcon() + } + } + + if behaviours.contains(.broadcastServicesUpdate) { + NotificationCenter.default.post(name: Events.ServicesUpdated, object: nil) + } + + error == nil ? success() : failure(error!) + } + } + } + + func asyncWithBusyUI( + _ execute: @escaping () throws -> Void, + completion: @escaping () -> Void = {} + ) { + asyncExecution({ + try! execute() + }, success: { + completion() + }, behaviours: [.setsBusyUI]) + } + +} diff --git a/phpmon/Domain/Menu/MainMenu.swift b/phpmon/Domain/Menu/MainMenu.swift index d6ef42d..8125eb1 100644 --- a/phpmon/Domain/Menu/MainMenu.swift +++ b/phpmon/Domain/Menu/MainMenu.swift @@ -85,85 +85,6 @@ class MainMenu: NSObject, NSWindowDelegate, NSMenuDelegate { } } - // MARK: - Nicer callbacks - - enum AsyncBehaviour { - case setsBusyUI - case reloadsPhpInstallation - case updatesMenuBarContents - case broadcastServicesUpdate - } - - /** - Attempts asynchronous execution of a callback that may throw an Error. - While the callback is being executed, the UI will be marked as busy. - - - Parameter execute: Callback of the work that needs to happen. - - Parameter success: Callback that is fired when all was OK. - - Parameter failure: Callback that is fired when an Error was thrown. - - Parameter behaviours: Various behaviours that can be tweaked, but usually best left to the default. - */ - private func asyncExecution( - _ execute: @escaping () throws -> Void, - success: @escaping () -> Void = {}, - failure: @escaping (Error) -> Void = { _ in }, - behaviours: [AsyncBehaviour] = [ - .setsBusyUI, - .reloadsPhpInstallation, - .updatesMenuBarContents, - .broadcastServicesUpdate - ] - ) { - if behaviours.contains(.reloadsPhpInstallation) { - PhpEnv.shared.isBusy = true - } - if behaviours.contains(.setsBusyUI) { - setBusyImage() - } - DispatchQueue.global(qos: .userInitiated).async { [unowned self] in - var error: Error? = nil - - do { try execute() } catch let e { error = e } - - if behaviours.contains(.setsBusyUI) { - PhpEnv.shared.isBusy = false - } - - DispatchQueue.main.async { [self] in - if behaviours.contains(.reloadsPhpInstallation) { - PhpEnv.shared.currentInstall = ActivePhpInstallation() - } - - if behaviours.contains(.updatesMenuBarContents) { - // Refresh the entire menu bar menu's contents - updatePhpVersionInStatusBar() - } else { - // We do still need to refresh the icon based on the busy state - if behaviours.contains(.setsBusyUI) { - refreshIcon() - } - } - - if behaviours.contains(.broadcastServicesUpdate) { - NotificationCenter.default.post(name: Events.ServicesUpdated, object: nil) - } - - error == nil ? success() : failure(error!) - } - } - } - - public func asyncWithBusyUI( - _ execute: @escaping () throws -> Void, - completion: @escaping () -> Void = {} - ) { - asyncExecution({ - try! execute() - }, success: { - completion() - }, behaviours: [.setsBusyUI]) - } - // MARK: - User Interface @objc func refreshActiveInstallation() {