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:
@ -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 */,
|
||||
|
@ -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)")
|
||||
|
@ -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.
|
||||
*/
|
||||
|
@ -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
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
||||
|
@ -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)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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"#
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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")
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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
|
||||
|
@ -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),
|
||||
|
@ -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() {
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user