1
0
mirror of https://github.com/nicoverbruggen/phpmon.git synced 2025-08-07 20:10:08 +02:00

👌 Async / await support for loading services

This commit is contained in:
2022-02-12 14:47:29 +01:00
parent e6fbe7c4a4
commit b5c1960260
16 changed files with 48 additions and 79 deletions

View File

@ -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 = "<group>"; };
C4EC1E65279DE0380010F296 /* ServicesView.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = ServicesView.xib; sourceTree = "<group>"; };
C4EC1E67279DE0540010F296 /* ServicesView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ServicesView.swift; sourceTree = "<group>"; };
C4EC1E6C279DF87A0010F296 /* Async.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Async.swift; sourceTree = "<group>"; };
C4EC1E72279DFCF40010F296 /* Events.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Events.swift; sourceTree = "<group>"; };
C4EE188322D3386B00E126E5 /* Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = "<group>"; };
C4EE55A627708B9E001DF387 /* PMHeaderView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PMHeaderView.swift; sourceTree = "<group>"; };
@ -536,7 +533,6 @@
C41C1B4822B00A9800E7CF16 /* MenuBarImageGenerator.swift */,
C4CCBA6B275C567B008C7055 /* PMWindowController.swift */,
C4B5635D276AB09000F12CCB /* VersionExtractor.swift */,
C4EC1E6C279DF87A0010F296 /* Async.swift */,
);
path = Helpers;
sourceTree = "<group>";
@ -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 */,

View File

@ -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)")

View File

@ -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.
*/

View File

@ -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

View File

@ -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()
}
}
}

View File

@ -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

View File

@ -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)
})
}
}

View File

@ -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)
}

View File

@ -111,7 +111,7 @@ public struct PhpVersionNumber: Equatable {
case greaterThanOrEqual = #"^>=(?<major>\d+).(?<minor>\d+).?(?<patch>\d+)?\z"#
case greaterThan = #"^>(?<major>\d+).(?<minor>\d+).?(?<patch>\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 = #"^<=(?<major>\d+).(?<minor>\d+).?(?<patch>\d+)?\z"#
case smallerThan = #"^<(?<major>\d+).(?<minor>\d+).?(?<patch>\d+)?\z"#

View File

@ -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)
}
}

View File

@ -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,

View File

@ -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")
}
/**

View File

@ -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

View File

@ -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),

View File

@ -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() {

View File

@ -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()
}
}