From b5c196026072716c2710c737cd308b80b39ada9e Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Sat, 12 Feb 2022 14:47:29 +0100 Subject: [PATCH] =?UTF-8?q?=F0=9F=91=8C=20Async=20/=20await=20support=20fo?= =?UTF-8?q?r=20loading=20services?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- PHP Monitor.xcodeproj/project.pbxproj | 6 ---- phpmon/Common/Core/Helpers.swift | 2 +- phpmon/Common/Core/Shell.swift | 10 ------- phpmon/Common/Helpers/Alert.swift | 8 +++++ phpmon/Common/Helpers/Async.swift | 29 ------------------- phpmon/Common/PHP/ActivePhpInstallation.swift | 2 +- .../Common/PHP/Homebrew/HomebrewService.swift | 14 +++++++++ phpmon/Common/PHP/PHP Version/PhpEnv.swift | 4 +-- .../PHP/PHP Version/PhpVersionNumber.swift | 2 +- phpmon/Common/PHP/PhpInstallation.swift | 3 +- phpmon/Domain/App/Startup.swift | 17 +++++++---- phpmon/Domain/Integrations/Valet/Valet.swift | 2 +- phpmon/Domain/Menu/MainMenu+Composer.swift | 2 +- phpmon/Domain/Menu/MainMenu+Switcher.swift | 2 +- phpmon/Domain/Menu/MainMenu.swift | 4 ++- phpmon/Domain/Menu/ServicesView.swift | 20 ++----------- 16 files changed, 48 insertions(+), 79 deletions(-) delete mode 100644 phpmon/Common/Helpers/Async.swift diff --git a/PHP Monitor.xcodeproj/project.pbxproj b/PHP Monitor.xcodeproj/project.pbxproj index c9e5428..ad7275f 100644 --- a/PHP Monitor.xcodeproj/project.pbxproj +++ b/PHP Monitor.xcodeproj/project.pbxproj @@ -31,7 +31,6 @@ C40B24F127A3106D0018C7D2 /* ServicesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4EC1E67279DE0540010F296 /* ServicesView.swift */; }; C40B24F227A310770018C7D2 /* Events.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4EC1E72279DFCF40010F296 /* Events.swift */; }; C40B24F427A310830018C7D2 /* StatusMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = C47331A1247093B7009A0597 /* StatusMenu.swift */; }; - C40B24F527A3108B0018C7D2 /* Async.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4EC1E6C279DF87A0010F296 /* Async.swift */; }; C40C7F1E2772136000DDDCDC /* PhpEnv.swift in Sources */ = {isa = PBXBuildFile; fileRef = C40C7F1D2772136000DDDCDC /* PhpEnv.swift */; }; C40C7F1F2772136000DDDCDC /* PhpEnv.swift in Sources */ = {isa = PBXBuildFile; fileRef = C40C7F1D2772136000DDDCDC /* PhpEnv.swift */; }; C40C7F2827721FF600DDDCDC /* ActivePhpInstallation+Checks.swift in Sources */ = {isa = PBXBuildFile; fileRef = C40C7F2727721FF600DDDCDC /* ActivePhpInstallation+Checks.swift */; }; @@ -149,7 +148,6 @@ C4DEB7D427A5D60B00834718 /* Stats.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4DEB7D327A5D60B00834718 /* Stats.swift */; }; C4EC1E66279DE0380010F296 /* ServicesView.xib in Resources */ = {isa = PBXBuildFile; fileRef = C4EC1E65279DE0380010F296 /* ServicesView.xib */; }; C4EC1E68279DE0540010F296 /* ServicesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4EC1E67279DE0540010F296 /* ServicesView.swift */; }; - C4EC1E6D279DF87A0010F296 /* Async.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4EC1E6C279DF87A0010F296 /* Async.swift */; }; C4EC1E73279DFCF40010F296 /* Events.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4EC1E72279DFCF40010F296 /* Events.swift */; }; C4EE188422D3386B00E126E5 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4EE188322D3386B00E126E5 /* Constants.swift */; }; C4EE55A927708B9E001DF387 /* PMHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4EE55A627708B9E001DF387 /* PMHeaderView.swift */; }; @@ -298,7 +296,6 @@ C4E713572570151400007428 /* docs */ = {isa = PBXFileReference; lastKnownFileType = folder; path = docs; sourceTree = ""; }; C4EC1E65279DE0380010F296 /* ServicesView.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = ServicesView.xib; sourceTree = ""; }; C4EC1E67279DE0540010F296 /* ServicesView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ServicesView.swift; sourceTree = ""; }; - C4EC1E6C279DF87A0010F296 /* Async.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Async.swift; sourceTree = ""; }; C4EC1E72279DFCF40010F296 /* Events.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Events.swift; sourceTree = ""; }; C4EE188322D3386B00E126E5 /* Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = ""; }; C4EE55A627708B9E001DF387 /* PMHeaderView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PMHeaderView.swift; sourceTree = ""; }; @@ -536,7 +533,6 @@ C41C1B4822B00A9800E7CF16 /* MenuBarImageGenerator.swift */, C4CCBA6B275C567B008C7055 /* PMWindowController.swift */, C4B5635D276AB09000F12CCB /* VersionExtractor.swift */, - C4EC1E6C279DF87A0010F296 /* Async.swift */, ); path = Helpers; sourceTree = ""; @@ -873,7 +869,6 @@ C41C1B3722B0097F00E7CF16 /* AppDelegate.swift in Sources */, 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 */, C4927F0B27B2DFC200C55AFD /* Errors.swift in Sources */, @@ -931,7 +926,6 @@ C4AF9F7B2754499000D44ED0 /* Valet.swift in Sources */, C4F780C025D80B6E000DBC97 /* Startup.swift in Sources */, C4CCBA6D275C567B008C7055 /* PMWindowController.swift in Sources */, - C40B24F527A3108B0018C7D2 /* Async.swift in Sources */, C4B5635F276AB09000F12CCB /* VersionExtractor.swift in Sources */, C4F2E4382752F08D0020E974 /* HomebrewDiagnostics.swift in Sources */, C4F780AE25D80B37000DBC97 /* ExtensionParserTest.swift in Sources */, diff --git a/phpmon/Common/Core/Helpers.swift b/phpmon/Common/Core/Helpers.swift index a6a4560..da1f439 100644 --- a/phpmon/Common/Core/Helpers.swift +++ b/phpmon/Common/Core/Helpers.swift @@ -35,7 +35,7 @@ func sed(file: String, original: String, replacement: String) // Check if gsed exists; it is able to follow symlinks, // which we want to do to toggle the extension - if Shell.fileExists("\(Paths.binPath)/gsed") { + if Filesystem.fileExists("\(Paths.binPath)/gsed") { Shell.run("\(Paths.binPath)/gsed -i --follow-symlinks 's/\(e_original)/\(e_replacement)/g' \(file)") } else { Shell.run("sed -i '' 's/\(e_original)/\(e_replacement)/g' \(file)") diff --git a/phpmon/Common/Core/Shell.swift b/phpmon/Common/Core/Shell.swift index 49529f6..5f7c89c 100644 --- a/phpmon/Common/Core/Shell.swift +++ b/phpmon/Common/Core/Shell.swift @@ -104,16 +104,6 @@ public class Shell { ) } - /** - Checks if a file exists at a certain path. - Used to be done with a shell command, now uses the native FileManager class instead. - */ - // TODO: To be moved - public static func fileExists(_ path: String) -> Bool { - let fullPath = path.replacingOccurrences(of: "~", with: "/Users/\(Paths.whoami)") - return FileManager.default.fileExists(atPath: fullPath) - } - /** Creates a new process with the correct PATH and shell. */ diff --git a/phpmon/Common/Helpers/Alert.swift b/phpmon/Common/Helpers/Alert.swift index 245d798..5a15af0 100644 --- a/phpmon/Common/Helpers/Alert.swift +++ b/phpmon/Common/Helpers/Alert.swift @@ -16,6 +16,10 @@ class Alert { secondButtonTitle: String = "", style: NSAlert.Style = .informational ) -> Bool { + if !Thread.isMainThread { + fatalError("You should always present alerts on the main thread!") + } + let alert = NSAlert.init() alert.alertStyle = style alert.messageText = messageText @@ -36,6 +40,10 @@ class Alert { style: NSAlert.Style = .warning, onFirstButtonPressed: @escaping (() -> Void) ) { + if !Thread.isMainThread { + fatalError("You should always present alerts on the main thread!") + } + let alert = NSAlert.init() alert.alertStyle = style alert.messageText = messageText diff --git a/phpmon/Common/Helpers/Async.swift b/phpmon/Common/Helpers/Async.swift deleted file mode 100644 index 515c805..0000000 --- a/phpmon/Common/Helpers/Async.swift +++ /dev/null @@ -1,29 +0,0 @@ -// -// Async.swift -// PHP Monitor -// -// Created by Nico Verbruggen on 23/01/2022. -// Copyright © 2022 Nico Verbruggen. All rights reserved. -// - -import Foundation - -/** - This generic async helper is something I'd like to use in more places. - - The `DispatchQueue.global` into `DispatchQueue.main.async` logic is common in the app. - - I could also use try `async` support which was introduced in Swift but that would - require too much refactoring at this time to consider. I also need to read up on async - in order to properly grasp all the gotchas. Looking into that later at some point. - */ -public func runAsync(_ execute: @escaping () -> Void, completion: @escaping () -> Void = {}) -{ - DispatchQueue.global(qos: .userInitiated).async { - execute() - - DispatchQueue.main.async { - completion() - } - } -} diff --git a/phpmon/Common/PHP/ActivePhpInstallation.swift b/phpmon/Common/PHP/ActivePhpInstallation.swift index f4b124e..6ea858f 100644 --- a/phpmon/Common/PHP/ActivePhpInstallation.swift +++ b/phpmon/Common/PHP/ActivePhpInstallation.swift @@ -137,7 +137,7 @@ class ActivePhpInstallation { } // Make sure to check if valet-fpm.conf exists. If it does, we should be fine :) - return Shell.fileExists("\(Paths.etcPath)/php/\(self.version.short)/php-fpm.d/valet-fpm.conf") + return Filesystem.fileExists("\(Paths.etcPath)/php/\(self.version.short)/php-fpm.d/valet-fpm.conf") } // MARK: - Structs diff --git a/phpmon/Common/PHP/Homebrew/HomebrewService.swift b/phpmon/Common/PHP/Homebrew/HomebrewService.swift index 6c21af4..75c1d84 100644 --- a/phpmon/Common/PHP/Homebrew/HomebrewService.swift +++ b/phpmon/Common/PHP/Homebrew/HomebrewService.swift @@ -18,4 +18,18 @@ struct HomebrewService: Decodable, Equatable { let status: String? let log_path: String? let error_log_path: String? + + public static func loadAll( + filter: [String] = [PhpEnv.phpInstall.formula, "nginx", "dnsmasq"] + ) async -> [HomebrewService] { + return try! JSONDecoder().decode( + [HomebrewService].self, + from: Shell.pipe( + "sudo \(Paths.brew) services info --all --json", + requiresPath: true + ).data(using: .utf8)! + ).filter({ service in + return filter.contains(service.name) + }) + } } diff --git a/phpmon/Common/PHP/PHP Version/PhpEnv.swift b/phpmon/Common/PHP/PHP Version/PhpEnv.swift index 04230e2..571b762 100644 --- a/phpmon/Common/PHP/PHP Version/PhpEnv.swift +++ b/phpmon/Common/PHP/PHP Version/PhpEnv.swift @@ -95,7 +95,7 @@ class PhpEnv { let phpAlias = homebrewPackage.version // Avoid inserting a duplicate - if (!versionsOnly.contains(phpAlias) && Shell.fileExists("\(Paths.optPath)/php/bin/php")) { + if (!versionsOnly.contains(phpAlias) && Filesystem.fileExists("\(Paths.optPath)/php/bin/php")) { versionsOnly.append(phpAlias) } @@ -134,7 +134,7 @@ class PhpEnv { // is supported and where the binary exists (avoids broken installs) if !output.contains(version) && Constants.SupportedPhpVersions.contains(version) - && (checkBinaries ? Shell.fileExists("\(Paths.optPath)/php@\(version)/bin/php") : true) + && (checkBinaries ? Filesystem.fileExists("\(Paths.optPath)/php@\(version)/bin/php") : true) { output.append(version) } diff --git a/phpmon/Common/PHP/PHP Version/PhpVersionNumber.swift b/phpmon/Common/PHP/PHP Version/PhpVersionNumber.swift index 5f4c352..301aae9 100644 --- a/phpmon/Common/PHP/PHP Version/PhpVersionNumber.swift +++ b/phpmon/Common/PHP/PHP Version/PhpVersionNumber.swift @@ -111,7 +111,7 @@ public struct PhpVersionNumber: Equatable { case greaterThanOrEqual = #"^>=(?\d+).(?\d+).?(?\d+)?\z"# case greaterThan = #"^>(?\d+).(?\d+).?(?\d+)?\z"# - // TODO: (5.1) Handle these cases (even though I suspect these are uncommon) + // TODO: (6.0) Handle these cases (even though I suspect these are uncommon) /* case smallerThanOrEqual = #"^<=(?\d+).(?\d+).?(?\d+)?\z"# case smallerThan = #"^<(?\d+).(?\d+).?(?\d+)?\z"# diff --git a/phpmon/Common/PHP/PhpInstallation.swift b/phpmon/Common/PHP/PhpInstallation.swift index f36fde1..ba2682e 100644 --- a/phpmon/Common/PHP/PhpInstallation.swift +++ b/phpmon/Common/PHP/PhpInstallation.swift @@ -21,7 +21,7 @@ class PhpInstallation { let phpConfigExecutablePath = "\(Paths.optPath)/php@\(version)/bin/php-config" self.longVersion = PhpVersionNumber.make(from: version)! - if Shell.fileExists(phpConfigExecutablePath) { + if Filesystem.fileExists(phpConfigExecutablePath) { let longVersionString = Command.execute( path: phpConfigExecutablePath, arguments: ["--version"] @@ -29,7 +29,6 @@ class PhpInstallation { // The parser should always work, or the string has to be very unusual. // If so, the app SHOULD crash, so that the users report what's up. - // TODO: Alert the user that the version number could not be parsed. self.longVersion = try! PhpVersionNumber.parse(longVersionString) } } diff --git a/phpmon/Domain/App/Startup.swift b/phpmon/Domain/App/Startup.swift index f4e7eda..f6657b4 100644 --- a/phpmon/Domain/App/Startup.swift +++ b/phpmon/Domain/App/Startup.swift @@ -14,8 +14,8 @@ class Startup { Checks the user's environment and checks if PHP Monitor can be used properly. This checks if PHP is installed, Valet is running, the appropriate permissions are set, and more. - - Parameter success: Callback that is fired if the application can proceed with launch - - Parameter failure: Callback that is fired if the application must retry launch + If this method returns false, there was a failed check and an alert was displayed. + If this method returns true, then all checks succeeded and the app can continue. */ func checkEnvironment() async -> Bool { @@ -41,6 +41,11 @@ class Startup { return true } + /** + Displays an alert for a particular check. There are two types of alerts: + - ones that require an app restart, which prompt the user to exit the app + - ones that allow the app to continue, which allow the user to retry + */ private func showAlert(for check: EnvironmentCheck) { DispatchQueue.main.async { if check.requiresAppRestart { @@ -63,7 +68,7 @@ class Startup { /** Because the Switcher requires various environment guarantees, the switcher is only - initialized when it is done working. + initialized when it is done working. The switcher must be initialized on the main thread. */ private func initializeSwitcher() { DispatchQueue.main.async { @@ -89,7 +94,7 @@ class Startup { requiresAppRestart: true ), EnvironmentCheck( - command: { return !Shell.fileExists(Paths.php) }, + command: { return !Filesystem.fileExists(Paths.php) }, name: "`\(Paths.php)` exists", titleText: "startup.errors.php_binary.title".localized, descriptionText: "startup.errors.php_binary.desc".localized( @@ -106,8 +111,8 @@ class Startup { ), EnvironmentCheck( command: { - return !(Shell.fileExists(Paths.valet) - || Shell.fileExists("~/.composer/vendor/bin/valet")) + return !(Filesystem.fileExists(Paths.valet) + || Filesystem.fileExists("~/.composer/vendor/bin/valet")) }, name: "`valet` binary exists", titleText: "startup.errors.valet_executable.title".localized, diff --git a/phpmon/Domain/Integrations/Valet/Valet.swift b/phpmon/Domain/Integrations/Valet/Valet.swift index 961d0df..e18c6f1 100644 --- a/phpmon/Domain/Integrations/Valet/Valet.swift +++ b/phpmon/Domain/Integrations/Valet/Valet.swift @@ -241,7 +241,7 @@ class Valet { - Note: The file is not validated, only its presence is checked. */ public func determineSecured(_ tld: String) { - secured = Shell.fileExists("~/.config/valet/Certificates/\(self.name!).\(tld).key") + secured = Filesystem.fileExists("~/.config/valet/Certificates/\(self.name!).\(tld).key") } /** diff --git a/phpmon/Domain/Menu/MainMenu+Composer.swift b/phpmon/Domain/Menu/MainMenu+Composer.swift index c24f73c..92cf265 100644 --- a/phpmon/Domain/Menu/MainMenu+Composer.swift +++ b/phpmon/Domain/Menu/MainMenu+Composer.swift @@ -15,7 +15,7 @@ extension MainMenu { This method should probably be broken up into several smaller methods at some point. */ func updateGlobalDependencies(notify: Bool, completion: @escaping (Bool) -> Void) { - if !Shell.fileExists("/usr/local/bin/composer") { + if !Filesystem.fileExists("/usr/local/bin/composer") { Alert.notify( message: "alert.composer_missing.title".localized, info: "alert.composer_missing.info".localized diff --git a/phpmon/Domain/Menu/MainMenu+Switcher.swift b/phpmon/Domain/Menu/MainMenu+Switcher.swift index 85f85b9..57f495c 100644 --- a/phpmon/Domain/Menu/MainMenu+Switcher.swift +++ b/phpmon/Domain/Menu/MainMenu+Switcher.swift @@ -52,7 +52,7 @@ extension MainMenu { } } - private func suggestFixMyValet(failed version: String) { + @MainActor private func suggestFixMyValet(failed version: String) { let outcome = Alert.present( messageText: "alert.php_switch_failed.title".localized(version), informativeText: "alert.php_switch_failed.info".localized(version), diff --git a/phpmon/Domain/Menu/MainMenu.swift b/phpmon/Domain/Menu/MainMenu.swift index ec5d1d3..6dc1300 100644 --- a/phpmon/Domain/Menu/MainMenu.swift +++ b/phpmon/Domain/Menu/MainMenu.swift @@ -180,6 +180,7 @@ class MainMenu: NSObject, NSWindowDelegate, NSMenuDelegate, PhpSwitcherDelegate return } + /* asyncExecution { try Actions.fixHomebrewPermissions() } success: { @@ -189,8 +190,9 @@ class MainMenu: NSObject, NSWindowDelegate, NSMenuDelegate, PhpSwitcherDelegate style: .warning ) } failure: { error in - Alert.notify(about: error as! HomebrewPermissionError) + await Alert.notify(about: error as! HomebrewPermissionError) } + */ } @objc func restartPhpFpm() { diff --git a/phpmon/Domain/Menu/ServicesView.swift b/phpmon/Domain/Menu/ServicesView.swift index f879f4e..a9731bc 100644 --- a/phpmon/Domain/Menu/ServicesView.swift +++ b/phpmon/Domain/Menu/ServicesView.swift @@ -54,26 +54,12 @@ class ServicesView: NSView, XibLoadable { self.loadData() } - // TODO: (5.1) Move data fetching, caching & retrieval somewhere else func loadData() { - // Use stale data self.applyAllInfoFieldsFromCachedValue() - // Re-fetch services - runAsync { - let servicesList = try! JSONDecoder().decode( - [HomebrewService].self, - from: Shell.pipe( - "sudo \(Paths.brew) services info --all --json", - requiresPath: true - ).data(using: .utf8)! - ).filter({ service in - return [PhpEnv.phpInstall.formula, "nginx", "dnsmasq"].contains(service.name) - }) - - ServicesView.services = Dictionary(uniqueKeysWithValues: servicesList.map{ ($0.name, $0) }) - } completion: { - // Use fresh data + Task { + let services = await HomebrewService.loadAll() + ServicesView.services = Dictionary(uniqueKeysWithValues: services.map{ ($0.name, $0) }) self.applyAllInfoFieldsFromCachedValue() } }