mirror of
https://github.com/nicoverbruggen/phpmon.git
synced 2025-08-07 03:50:08 +02:00
🏗 Remove LegacyShell entirely
This commit is contained in:
@ -103,8 +103,6 @@
|
||||
C42CFB1627DFDE7900862737 /* nginx-site.test in Resources */ = {isa = PBXBuildFile; fileRef = C42CFB1527DFDE7900862737 /* nginx-site.test */; };
|
||||
C42CFB1827DFDFDC00862737 /* nginx-site-isolated.test in Resources */ = {isa = PBXBuildFile; fileRef = C42CFB1727DFDFDC00862737 /* nginx-site-isolated.test */; };
|
||||
C42CFB1A27DFE8BD00862737 /* NginxConfigurationTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = C42CFB1927DFE8BD00862737 /* NginxConfigurationTest.swift */; };
|
||||
C42E3BF428A9BF5100AFECFC /* LegacyShell+PATH.swift in Sources */ = {isa = PBXBuildFile; fileRef = C42E3BF328A9BF5100AFECFC /* LegacyShell+PATH.swift */; };
|
||||
C42E3BF528A9BF5100AFECFC /* LegacyShell+PATH.swift in Sources */ = {isa = PBXBuildFile; fileRef = C42E3BF328A9BF5100AFECFC /* LegacyShell+PATH.swift */; };
|
||||
C42F26732805B4B400938AC7 /* DomainListable.swift in Sources */ = {isa = PBXBuildFile; fileRef = C42F26722805B4B400938AC7 /* DomainListable.swift */; };
|
||||
C42F26742805B4B400938AC7 /* DomainListable.swift in Sources */ = {isa = PBXBuildFile; fileRef = C42F26722805B4B400938AC7 /* DomainListable.swift */; };
|
||||
C42F26762805FEE200938AC7 /* nginx-secure-proxy.test in Resources */ = {isa = PBXBuildFile; fileRef = C42F26752805FEE200938AC7 /* nginx-secure-proxy.test */; };
|
||||
@ -224,8 +222,6 @@
|
||||
C4B56362276AB0A500F12CCB /* VersionExtractorTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4B56360276AB0A500F12CCB /* VersionExtractorTest.swift */; };
|
||||
C4B5853E2770FE3900DA4FBE /* Paths.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4B5853B2770FE3900DA4FBE /* Paths.swift */; };
|
||||
C4B5853F2770FE3900DA4FBE /* Paths.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4B5853B2770FE3900DA4FBE /* Paths.swift */; };
|
||||
C4B585412770FE3900DA4FBE /* LegacyShell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4B5853C2770FE3900DA4FBE /* LegacyShell.swift */; };
|
||||
C4B585422770FE3900DA4FBE /* LegacyShell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4B5853C2770FE3900DA4FBE /* LegacyShell.swift */; };
|
||||
C4B585442770FE3900DA4FBE /* Command.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4B5853D2770FE3900DA4FBE /* Command.swift */; };
|
||||
C4B585452770FE3900DA4FBE /* Command.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4B5853D2770FE3900DA4FBE /* Command.swift */; };
|
||||
C4B6091A2853AAD300C95265 /* SectionHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4B609192853AAD300C95265 /* SectionHeaderView.swift */; };
|
||||
@ -406,7 +402,6 @@
|
||||
C42CFB1527DFDE7900862737 /* nginx-site.test */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "nginx-site.test"; sourceTree = "<group>"; };
|
||||
C42CFB1727DFDFDC00862737 /* nginx-site-isolated.test */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "nginx-site-isolated.test"; sourceTree = "<group>"; };
|
||||
C42CFB1927DFE8BD00862737 /* NginxConfigurationTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NginxConfigurationTest.swift; sourceTree = "<group>"; };
|
||||
C42E3BF328A9BF5100AFECFC /* LegacyShell+PATH.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "LegacyShell+PATH.swift"; sourceTree = "<group>"; };
|
||||
C42F26722805B4B400938AC7 /* DomainListable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DomainListable.swift; sourceTree = "<group>"; };
|
||||
C42F26752805FEE200938AC7 /* nginx-secure-proxy.test */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "nginx-secure-proxy.test"; sourceTree = "<group>"; };
|
||||
C436039F275E67610028EFC6 /* AppDelegate+Notifications.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AppDelegate+Notifications.swift"; sourceTree = "<group>"; };
|
||||
@ -470,7 +465,6 @@
|
||||
C4B5635D276AB09000F12CCB /* VersionExtractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VersionExtractor.swift; sourceTree = "<group>"; };
|
||||
C4B56360276AB0A500F12CCB /* VersionExtractorTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VersionExtractorTest.swift; sourceTree = "<group>"; };
|
||||
C4B5853B2770FE3900DA4FBE /* Paths.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Paths.swift; sourceTree = "<group>"; };
|
||||
C4B5853C2770FE3900DA4FBE /* LegacyShell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LegacyShell.swift; sourceTree = "<group>"; };
|
||||
C4B5853D2770FE3900DA4FBE /* Command.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Command.swift; sourceTree = "<group>"; };
|
||||
C4B609192853AAD300C95265 /* SectionHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SectionHeaderView.swift; sourceTree = "<group>"; };
|
||||
C4B6091C2853AB9700C95265 /* ServicesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServicesView.swift; sourceTree = "<group>"; };
|
||||
@ -897,15 +891,6 @@
|
||||
path = Next;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
C46EBC4C28DB9F43007ACC74 /* Pending Removal */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
C4B5853C2770FE3900DA4FBE /* LegacyShell.swift */,
|
||||
C42E3BF328A9BF5100AFECFC /* LegacyShell+PATH.swift */,
|
||||
);
|
||||
path = "Pending Removal";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
C47331A0247093AC009A0597 /* Menu */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@ -1007,7 +992,6 @@
|
||||
C4B5853A2770FE2500DA4FBE /* Common */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
C46EBC4C28DB9F43007ACC74 /* Pending Removal */,
|
||||
C40C7F2127721F7300DDDCDC /* Core */,
|
||||
54B20EDF263AA22C00D3250E /* PHP */,
|
||||
C44CCD4327AFE93300CE40E5 /* Errors */,
|
||||
@ -1383,7 +1367,6 @@
|
||||
C4D8016622B1584700C6DA1B /* Startup.swift in Sources */,
|
||||
C42C49DB27C2806F0074ABAC /* MainMenu+FixMyValet.swift in Sources */,
|
||||
C48D6C70279CD2AC00F26D7E /* PhpVersionNumber.swift in Sources */,
|
||||
C4B585412770FE3900DA4FBE /* LegacyShell.swift in Sources */,
|
||||
C4998F0A2617633900B2526E /* PreferencesWindowController.swift in Sources */,
|
||||
C46FA9882822EFDC00D78807 /* PhpConfigurationFile.swift in Sources */,
|
||||
C4F8C0A422D4F12C002EFE61 /* DateExtension.swift in Sources */,
|
||||
@ -1509,7 +1492,6 @@
|
||||
C40508AF28ADA23D008FAC1F /* NoDomainResultsView.swift in Sources */,
|
||||
C4D89BC62783C99400A02B68 /* ComposerJson.swift in Sources */,
|
||||
C46FA23F246C358E00944F05 /* StringExtension.swift in Sources */,
|
||||
C42E3BF428A9BF5100AFECFC /* LegacyShell+PATH.swift in Sources */,
|
||||
C42337A3281F19F000459A48 /* Xdebug.swift in Sources */,
|
||||
C4B97B75275CF08C003F3378 /* AppDelegate+MenuOutlets.swift in Sources */,
|
||||
C41C02A627E60D7A009F26CB /* SiteScanner.swift in Sources */,
|
||||
@ -1563,7 +1545,6 @@
|
||||
C4FBFC532616485F00CDB8E1 /* PhpVersionDetectionTest.swift in Sources */,
|
||||
C43A8A2425D9D20D00591B77 /* HomebrewPackageTest.swift in Sources */,
|
||||
C485707928BF456C00539B36 /* ArrayExtension.swift in Sources */,
|
||||
C42E3BF528A9BF5100AFECFC /* LegacyShell+PATH.swift in Sources */,
|
||||
C4F780CA25D80B75000DBC97 /* HomebrewPackage.swift in Sources */,
|
||||
C4C8E81C276F54E5003AC782 /* PhpConfigWatcher.swift in Sources */,
|
||||
C4F319C927B034A500AFF46F /* Stats.swift in Sources */,
|
||||
@ -1668,7 +1649,6 @@
|
||||
C4A6957728D23EE300A14CF8 /* EnvironmentManager.swift in Sources */,
|
||||
C4E0F7EE27BEBDA9007475F2 /* NSWindowExtension.swift in Sources */,
|
||||
C4A81CA528C67101008DD9D1 /* PMTableView.swift in Sources */,
|
||||
C4B585422770FE3900DA4FBE /* LegacyShell.swift in Sources */,
|
||||
C45E76152854A65300B4FE0C /* ServicesManager.swift in Sources */,
|
||||
C464ADAD275A7A3F003FCD53 /* DomainListWindowController.swift in Sources */,
|
||||
C40C7F1F2772136000DDDCDC /* PhpEnv.swift in Sources */,
|
||||
|
@ -12,22 +12,22 @@ class Actions {
|
||||
|
||||
// MARK: - Services
|
||||
|
||||
public static func restartPhpFpm() {
|
||||
brew("services restart \(PhpEnv.phpInstall.formula)", sudo: true)
|
||||
public static func restartPhpFpm() async {
|
||||
await brew("services restart \(PhpEnv.phpInstall.formula)", sudo: true)
|
||||
}
|
||||
|
||||
public static func restartNginx() {
|
||||
brew("services restart nginx", sudo: true)
|
||||
public static func restartNginx() async {
|
||||
await brew("services restart nginx", sudo: true)
|
||||
}
|
||||
|
||||
public static func restartDnsMasq() {
|
||||
brew("services restart dnsmasq", sudo: true)
|
||||
public static func restartDnsMasq() async {
|
||||
await brew("services restart dnsmasq", sudo: true)
|
||||
}
|
||||
|
||||
public static func stopValetServices() {
|
||||
brew("services stop \(PhpEnv.phpInstall.formula)", sudo: true)
|
||||
brew("services stop nginx", sudo: true)
|
||||
brew("services stop dnsmasq", sudo: true)
|
||||
public static func stopValetServices() async {
|
||||
await brew("services stop \(PhpEnv.phpInstall.formula)", sudo: true)
|
||||
await brew("services stop nginx", sudo: true)
|
||||
await brew("services stop dnsmasq", sudo: true)
|
||||
}
|
||||
|
||||
public static func fixHomebrewPermissions() throws {
|
||||
@ -65,26 +65,20 @@ class Actions {
|
||||
}
|
||||
|
||||
// MARK: - Third Party Services
|
||||
public static func stopService(name: String, completion: @escaping () -> Void) {
|
||||
DispatchQueue.global(qos: .userInitiated).async {
|
||||
brew("services stop \(name)", sudo: ServicesManager.shared.rootServices.contains { $0.value.name == name })
|
||||
ServicesManager.loadHomebrewServices(completed: {
|
||||
DispatchQueue.main.async {
|
||||
completion()
|
||||
}
|
||||
})
|
||||
}
|
||||
public static func stopService(name: String) async {
|
||||
await brew(
|
||||
"services stop \(name)",
|
||||
sudo: ServicesManager.shared.rootServices.contains { $0.value.name == name }
|
||||
)
|
||||
await ServicesManager.loadHomebrewServices()
|
||||
}
|
||||
|
||||
public static func startService(name: String, completion: @escaping () -> Void) {
|
||||
DispatchQueue.global(qos: .userInitiated).async {
|
||||
brew("services start \(name)", sudo: ServicesManager.shared.rootServices.contains { $0.value.name == name })
|
||||
ServicesManager.loadHomebrewServices(completed: {
|
||||
DispatchQueue.main.async {
|
||||
completion()
|
||||
}
|
||||
})
|
||||
}
|
||||
public static func startService(name: String) async {
|
||||
await brew(
|
||||
"services start \(name)",
|
||||
sudo: ServicesManager.shared.rootServices.contains { $0.value.name == name }
|
||||
)
|
||||
await ServicesManager.loadHomebrewServices()
|
||||
}
|
||||
|
||||
// MARK: - Finding Config Files
|
||||
@ -119,12 +113,12 @@ class Actions {
|
||||
|
||||
// MARK: - Other Actions
|
||||
|
||||
public static func createTempPhpInfoFile() -> URL {
|
||||
public static func createTempPhpInfoFile() async -> URL {
|
||||
// Write a file called `phpmon_phpinfo.php` to /tmp
|
||||
try! "<?php phpinfo();".write(toFile: "/tmp/phpmon_phpinfo.php", atomically: true, encoding: .utf8)
|
||||
|
||||
// Tell php-cgi to run the PHP and output as an .html file
|
||||
LegacyShell.run("\(Paths.binPath)/php-cgi -q /tmp/phpmon_phpinfo.php > /tmp/phpmon_phpinfo.html")
|
||||
await Shell.quiet("\(Paths.binPath)/php-cgi -q /tmp/phpmon_phpinfo.php > /tmp/phpmon_phpinfo.html")
|
||||
|
||||
return URL(string: "file:///private/tmp/phpmon_phpinfo.html")!
|
||||
}
|
||||
@ -145,10 +139,12 @@ class Actions {
|
||||
*/
|
||||
public static func fixMyValet(completed: @escaping () -> Void) {
|
||||
InternalSwitcher().performSwitch(to: PhpEnv.brewPhpAlias, completion: {
|
||||
brew("services restart dnsmasq", sudo: true)
|
||||
brew("services restart php", sudo: true)
|
||||
brew("services restart nginx", sudo: true)
|
||||
completed()
|
||||
Task { // restart all services and fire callback upon completion
|
||||
await brew("services restart dnsmasq", sudo: true)
|
||||
await brew("services restart php", sudo: true)
|
||||
await brew("services restart nginx", sudo: true)
|
||||
completed()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -11,21 +11,21 @@
|
||||
/**
|
||||
Runs a `valet` command. Defaults to running as superuser.
|
||||
*/
|
||||
func valet(_ command: String, sudo: Bool = true) -> String {
|
||||
return LegacyShell.pipe("\(sudo ? "sudo " : "")" + "\(Paths.valet) \(command)", requiresPath: true)
|
||||
func valet(_ command: String, sudo: Bool = true) async -> String {
|
||||
return await Shell.pipe("\(sudo ? "sudo " : "")" + "\(Paths.valet) \(command)").out
|
||||
}
|
||||
|
||||
/**
|
||||
Runs a `brew` command. Can run as superuser.
|
||||
*/
|
||||
func brew(_ command: String, sudo: Bool = false) {
|
||||
LegacyShell.run("\(sudo ? "sudo " : "")" + "\(Paths.brew) \(command)")
|
||||
func brew(_ command: String, sudo: Bool = false) async {
|
||||
await Shell.quiet("\(sudo ? "sudo " : "")" + "\(Paths.brew) \(command)")
|
||||
}
|
||||
|
||||
/**
|
||||
Runs `sed` in order to replace all occurrences of a string in a specific file with another.
|
||||
*/
|
||||
func sed(file: String, original: String, replacement: String) {
|
||||
func sed(file: String, original: String, replacement: String) async {
|
||||
// Escape slashes (or `sed` won't work)
|
||||
let e_original = original.replacingOccurrences(of: "/", with: "\\/")
|
||||
let e_replacement = replacement.replacingOccurrences(of: "/", with: "\\/")
|
||||
@ -33,19 +33,19 @@ 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 Filesystem.fileExists("\(Paths.binPath)/gsed") {
|
||||
LegacyShell.run("\(Paths.binPath)/gsed -i --follow-symlinks 's/\(e_original)/\(e_replacement)/g' \(file)")
|
||||
await Shell.quiet("\(Paths.binPath)/gsed -i --follow-symlinks 's/\(e_original)/\(e_replacement)/g' \(file)")
|
||||
} else {
|
||||
LegacyShell.run("sed -i '' 's/\(e_original)/\(e_replacement)/g' \(file)")
|
||||
await Shell.quiet("sed -i '' 's/\(e_original)/\(e_replacement)/g' \(file)")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Uses `grep` to determine whether a particular query string can be found in a particular file.
|
||||
*/
|
||||
func grepContains(file: String, query: String) -> Bool {
|
||||
return LegacyShell.pipe("""
|
||||
func grepContains(file: String, query: String) async -> Bool {
|
||||
return await Shell.pipe("""
|
||||
grep -q '\(query)' \(file); [ $? -eq 0 ] && echo "YES" || echo "NO"
|
||||
""")
|
||||
""").out
|
||||
.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
.contains("YES")
|
||||
}
|
||||
|
@ -21,11 +21,11 @@ public class Paths {
|
||||
|
||||
init() {
|
||||
baseDir = App.architecture != "x86_64" ? .opt : .usr
|
||||
}
|
||||
|
||||
Task {
|
||||
let output = await Shell.pipe("id -un").out
|
||||
userName = String(output.split(separator: "\n")[0])
|
||||
}
|
||||
public func loadUser() async {
|
||||
let output = await Shell.pipe("id -un").out
|
||||
userName = String(output.split(separator: "\n")[0])
|
||||
}
|
||||
|
||||
public func detectBinaryPaths() {
|
||||
|
@ -125,11 +125,12 @@ class ActivePhpInstallation {
|
||||
versions of PHP, we can just check for the existence of the `valet-fpm.conf` file. If the check here fails,
|
||||
that means that Valet won't work properly.
|
||||
*/
|
||||
func checkPhpFpmStatus() -> Bool {
|
||||
func checkPhpFpmStatus() async -> Bool {
|
||||
if self.version.short == "5.6" {
|
||||
// The main PHP config file should contain `valet.sock` and then we're probably fine?
|
||||
let fileName = "\(Paths.etcPath)/php/5.6/php-fpm.conf"
|
||||
return LegacyShell.pipe("cat \(fileName)").contains("valet.sock")
|
||||
return await Shell.pipe("cat \(fileName)").out
|
||||
.contains("valet.sock")
|
||||
}
|
||||
|
||||
// Make sure to check if valet-fpm.conf exists. If it does, we should be fine :)
|
||||
|
@ -16,17 +16,15 @@ class PhpEnv {
|
||||
self.currentInstall = ActivePhpInstallation()
|
||||
}
|
||||
|
||||
func determinePhpAlias() {
|
||||
Task {
|
||||
let brewPhpAlias = await Shell.pipe("\(Paths.brew) info php --json").out
|
||||
func determinePhpAlias() async {
|
||||
let brewPhpAlias = await Shell.pipe("\(Paths.brew) info php --json").out
|
||||
|
||||
self.homebrewPackage = try! JSONDecoder().decode(
|
||||
[HomebrewPackage].self,
|
||||
from: brewPhpAlias.data(using: .utf8)!
|
||||
).first!
|
||||
self.homebrewPackage = try! JSONDecoder().decode(
|
||||
[HomebrewPackage].self,
|
||||
from: brewPhpAlias.data(using: .utf8)!
|
||||
).first!
|
||||
|
||||
Log.info("[BREW] On your system, the `php` formula means version \(homebrewPackage.version)!")
|
||||
}
|
||||
Log.info("[BREW] On your system, the `php` formula means version \(homebrewPackage.version)!")
|
||||
}
|
||||
|
||||
// MARK: - Properties
|
||||
@ -80,8 +78,8 @@ class PhpEnv {
|
||||
return InternalSwitcher()
|
||||
}
|
||||
|
||||
public static func detectPhpVersions() {
|
||||
Task { await Self.shared.detectPhpVersions() }
|
||||
public static func detectPhpVersions() async {
|
||||
_ = await Self.shared.detectPhpVersions()
|
||||
}
|
||||
|
||||
/**
|
||||
@ -90,7 +88,7 @@ class PhpEnv {
|
||||
public func detectPhpVersions() async -> [String] {
|
||||
let files = await Shell.pipe("ls \(Paths.optPath) | grep php@").out
|
||||
|
||||
var versionsOnly = extractPhpVersions(from: files.components(separatedBy: "\n"))
|
||||
var versionsOnly = await extractPhpVersions(from: files.components(separatedBy: "\n"))
|
||||
|
||||
// Make sure the aliased version is detected
|
||||
// The user may have `php` installed, but not e.g. `php@8.0`
|
||||
@ -128,7 +126,7 @@ class PhpEnv {
|
||||
from versions: [String],
|
||||
checkBinaries: Bool = true,
|
||||
generateHelpers: Bool = true
|
||||
) -> [String] {
|
||||
) async -> [String] {
|
||||
var output: [String] = []
|
||||
|
||||
var supported = Constants.SupportedPhpVersions
|
||||
@ -153,7 +151,9 @@ class PhpEnv {
|
||||
}
|
||||
|
||||
if generateHelpers {
|
||||
output.forEach { PhpHelper.generate(for: $0) }
|
||||
for item in output {
|
||||
await PhpHelper.generate(for: item)
|
||||
}
|
||||
}
|
||||
|
||||
return output
|
||||
|
@ -12,7 +12,7 @@ class PhpHelper {
|
||||
|
||||
static let keyPhrase = "This file was automatically generated by PHP Monitor."
|
||||
|
||||
public static func generate(for version: String) {
|
||||
public static func generate(for version: String) async {
|
||||
// Take the PHP version (e.g. "7.2") and generate a dotless version
|
||||
let dotless = version.replacingOccurrences(of: ".", with: "")
|
||||
|
||||
@ -20,79 +20,81 @@ class PhpHelper {
|
||||
let destination = "\(Paths.homePath)/.config/phpmon/bin/pm\(dotless)"
|
||||
|
||||
// Check if the ~/.config/phpmon/bin directory is in the PATH
|
||||
let inPath = LegacyShell.user.PATH.contains("\(Paths.homePath)/.config/phpmon/bin")
|
||||
let inPath = Shell.PATH.contains("\(Paths.homePath)/.config/phpmon/bin")
|
||||
|
||||
// Check if we can create symlinks (`/usr/local/bin` must be writable)
|
||||
let canWriteSymlinks = FileManager.default.isWritableFile(atPath: "/usr/local/bin/")
|
||||
|
||||
do {
|
||||
Task { await Shell.quiet("mkdir -p ~/.config/phpmon/bin") }
|
||||
Task { // Create the appropriate folders and check if the files exist
|
||||
do {
|
||||
await Shell.quiet("mkdir -p ~/.config/phpmon/bin")
|
||||
|
||||
if Filesystem.fileExists(destination) {
|
||||
let contents = try String(contentsOfFile: destination)
|
||||
if !contents.contains(keyPhrase) {
|
||||
Log.info("The file at '\(destination)' already exists and was not generated by PHP Monitor "
|
||||
+ "(or is unreadable). Not updating this file.")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Let's follow the symlink to the PHP binary folder
|
||||
let path = URL(fileURLWithPath: "\(Paths.optPath)/php@\(version)/bin")
|
||||
.resolvingSymlinksInPath().path
|
||||
|
||||
// The contents of the script!
|
||||
let script = """
|
||||
#!/bin/zsh
|
||||
# \(keyPhrase)
|
||||
# It reflects the location of PHP \(version)'s binaries on your system.
|
||||
# Usage: . pm\(dotless)
|
||||
[[ $ZSH_EVAL_CONTEXT =~ :file$ ]] \\
|
||||
&& echo "PHP Monitor has enabled this terminal to use PHP \(version)." \\
|
||||
|| echo "You must run '. pm\(dotless)' (or 'source pm\(dotless)') instead!";
|
||||
export PATH=\(path):$PATH
|
||||
"""
|
||||
|
||||
// Write to the destination
|
||||
try script.write(
|
||||
to: URL(fileURLWithPath: destination),
|
||||
atomically: true,
|
||||
encoding: String.Encoding.utf8
|
||||
)
|
||||
|
||||
// Make sure the file is executable
|
||||
LegacyShell.run("chmod +x \(destination)")
|
||||
|
||||
// Create a symlink if the folder is not in the PATH
|
||||
if !inPath {
|
||||
// First, check if we can create symlinks at all
|
||||
if !canWriteSymlinks {
|
||||
Log.err("PHP Monitor does not have permission to symlink `/usr/local/bin/\(dotless)`.")
|
||||
return
|
||||
if Filesystem.fileExists(destination) {
|
||||
let contents = try String(contentsOfFile: destination)
|
||||
if !contents.contains(keyPhrase) {
|
||||
Log.info("The file at '\(destination)' already exists and was not generated by PHP Monitor "
|
||||
+ "(or is unreadable). Not updating this file.")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Write the symlink
|
||||
self.createSymlink(dotless)
|
||||
// Let's follow the symlink to the PHP binary folder
|
||||
let path = URL(fileURLWithPath: "\(Paths.optPath)/php@\(version)/bin")
|
||||
.resolvingSymlinksInPath().path
|
||||
|
||||
// The contents of the script!
|
||||
let script = """
|
||||
#!/bin/zsh
|
||||
# \(keyPhrase)
|
||||
# It reflects the location of PHP \(version)'s binaries on your system.
|
||||
# Usage: . pm\(dotless)
|
||||
[[ $ZSH_EVAL_CONTEXT =~ :file$ ]] \\
|
||||
&& echo "PHP Monitor has enabled this terminal to use PHP \(version)." \\
|
||||
|| echo "You must run '. pm\(dotless)' (or 'source pm\(dotless)') instead!";
|
||||
export PATH=\(path):$PATH
|
||||
"""
|
||||
|
||||
// Write to the destination
|
||||
try script.write(
|
||||
to: URL(fileURLWithPath: destination),
|
||||
atomically: true,
|
||||
encoding: String.Encoding.utf8
|
||||
)
|
||||
|
||||
// Make sure the file is executable
|
||||
await Shell.quiet("chmod +x \(destination)")
|
||||
|
||||
// Create a symlink if the folder is not in the PATH
|
||||
if !inPath {
|
||||
// First, check if we can create symlinks at all
|
||||
if !canWriteSymlinks {
|
||||
Log.err("PHP Monitor does not have permission to symlink `/usr/local/bin/\(dotless)`.")
|
||||
return
|
||||
}
|
||||
|
||||
// Write the symlink
|
||||
await self.createSymlink(dotless)
|
||||
}
|
||||
} catch {
|
||||
print(error)
|
||||
Log.err("Could not write PHP Monitor helper for PHP \(version) to \(destination))")
|
||||
}
|
||||
} catch {
|
||||
print(error)
|
||||
Log.err("Could not write PHP Monitor helper for PHP \(version) to \(destination))")
|
||||
}
|
||||
}
|
||||
|
||||
private static func createSymlink(_ dotless: String) {
|
||||
private static func createSymlink(_ dotless: String) async {
|
||||
let source = "\(Paths.homePath)/.config/phpmon/bin/pm\(dotless)"
|
||||
let destination = "/usr/local/bin/pm\(dotless)"
|
||||
|
||||
if !Filesystem.fileExists(destination) {
|
||||
Log.info("Creating new symlink: \(destination)")
|
||||
LegacyShell.run("ln -s \(source) \(destination)")
|
||||
await Shell.quiet("ln -s \(source) \(destination)")
|
||||
return
|
||||
}
|
||||
|
||||
if !Filesystem.fileIsSymlink(destination) {
|
||||
Log.info("Overwriting existing file with new symlink: \(destination)")
|
||||
LegacyShell.run("ln -fs \(source) \(destination)")
|
||||
await Shell.quiet("ln -fs \(source) \(destination)")
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -75,14 +75,14 @@ class PhpExtension {
|
||||
This simply toggles the extension in the .ini file.
|
||||
You may need to restart the other services in order for this change to apply.
|
||||
*/
|
||||
func toggle() {
|
||||
func toggle() async {
|
||||
let newLine = enabled
|
||||
// DISABLED: Commented out line
|
||||
? "; \(line)"
|
||||
// ENABLED: Line where the comment delimiter (;) is removed
|
||||
: line.replacingOccurrences(of: "; ", with: "")
|
||||
|
||||
sed(file: file, original: line, replacement: newLine)
|
||||
await sed(file: file, original: line, replacement: newLine)
|
||||
|
||||
enabled.toggle()
|
||||
}
|
||||
|
@ -19,6 +19,8 @@ class InternalSwitcher: PhpSwitcher {
|
||||
Please note that depending on which version is installed,
|
||||
the version that is switched to may or may not be identical to `php`
|
||||
(without @version).
|
||||
|
||||
TODO: Use `async` and use structured concurrency: https://www.hackingwithswift.com/swift/5.5/structured-concurrency
|
||||
*/
|
||||
func performSwitch(to version: String, completion: @escaping () -> Void) {
|
||||
Log.info("Switching to \(version), unlinking all versions...")
|
||||
@ -30,26 +32,28 @@ class InternalSwitcher: PhpSwitcher {
|
||||
PhpEnv.shared.availablePhpVersions.forEach { (available) in
|
||||
group.enter()
|
||||
|
||||
DispatchQueue.global(qos: .userInitiated).async {
|
||||
self.disableDefaultPhpFpmPool(available)
|
||||
self.stopPhpVersion(available)
|
||||
Task { // TODO: Use structured concurrency
|
||||
await self.disableDefaultPhpFpmPool(available)
|
||||
await self.stopPhpVersion(available)
|
||||
group.leave()
|
||||
}
|
||||
}
|
||||
|
||||
group.notify(queue: .global(qos: .userInitiated)) {
|
||||
Log.info("All versions have been unlinked!")
|
||||
Log.info("Linking the new version!")
|
||||
Task { // TODO: Use structured concurrency
|
||||
Log.info("All versions have been unlinked!")
|
||||
Log.info("Linking the new version!")
|
||||
|
||||
for formula in versions {
|
||||
self.startPhpVersion(formula, primary: (version == formula))
|
||||
for formula in versions {
|
||||
await self.startPhpVersion(formula, primary: (version == formula))
|
||||
}
|
||||
|
||||
Log.info("Restarting nginx, just to be sure!")
|
||||
await brew("services restart nginx", sudo: true)
|
||||
|
||||
Log.info("The new version(s) have been linked!")
|
||||
completion()
|
||||
}
|
||||
|
||||
Log.info("Restarting nginx, just to be sure!")
|
||||
brew("services restart nginx", sudo: true)
|
||||
|
||||
Log.info("The new version(s) have been linked!")
|
||||
completion()
|
||||
}
|
||||
}
|
||||
|
||||
@ -74,7 +78,7 @@ class InternalSwitcher: PhpSwitcher {
|
||||
return FileManager.default.fileExists(atPath: pool)
|
||||
}
|
||||
|
||||
func disableDefaultPhpFpmPool(_ version: String) {
|
||||
func disableDefaultPhpFpmPool(_ version: String) async {
|
||||
let pool = "\(Paths.etcPath)/php/\(version)/php-fpm.d/www.conf"
|
||||
if FileManager.default.fileExists(atPath: pool) {
|
||||
Log.info("A default `www.conf` file was found in the php-fpm.d directory for PHP \(version).")
|
||||
@ -94,28 +98,28 @@ class InternalSwitcher: PhpSwitcher {
|
||||
}
|
||||
}
|
||||
|
||||
func stopPhpVersion(_ version: String) {
|
||||
func stopPhpVersion(_ version: String) async {
|
||||
let formula = (version == PhpEnv.brewPhpAlias) ? "php" : "php@\(version)"
|
||||
brew("unlink \(formula)")
|
||||
brew("services stop \(formula)", sudo: true)
|
||||
await brew("unlink \(formula)")
|
||||
await brew("services stop \(formula)", sudo: true)
|
||||
Log.info("Unlinked and stopped services for \(formula)")
|
||||
}
|
||||
|
||||
func startPhpVersion(_ version: String, primary: Bool) {
|
||||
func startPhpVersion(_ version: String, primary: Bool) async {
|
||||
let formula = (version == PhpEnv.brewPhpAlias) ? "php" : "php@\(version)"
|
||||
|
||||
if primary {
|
||||
Log.info("\(formula) is the primary formula, linking and starting services...")
|
||||
brew("link \(formula) --overwrite --force")
|
||||
await brew("link \(formula) --overwrite --force")
|
||||
} else {
|
||||
Log.info("\(formula) is an isolated PHP version, starting services only...")
|
||||
}
|
||||
|
||||
brew("services start \(formula)", sudo: true)
|
||||
await brew("services start \(formula)", sudo: true)
|
||||
|
||||
if Valet.enabled(feature: .isolatedSites) && primary {
|
||||
let socketVersion = version.replacingOccurrences(of: ".", with: "")
|
||||
LegacyShell.run("ln -sF ~/.config/valet/valet\(socketVersion).sock ~/.config/valet/valet.sock")
|
||||
await Shell.quiet("ln -sF ~/.config/valet/valet\(socketVersion).sock ~/.config/valet/valet.sock")
|
||||
Log.info("Symlinked new socket version (valet\(socketVersion).sock → valet.sock).")
|
||||
}
|
||||
|
||||
|
@ -1,33 +0,0 @@
|
||||
//
|
||||
// Shell+PATH.swift
|
||||
// PHP Monitor
|
||||
//
|
||||
// Created by Nico Verbruggen on 15/08/2022.
|
||||
// Copyright © 2022 Nico Verbruggen. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
extension LegacyShell {
|
||||
|
||||
var PATH: String {
|
||||
let task = Process()
|
||||
task.launchPath = "/bin/zsh"
|
||||
|
||||
let command = Filesystem.fileExists("~/.zshrc")
|
||||
// source the user's .zshrc file if it exists to complete $PATH
|
||||
? ". ~/.zshrc && echo $PATH"
|
||||
// otherwise, non-interactive mode is sufficient
|
||||
: "echo $PATH"
|
||||
|
||||
task.arguments = ["--login", "-lc", command]
|
||||
|
||||
let pipe = Pipe()
|
||||
task.standardOutput = pipe
|
||||
task.launch()
|
||||
|
||||
let data = pipe.fileHandleForReading.readDataToEndOfFile()
|
||||
|
||||
return String(data: data, encoding: String.Encoding.utf8) ?? ""
|
||||
}
|
||||
}
|
@ -1,175 +0,0 @@
|
||||
//
|
||||
// Shell.swift
|
||||
// PHP Monitor
|
||||
//
|
||||
// Copyright © 2022 Nico Verbruggen. All rights reserved.
|
||||
//
|
||||
|
||||
import Cocoa
|
||||
|
||||
// TODO: Enable this to see where deprecations and replacements are needed.
|
||||
@available(*, deprecated, message: "Use the new replacement `Shell` instead")
|
||||
public class LegacyShell {
|
||||
|
||||
// MARK: - Invoke static functions
|
||||
|
||||
public static func run(
|
||||
_ command: String,
|
||||
requiresPath: Bool = false
|
||||
) {
|
||||
LegacyShell.user.run(command, requiresPath: requiresPath)
|
||||
}
|
||||
|
||||
public static func pipe(
|
||||
_ command: String,
|
||||
requiresPath: Bool = false
|
||||
) -> String {
|
||||
return LegacyShell.user.pipe(command, requiresPath: requiresPath)
|
||||
}
|
||||
|
||||
// MARK: - Singleton
|
||||
|
||||
/**
|
||||
We now require macOS 11, so no need to detect which terminal to use.
|
||||
*/
|
||||
public var shell: String = "/bin/sh"
|
||||
|
||||
/** Additional exports that are sent if `requiresPath` is set to true. */
|
||||
public var exports: String = ""
|
||||
|
||||
/**
|
||||
Singleton to access a user shell (with --login)
|
||||
*/
|
||||
public static let user = LegacyShell()
|
||||
|
||||
/**
|
||||
Runs a shell command without using the output.
|
||||
Uses the default shell.
|
||||
|
||||
- Parameter command: The command to run
|
||||
- Parameter requiresPath: By default, the PATH is not resolved but some binaries might require this
|
||||
*/
|
||||
private func run(
|
||||
_ command: String,
|
||||
requiresPath: Bool = false
|
||||
) {
|
||||
// Equivalent of piping to /dev/null; don't do anything with the string
|
||||
_ = LegacyShell.pipe(command, requiresPath: requiresPath)
|
||||
}
|
||||
|
||||
/**
|
||||
Runs a shell command and returns the output.
|
||||
|
||||
- Parameter command: The command to run
|
||||
- Parameter requiresPath: By default, the PATH is not resolved but some binaries might require this
|
||||
*/
|
||||
private func pipe(
|
||||
_ command: String,
|
||||
requiresPath: Bool = false
|
||||
) -> String {
|
||||
let shellOutput = self.executeSynchronously(command, requiresPath: requiresPath)
|
||||
let hasError = (
|
||||
shellOutput.standardOutput == ""
|
||||
&& shellOutput.errorOutput.lengthOfBytes(using: .utf8) > 0
|
||||
)
|
||||
return !hasError ? shellOutput.standardOutput : shellOutput.errorOutput
|
||||
}
|
||||
|
||||
/**
|
||||
Runs the command and returns a `ShellOutput` object, which contains info about the process.
|
||||
|
||||
- Parameter command: The command to run
|
||||
- Parameter requiresPath: By default, the PATH is not resolved but some binaries might require this
|
||||
- Parameter waitUntilExit: Waits for the command to complete before returning the `ShellOutput`
|
||||
*/
|
||||
public func executeSynchronously(
|
||||
_ command: String,
|
||||
requiresPath: Bool = false
|
||||
) -> LegacyShell.Output {
|
||||
|
||||
let outputPipe = Pipe()
|
||||
let errorPipe = Pipe()
|
||||
|
||||
let task = self.createTask(for: command, requiresPath: requiresPath)
|
||||
task.standardOutput = outputPipe
|
||||
task.standardError = errorPipe
|
||||
task.launch()
|
||||
task.waitUntilExit()
|
||||
|
||||
let output = LegacyShell.Output(
|
||||
standardOutput: String(
|
||||
data: outputPipe.fileHandleForReading.readDataToEndOfFile(),
|
||||
encoding: .utf8
|
||||
)!,
|
||||
errorOutput: String(
|
||||
data: errorPipe.fileHandleForReading.readDataToEndOfFile(),
|
||||
encoding: .utf8
|
||||
)!,
|
||||
task: task
|
||||
)
|
||||
|
||||
if CommandLine.arguments.contains("--v") {
|
||||
log(task: task, output: output)
|
||||
}
|
||||
|
||||
return output
|
||||
}
|
||||
|
||||
/**
|
||||
Creates a new process with the correct PATH and shell.
|
||||
*/
|
||||
public func createTask(for command: String, requiresPath: Bool) -> Process {
|
||||
var completeCommand = ""
|
||||
|
||||
Log.info("LEGACY COMMAND: \(command)")
|
||||
|
||||
if requiresPath {
|
||||
// Basic export (PATH)
|
||||
completeCommand += "export PATH=\(Paths.binPath):$PATH && "
|
||||
|
||||
// Put additional exports in between
|
||||
if !self.exports.isEmpty {
|
||||
completeCommand += "\(self.exports) && "
|
||||
}
|
||||
}
|
||||
|
||||
completeCommand += command
|
||||
|
||||
let task = Process()
|
||||
task.launchPath = self.shell
|
||||
task.arguments = ["--noprofile", "-norc", "--login", "-c", completeCommand]
|
||||
|
||||
return task
|
||||
}
|
||||
|
||||
/**
|
||||
Verbose logging for PHP Monitor's synchronous shell output.
|
||||
*/
|
||||
private func log(task: Process, output: Output) {
|
||||
Log.info("")
|
||||
Log.info("==== COMMAND ====")
|
||||
Log.info("")
|
||||
Log.info("\(self.shell) \(task.arguments?.joined(separator: " ") ?? "")")
|
||||
Log.info("")
|
||||
Log.info("==== OUTPUT ====")
|
||||
Log.info("")
|
||||
dump(output)
|
||||
Log.info("")
|
||||
Log.info("==== END OUTPUT ====")
|
||||
Log.info("")
|
||||
}
|
||||
|
||||
public class Output {
|
||||
public let standardOutput: String
|
||||
public let errorOutput: String
|
||||
public let task: Process
|
||||
|
||||
init(standardOutput: String,
|
||||
errorOutput: String,
|
||||
task: Process) {
|
||||
self.standardOutput = standardOutput
|
||||
self.errorOutput = errorOutput
|
||||
self.task = task
|
||||
}
|
||||
}
|
||||
}
|
@ -33,15 +33,17 @@ extension AppDelegate {
|
||||
}
|
||||
|
||||
@IBAction func reloadDomainListPressed(_ sender: Any) {
|
||||
let vc = App.shared.domainListWindowController?
|
||||
.window?.contentViewController as? DomainListVC
|
||||
Task { // Reload domains
|
||||
let vc = App.shared.domainListWindowController?
|
||||
.window?.contentViewController as? DomainListVC
|
||||
|
||||
if vc != nil {
|
||||
// If the view exists, directly reload the list of sites.
|
||||
vc!.reloadDomains()
|
||||
} else {
|
||||
// If the view does not exist, reload the cached data that was populated when the app initially launched.
|
||||
Valet.shared.reloadSites()
|
||||
if vc != nil {
|
||||
// If the view exists, directly reload the list of sites.
|
||||
await vc!.reloadDomains()
|
||||
} else {
|
||||
// If the view does not exist, reload the cached data that was populated when the app launched.
|
||||
await Valet.shared.reloadSites()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -13,13 +13,6 @@ class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCenterDele
|
||||
|
||||
// MARK: - Variables
|
||||
|
||||
/**
|
||||
The Shell singleton that keeps track of the history of all
|
||||
(invoked by PHP Monitor) shell commands. It is used to
|
||||
invoke all commands in this application.
|
||||
*/
|
||||
let sharedShell: LegacyShell
|
||||
|
||||
/**
|
||||
The App singleton contains information about the state of
|
||||
the application and global variables.
|
||||
@ -68,7 +61,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCenterDele
|
||||
logger.verbosity = .performance
|
||||
|
||||
// TODO: Enable to fake broken setup during testing
|
||||
ActiveShell.useTestable(Testables.working.shellOutput)
|
||||
// ActiveShell.useTestable(Testables.working.shellOutput)
|
||||
|
||||
#endif
|
||||
if CommandLine.arguments.contains("--v") {
|
||||
@ -81,7 +74,6 @@ class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCenterDele
|
||||
Log.info("Version \(App.version)")
|
||||
Log.separator(as: .info)
|
||||
|
||||
self.sharedShell = LegacyShell.user
|
||||
self.state = App.shared
|
||||
self.menu = MainMenu.shared
|
||||
self.paths = Paths.shared
|
||||
@ -103,8 +95,10 @@ class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCenterDele
|
||||
func applicationDidFinishLaunching(_ aNotification: Notification) {
|
||||
// Make sure notifications will work
|
||||
setupNotifications()
|
||||
// Make sure the menu performs its initial checks
|
||||
Task { await menu.startup() }
|
||||
Task { // Make sure the menu performs its initial checks
|
||||
await paths.loadUser()
|
||||
await menu.startup()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -21,7 +21,7 @@ class AppUpdateChecker {
|
||||
|
||||
public static func retrieveVersionFromCask(
|
||||
_ initiatedFromBackground: Bool = true
|
||||
) -> String {
|
||||
) async -> String {
|
||||
let caskFile = App.version.contains("-dev")
|
||||
? Constants.Urls.DevBuildCaskFile.absoluteString
|
||||
: Constants.Urls.StableBuildCaskFile.absoluteString
|
||||
@ -32,14 +32,14 @@ class AppUpdateChecker {
|
||||
command = "curl -s --max-time 5"
|
||||
}
|
||||
|
||||
return LegacyShell.pipe(
|
||||
return await Shell.pipe(
|
||||
"\(command) '\(caskFile)' | grep version"
|
||||
)
|
||||
).out
|
||||
}
|
||||
|
||||
public static func checkIfNewerVersionIsAvailable(
|
||||
initiatedFromBackground: Bool = true
|
||||
) {
|
||||
) async {
|
||||
if initiatedFromBackground {
|
||||
if !Preferences.isEnabled(.automaticBackgroundUpdateCheck) {
|
||||
Log.info("Automatic updates are disabled. No check will be performed.")
|
||||
@ -49,7 +49,7 @@ class AppUpdateChecker {
|
||||
Log.info("Automatic updates are enabled, a check will be performed.")
|
||||
}
|
||||
|
||||
let versionString = retrieveVersionFromCask(initiatedFromBackground)
|
||||
let versionString = await retrieveVersionFromCask(initiatedFromBackground)
|
||||
|
||||
guard let onlineVersion = AppVersion.from(versionString) else {
|
||||
Log.err("We couldn't check for updates!")
|
||||
|
@ -26,10 +26,14 @@ class InterApp {
|
||||
DomainListVC.show()
|
||||
}),
|
||||
InterApp.Action(command: "services/stop", action: { _ in
|
||||
MainMenu.shared.stopValetServices()
|
||||
Task { // Stopping services as standalone task
|
||||
await MainMenu.shared.stopValetServices()
|
||||
}
|
||||
}),
|
||||
InterApp.Action(command: "services/restart/all", action: { _ in
|
||||
MainMenu.shared.restartValetServices()
|
||||
Task { // Restarting services as standalone task
|
||||
await MainMenu.shared.restartValetServices()
|
||||
}
|
||||
}),
|
||||
InterApp.Action(command: "services/restart/nginx", action: { _ in
|
||||
MainMenu.shared.restartNginx()
|
||||
|
@ -16,53 +16,44 @@ class ServicesManager: ObservableObject {
|
||||
@Published var rootServices: [String: HomebrewService] = [:]
|
||||
@Published var userServices: [String: HomebrewService] = [:]
|
||||
|
||||
public static func loadHomebrewServices(completed: (() -> Void)? = nil) {
|
||||
public static func loadHomebrewServices() async {
|
||||
let rootServiceNames = [
|
||||
PhpEnv.phpInstall.formula,
|
||||
"nginx",
|
||||
"dnsmasq"
|
||||
]
|
||||
|
||||
DispatchQueue.global(qos: .background).async {
|
||||
let data = LegacyShell
|
||||
.pipe("sudo \(Paths.brew) services info --all --json", requiresPath: true)
|
||||
.data(using: .utf8)!
|
||||
let normalJson = await Shell
|
||||
.pipe("sudo \(Paths.brew) services info --all --json")
|
||||
.out
|
||||
.data(using: .utf8)!
|
||||
|
||||
let services = try! JSONDecoder()
|
||||
.decode([HomebrewService].self, from: data)
|
||||
.filter({ return rootServiceNames.contains($0.name) })
|
||||
let normalServices = try! JSONDecoder()
|
||||
.decode([HomebrewService].self, from: normalJson)
|
||||
.filter({ return rootServiceNames.contains($0.name) })
|
||||
|
||||
DispatchQueue.main.async {
|
||||
ServicesManager.shared.rootServices = Dictionary(
|
||||
uniqueKeysWithValues: services.map { ($0.name, $0) }
|
||||
)
|
||||
}
|
||||
DispatchQueue.main.async {
|
||||
ServicesManager.shared.rootServices = Dictionary(
|
||||
uniqueKeysWithValues: normalServices.map { ($0.name, $0) }
|
||||
)
|
||||
}
|
||||
|
||||
guard let userServiceNames = Preferences.custom.services else {
|
||||
return
|
||||
}
|
||||
|
||||
DispatchQueue.global(qos: .background).async {
|
||||
let data = LegacyShell
|
||||
.pipe("\(Paths.brew) services info --all --json", requiresPath: true)
|
||||
.data(using: .utf8)!
|
||||
let rootJson = await Shell
|
||||
.pipe("\(Paths.brew) services info --all --json")
|
||||
.out
|
||||
.data(using: .utf8)!
|
||||
|
||||
let services = try! JSONDecoder()
|
||||
.decode([HomebrewService].self, from: data)
|
||||
.filter({ return userServiceNames.contains($0.name) })
|
||||
let rootServices = try! JSONDecoder()
|
||||
.decode([HomebrewService].self, from: rootJson)
|
||||
.filter({ return userServiceNames.contains($0.name) })
|
||||
|
||||
DispatchQueue.main.async {
|
||||
ServicesManager.shared.userServices = Dictionary(
|
||||
uniqueKeysWithValues: services.map { ($0.name, $0) }
|
||||
)
|
||||
completed?()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func loadData() {
|
||||
Self.loadHomebrewServices()
|
||||
ServicesManager.shared.userServices = Dictionary(
|
||||
uniqueKeysWithValues: rootServices.map { ($0.name, $0) }
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -71,9 +71,9 @@ class AddProxyVC: NSViewController, NSTextFieldDelegate {
|
||||
|
||||
App.shared.domainListWindowController?.contentVC.setUIBusy()
|
||||
|
||||
DispatchQueue.global(qos: .userInitiated).async {
|
||||
LegacyShell.run("\(Paths.valet) proxy \(domain) \(proxyName)\(secure)", requiresPath: true)
|
||||
Actions.restartNginx()
|
||||
Task {
|
||||
await Shell.quiet("\(Paths.valet) proxy \(domain) \(proxyName)\(secure)")
|
||||
await Actions.restartNginx()
|
||||
|
||||
DispatchQueue.main.async {
|
||||
App.shared.domainListWindowController?.contentVC.setUINotBusy()
|
||||
|
@ -51,7 +51,7 @@ class AddSiteVC: NSViewController, NSTextFieldDelegate {
|
||||
|
||||
// MARK: - Outlet Interactions
|
||||
|
||||
@IBAction func pressedCreateLink(_ sender: Any) {
|
||||
@IBAction func pressedCreateLink(_ sender: Any) async {
|
||||
let path = pathControl.url!.path
|
||||
let name = inputDomainName.stringValue
|
||||
|
||||
@ -71,7 +71,9 @@ class AddSiteVC: NSViewController, NSTextFieldDelegate {
|
||||
|
||||
// Adding `valet links` is a workaround for Valet malforming the config.json file
|
||||
// TODO: I will have to investigate and report this behaviour if possible
|
||||
LegacyShell.run("cd '\(path)' && \(Paths.valet) link '\(name)' && valet links", requiresPath: true)
|
||||
Task {
|
||||
await Shell.quiet("cd '\(path)' && \(Paths.valet) link '\(name)' && valet links")
|
||||
}
|
||||
|
||||
dismissView(outcome: .OK)
|
||||
|
||||
@ -81,7 +83,7 @@ class AddSiteVC: NSViewController, NSTextFieldDelegate {
|
||||
.searchField.stringValue = ""
|
||||
|
||||
// Add the new item and scrolls to it
|
||||
App.shared.domainListWindowController?
|
||||
await App.shared.domainListWindowController?
|
||||
.contentVC
|
||||
.addedNewSite(
|
||||
name: name,
|
||||
|
@ -25,15 +25,14 @@ extension DomainListVC {
|
||||
|
||||
self.waitAndExecute {
|
||||
// 1. Remove the original proxy
|
||||
LegacyShell.run("\(Paths.valet) unproxy \(selectedProxy.domain)", requiresPath: true)
|
||||
await Shell.quiet("\(Paths.valet) unproxy \(selectedProxy.domain)")
|
||||
|
||||
// 2. Add a new proxy, which is either secured/unsecured
|
||||
let secure = originalSecureStatus ? "" : " --secure"
|
||||
LegacyShell.run("\(Paths.valet) proxy \(selectedProxy.domain) \(selectedProxy.target)\(secure)",
|
||||
requiresPath: true)
|
||||
await Shell.quiet("\(Paths.valet) proxy \(selectedProxy.domain) \(selectedProxy.target)\(secure)")
|
||||
|
||||
// 3. Restart nginx
|
||||
Actions.restartNginx()
|
||||
await Actions.restartNginx()
|
||||
|
||||
// 4. Reload site list
|
||||
DispatchQueue.main.async {
|
||||
@ -50,7 +49,7 @@ extension DomainListVC {
|
||||
let command = "cd '\(selectedSite.absolutePath)' && sudo \(Paths.valet) \(action) && exit;"
|
||||
|
||||
waitAndExecute {
|
||||
LegacyShell.run(command, requiresPath: true)
|
||||
await Shell.quiet(command)
|
||||
} completion: { [self] in
|
||||
selectedSite.determineSecured()
|
||||
if selectedSite.secured == originalSecureStatus {
|
||||
@ -99,12 +98,12 @@ extension DomainListVC {
|
||||
NSWorkspace.shared.open(url)
|
||||
}
|
||||
|
||||
@objc func openInFinder() {
|
||||
LegacyShell.run("open '\(selectedSite!.absolutePath)'")
|
||||
@objc func openInFinder() async {
|
||||
await Shell.quiet("open '\(selectedSite!.absolutePath)'")
|
||||
}
|
||||
|
||||
@objc func openInTerminal() {
|
||||
LegacyShell.run("open -b com.apple.terminal '\(selectedSite!.absolutePath)'")
|
||||
@objc func openInTerminal() async {
|
||||
await Shell.quiet("open -b com.apple.terminal '\(selectedSite!.absolutePath)'")
|
||||
}
|
||||
|
||||
@objc func openWithEditor(sender: EditorMenuItem) async {
|
||||
@ -157,9 +156,9 @@ extension DomainListVC {
|
||||
style: .critical,
|
||||
onFirstButtonPressed: {
|
||||
self.waitAndExecute {
|
||||
LegacyShell.run("valet unlink '\(site.name)'", requiresPath: true)
|
||||
Task { await Shell.quiet("valet unlink '\(site.name)'") }
|
||||
} completion: {
|
||||
self.reloadDomains()
|
||||
Task { await self.reloadDomains() }
|
||||
}
|
||||
}
|
||||
)
|
||||
@ -179,9 +178,9 @@ extension DomainListVC {
|
||||
style: .critical,
|
||||
onFirstButtonPressed: {
|
||||
self.waitAndExecute {
|
||||
LegacyShell.run("valet unproxy '\(proxy.domain)'", requiresPath: true)
|
||||
Task { await Shell.quiet("valet unproxy '\(proxy.domain)'") }
|
||||
} completion: {
|
||||
self.reloadDomains()
|
||||
Task { await self.reloadDomains() }
|
||||
}
|
||||
}
|
||||
)
|
||||
@ -191,7 +190,7 @@ extension DomainListVC {
|
||||
let rowToReload = tableView.selectedRow
|
||||
|
||||
waitAndExecute {
|
||||
LegacyShell.run(command, requiresPath: true)
|
||||
await Shell.quiet(command)
|
||||
} completion: { [self] in
|
||||
beforeCellReload()
|
||||
tableView.reloadData(forRowIndexes: [rowToReload], columnIndexes: [0, 1, 2, 3, 4])
|
||||
|
@ -97,7 +97,7 @@ class DomainListVC: NSViewController, NSTableViewDelegate, NSTableViewDataSource
|
||||
domains = Valet.getDomainListable()
|
||||
searchedFor(text: lastSearchedFor)
|
||||
} else {
|
||||
reloadDomains()
|
||||
Task { await reloadDomains() }
|
||||
}
|
||||
}
|
||||
|
||||
@ -107,7 +107,7 @@ class DomainListVC: NSViewController, NSTableViewDelegate, NSTableViewDataSource
|
||||
Disables the UI so the user cannot interact with it.
|
||||
Also shows a spinner to indicate that we're busy.
|
||||
*/
|
||||
public func setUIBusy() {
|
||||
@MainActor public func setUIBusy() {
|
||||
// If it takes more than 0.5s to set the UI to not busy, show a spinner
|
||||
timer = Timer.scheduledTimer(withTimeInterval: 0.5, repeats: false, block: { _ in
|
||||
self.progressIndicator.startAnimation(true)
|
||||
@ -121,7 +121,7 @@ class DomainListVC: NSViewController, NSTableViewDelegate, NSTableViewDataSource
|
||||
/**
|
||||
Re-enables the UI so the user can interact with it.
|
||||
*/
|
||||
public func setUINotBusy() {
|
||||
@MainActor public func setUINotBusy() {
|
||||
timer?.invalidate()
|
||||
progressIndicator.stopAnimation(nil)
|
||||
tableView.alphaValue = 1.0
|
||||
@ -136,12 +136,11 @@ class DomainListVC: NSViewController, NSTableViewDelegate, NSTableViewDataSource
|
||||
- Parameter execute: Callback of the work that needs to happen.
|
||||
- Parameter completion: Callback that is fired when the work is done.
|
||||
*/
|
||||
internal func waitAndExecute(_ execute: @escaping () -> Void, completion: @escaping () -> Void = {}) {
|
||||
setUIBusy()
|
||||
DispatchQueue.global(qos: .userInitiated).async { [unowned self] in
|
||||
execute()
|
||||
internal func waitAndExecute(_ execute: @escaping () async -> Void, completion: @escaping () -> Void = {}) {
|
||||
Task {
|
||||
setUIBusy()
|
||||
await execute()
|
||||
|
||||
// For a smoother animation, expect at least a 0.2 second delay
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) { [self] in
|
||||
completion()
|
||||
setUINotBusy()
|
||||
@ -151,9 +150,9 @@ class DomainListVC: NSViewController, NSTableViewDelegate, NSTableViewDataSource
|
||||
|
||||
// MARK: - Site Data Loading
|
||||
|
||||
func reloadDomains() {
|
||||
func reloadDomains() async {
|
||||
waitAndExecute {
|
||||
Valet.shared.reloadSites()
|
||||
await Valet.shared.reloadSites()
|
||||
} completion: { [self] in
|
||||
domains = Valet.shared.sites
|
||||
searchedFor(text: lastSearchedFor)
|
||||
@ -177,9 +176,9 @@ class DomainListVC: NSViewController, NSTableViewDelegate, NSTableViewDataSource
|
||||
self.domains = descriptor.ascending ? sorted.reversed() : sorted
|
||||
}
|
||||
|
||||
func addedNewSite(name: String, secure: Bool) {
|
||||
func addedNewSite(name: String, secure: Bool) async {
|
||||
waitAndExecute {
|
||||
Valet.shared.reloadSites()
|
||||
await Valet.shared.reloadSites()
|
||||
} completion: { [self] in
|
||||
find(name, secure)
|
||||
}
|
||||
|
@ -51,7 +51,9 @@ class DomainListWindowController: PMWindowController, NSSearchFieldDelegate, NST
|
||||
// MARK: - Reload functionality
|
||||
|
||||
@IBAction func pressedReload(_ sender: Any?) {
|
||||
contentVC.reloadDomains()
|
||||
Task {
|
||||
await contentVC.reloadDomains()
|
||||
}
|
||||
}
|
||||
|
||||
@IBAction func pressedAddLink(_ sender: Any?) {
|
||||
|
@ -21,10 +21,9 @@ import Foundation
|
||||
self.completion = completion
|
||||
|
||||
Paths.shared.detectBinaryPaths()
|
||||
|
||||
if Paths.composer == nil {
|
||||
DispatchQueue.main.async {
|
||||
self.presentMissingAlert()
|
||||
}
|
||||
self.presentMissingAlert()
|
||||
return
|
||||
}
|
||||
|
||||
@ -39,7 +38,9 @@ import Foundation
|
||||
|
||||
window?.setType(info: true)
|
||||
|
||||
Task { await performComposerUpdate() }
|
||||
Task { // Start the Composer global update as a separate task
|
||||
await performComposerUpdate()
|
||||
}
|
||||
}
|
||||
|
||||
private func performComposerUpdate() async {
|
||||
|
@ -42,7 +42,7 @@ class HomebrewDiagnostics {
|
||||
This check only needs to be performed if the `shivammathur/php` tap is active.
|
||||
*/
|
||||
public static func checkForCaskConflict() {
|
||||
Task {
|
||||
Task { // Check if there's a conflict
|
||||
if await hasAliasConflict() {
|
||||
presentAlertAboutConflict()
|
||||
}
|
||||
@ -72,9 +72,11 @@ class HomebrewDiagnostics {
|
||||
}
|
||||
|
||||
versions.forEach { version in
|
||||
switcher.disableDefaultPhpFpmPool(version)
|
||||
switcher.stopPhpVersion(version)
|
||||
switcher.startPhpVersion(version, primary: version == primary)
|
||||
Task { // Fix each pool concurrently (but perform the tasks sequentially)
|
||||
await switcher.disableDefaultPhpFpmPool(version)
|
||||
await switcher.stopPhpVersion(version)
|
||||
await switcher.startPhpVersion(version, primary: version == primary)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -116,15 +116,15 @@ class Valet {
|
||||
Starts the preload of sites. In order to make sure PHP Monitor can correctly
|
||||
handle all PHP versions including isolation, it needs to know about all sites.
|
||||
*/
|
||||
public func startPreloadingSites() {
|
||||
self.reloadSites()
|
||||
public func startPreloadingSites() async {
|
||||
await self.reloadSites()
|
||||
}
|
||||
|
||||
/**
|
||||
Reloads the list of sites, assuming that the list isn't being reloaded at the time.
|
||||
(We don't want to do duplicate or parallel work!)
|
||||
*/
|
||||
public func reloadSites() {
|
||||
public func reloadSites() async {
|
||||
loadConfiguration()
|
||||
|
||||
if isBusy {
|
||||
|
@ -42,17 +42,17 @@ extension MainMenu {
|
||||
}
|
||||
|
||||
@objc func restartPhpFpm() {
|
||||
asyncExecution {
|
||||
Actions.restartPhpFpm()
|
||||
Task { // Simple restart service
|
||||
await Actions.restartPhpFpm()
|
||||
}
|
||||
}
|
||||
|
||||
@objc func restartValetServices() {
|
||||
asyncExecution {
|
||||
Actions.restartDnsMasq()
|
||||
Actions.restartPhpFpm()
|
||||
Actions.restartNginx()
|
||||
} success: {
|
||||
@MainActor @objc func restartValetServices() {
|
||||
Task { // Restart services and show notification
|
||||
await Actions.restartDnsMasq()
|
||||
await Actions.restartPhpFpm()
|
||||
await Actions.restartNginx()
|
||||
|
||||
LocalNotification.send(
|
||||
title: "notification.services_restarted".localized,
|
||||
subtitle: "notification.services_restarted_desc".localized,
|
||||
@ -61,10 +61,10 @@ extension MainMenu {
|
||||
}
|
||||
}
|
||||
|
||||
@objc func stopValetServices() {
|
||||
asyncExecution {
|
||||
Actions.stopValetServices()
|
||||
} success: {
|
||||
@MainActor @objc func stopValetServices() {
|
||||
Task { // Stop services and show notification
|
||||
await Actions.stopValetServices()
|
||||
|
||||
LocalNotification.send(
|
||||
title: "notification.services_stopped".localized,
|
||||
subtitle: "notification.services_stopped_desc".localized,
|
||||
@ -74,14 +74,14 @@ extension MainMenu {
|
||||
}
|
||||
|
||||
@objc func restartNginx() {
|
||||
asyncExecution {
|
||||
Actions.restartNginx()
|
||||
Task {
|
||||
await Actions.restartNginx()
|
||||
}
|
||||
}
|
||||
|
||||
@objc func restartDnsMasq() {
|
||||
asyncExecution {
|
||||
Actions.restartDnsMasq()
|
||||
Task {
|
||||
await Actions.restartDnsMasq()
|
||||
}
|
||||
}
|
||||
|
||||
@ -134,18 +134,18 @@ extension MainMenu {
|
||||
}
|
||||
|
||||
@objc func toggleExtension(sender: ExtensionMenuItem) {
|
||||
asyncExecution {
|
||||
sender.phpExtension?.toggle()
|
||||
Task {
|
||||
await sender.phpExtension?.toggle()
|
||||
|
||||
if Preferences.isEnabled(.autoServiceRestartAfterExtensionToggle) {
|
||||
Actions.restartPhpFpm()
|
||||
await Actions.restartPhpFpm()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func performRollback() {
|
||||
asyncExecution {
|
||||
PresetHelper.rollbackPreset?.apply()
|
||||
Task {
|
||||
await PresetHelper.rollbackPreset?.apply()
|
||||
PresetHelper.rollbackPreset = nil
|
||||
MainMenu.shared.rebuild()
|
||||
}
|
||||
@ -171,8 +171,8 @@ extension MainMenu {
|
||||
}
|
||||
|
||||
@objc func togglePreset(sender: PresetMenuItem) {
|
||||
asyncExecution {
|
||||
sender.preset?.apply()
|
||||
Task {
|
||||
await sender.preset?.apply()
|
||||
}
|
||||
}
|
||||
|
||||
@ -191,12 +191,11 @@ extension MainMenu {
|
||||
}
|
||||
|
||||
@objc func openPhpInfo() {
|
||||
var url: URL?
|
||||
|
||||
asyncWithBusyUI {
|
||||
url = Actions.createTempPhpInfoFile()
|
||||
} completion: {
|
||||
if url != nil { NSWorkspace.shared.open(url!) }
|
||||
Task {
|
||||
let url = await Actions.createTempPhpInfoFile()
|
||||
NSWorkspace.shared.open(url)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -74,7 +74,7 @@ extension MainMenu {
|
||||
}
|
||||
|
||||
if behaviours.contains(.broadcastServicesUpdate) {
|
||||
ServicesManager.shared.loadData()
|
||||
Task { await ServicesManager.loadHomebrewServices() }
|
||||
}
|
||||
|
||||
if error != nil {
|
||||
|
@ -21,18 +21,18 @@ extension MainMenu {
|
||||
await App.shared.environment.process()
|
||||
|
||||
if await Startup().checkEnvironment() {
|
||||
self.onEnvironmentPass()
|
||||
await self.onEnvironmentPass()
|
||||
} else {
|
||||
self.onEnvironmentFail()
|
||||
await self.onEnvironmentFail()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
When the environment is all clear and the app can run, let's go.
|
||||
*/
|
||||
private func onEnvironmentPass() {
|
||||
private func onEnvironmentPass() async {
|
||||
// Determine what the `php` formula is aliased to
|
||||
PhpEnv.shared.determinePhpAlias()
|
||||
await PhpEnv.shared.determinePhpAlias()
|
||||
|
||||
// Determine install method
|
||||
Log.info(HomebrewDiagnostics.customCaskInstalled
|
||||
@ -49,7 +49,7 @@ extension MainMenu {
|
||||
Valet.shared.validateVersion()
|
||||
|
||||
// Actually detect the PHP versions
|
||||
PhpEnv.detectPhpVersions()
|
||||
await PhpEnv.detectPhpVersions()
|
||||
|
||||
// Check for an alias conflict
|
||||
HomebrewDiagnostics.checkForCaskConflict()
|
||||
@ -79,7 +79,7 @@ extension MainMenu {
|
||||
App.shared.loadGlobalHotkey()
|
||||
|
||||
// Preload sites
|
||||
Valet.shared.startPreloadingSites()
|
||||
await Valet.shared.startPreloadingSites()
|
||||
|
||||
// After preloading sites, check for PHP-FPM pool conflicts
|
||||
HomebrewDiagnostics.checkForPhpFpmPoolConflicts()
|
||||
@ -88,7 +88,7 @@ extension MainMenu {
|
||||
Valet.notifyAboutUnsupportedTLD()
|
||||
|
||||
// Find out which services are active
|
||||
ServicesManager.shared.loadData()
|
||||
await ServicesManager.loadHomebrewServices()
|
||||
|
||||
// Start the background refresh timer
|
||||
startSharedTimer()
|
||||
@ -111,9 +111,7 @@ extension MainMenu {
|
||||
}
|
||||
|
||||
// Check for updates
|
||||
DispatchQueue.global(qos: .utility).async {
|
||||
AppUpdateChecker.checkIfNewerVersionIsAvailable()
|
||||
}
|
||||
await AppUpdateChecker.checkIfNewerVersionIsAvailable()
|
||||
|
||||
// We are ready!
|
||||
Log.info("PHP Monitor is ready to serve!")
|
||||
@ -122,9 +120,8 @@ extension MainMenu {
|
||||
/**
|
||||
When the environment is not OK, present an alert to inform the user.
|
||||
*/
|
||||
private func onEnvironmentFail() {
|
||||
private func onEnvironmentFail() async {
|
||||
DispatchQueue.main.async { [self] in
|
||||
|
||||
BetterAlert()
|
||||
.withInformation(
|
||||
title: "alert.cannot_start.title".localized,
|
||||
|
@ -19,36 +19,38 @@ extension MainMenu {
|
||||
PhpEnv.shared.isBusy = false
|
||||
|
||||
// Reload the site list
|
||||
self.reloadDomainListData()
|
||||
Task {
|
||||
await self.reloadDomainListData()
|
||||
|
||||
// Perform UI updates on main thread
|
||||
DispatchQueue.main.async { [self] in
|
||||
updatePhpVersionInStatusBar()
|
||||
rebuild()
|
||||
// Perform UI updates on main thread
|
||||
DispatchQueue.main.async { [self] in
|
||||
updatePhpVersionInStatusBar()
|
||||
rebuild()
|
||||
|
||||
if !PhpEnv.shared.validate(version) {
|
||||
self.suggestFixMyValet(failed: version)
|
||||
return
|
||||
if !PhpEnv.shared.validate(version) {
|
||||
self.suggestFixMyValet(failed: version)
|
||||
return
|
||||
}
|
||||
|
||||
// Run composer updates
|
||||
if Preferences.isEnabled(.autoComposerGlobalUpdateAfterSwitch) {
|
||||
ComposerWindow().updateGlobalDependencies(
|
||||
notify: false,
|
||||
completion: { _ in
|
||||
self.notifyAboutVersionChange(to: version)
|
||||
}
|
||||
)
|
||||
} else {
|
||||
self.notifyAboutVersionChange(to: version)
|
||||
}
|
||||
|
||||
// Check if Valet still works correctly
|
||||
self.checkForPlatformIssues()
|
||||
|
||||
// Update stats
|
||||
Stats.incrementSuccessfulSwitchCount()
|
||||
Stats.evaluateSponsorMessageShouldBeDisplayed()
|
||||
}
|
||||
|
||||
// Run composer updates
|
||||
if Preferences.isEnabled(.autoComposerGlobalUpdateAfterSwitch) {
|
||||
ComposerWindow().updateGlobalDependencies(
|
||||
notify: false,
|
||||
completion: { _ in
|
||||
self.notifyAboutVersionChange(to: version)
|
||||
}
|
||||
)
|
||||
} else {
|
||||
self.notifyAboutVersionChange(to: version)
|
||||
}
|
||||
|
||||
// Check if Valet still works correctly
|
||||
self.checkForPlatformIssues()
|
||||
|
||||
// Update stats
|
||||
Stats.incrementSuccessfulSwitchCount()
|
||||
Stats.evaluateSponsorMessageShouldBeDisplayed()
|
||||
}
|
||||
}
|
||||
|
||||
@ -102,25 +104,21 @@ extension MainMenu {
|
||||
.show()
|
||||
}
|
||||
|
||||
private func reloadDomainListData() {
|
||||
private func reloadDomainListData() async {
|
||||
if let window = App.shared.domainListWindowController {
|
||||
DispatchQueue.main.async {
|
||||
window.contentVC.reloadDomains()
|
||||
}
|
||||
await window.contentVC.reloadDomains()
|
||||
} else {
|
||||
Valet.shared.reloadSites()
|
||||
await Valet.shared.reloadSites()
|
||||
}
|
||||
}
|
||||
|
||||
private func notifyAboutVersionChange(to version: String) {
|
||||
DispatchQueue.main.async {
|
||||
LocalNotification.send(
|
||||
title: String(format: "notification.version_changed_title".localized, version),
|
||||
subtitle: String(format: "notification.version_changed_desc".localized, version),
|
||||
preference: .notifyAboutVersionChange
|
||||
)
|
||||
@MainActor private func notifyAboutVersionChange(to version: String) {
|
||||
LocalNotification.send(
|
||||
title: String(format: "notification.version_changed_title".localized, version),
|
||||
subtitle: String(format: "notification.version_changed_desc".localized, version),
|
||||
preference: .notifyAboutVersionChange
|
||||
)
|
||||
|
||||
PhpEnv.phpInstall.notifyAboutBrokenPhpFpm()
|
||||
}
|
||||
Task { await PhpEnv.phpInstall.notifyAboutBrokenPhpFpm() }
|
||||
}
|
||||
}
|
||||
|
@ -103,11 +103,11 @@ class MainMenu: NSObject, NSWindowDelegate, NSMenuDelegate, PhpSwitcherDelegate
|
||||
Reloads the menu in the foreground.
|
||||
This mimics the exact behaviours of `asyncExecution` as set in the method below.
|
||||
*/
|
||||
@objc func reloadPhpMonitorMenuInForeground() {
|
||||
@MainActor @objc func reloadPhpMonitorMenuInForeground() async {
|
||||
refreshActiveInstallation()
|
||||
refreshIcon()
|
||||
rebuild(async: false)
|
||||
ServicesManager.shared.loadData()
|
||||
await ServicesManager.loadHomebrewServices()
|
||||
}
|
||||
|
||||
/**
|
||||
@ -185,10 +185,8 @@ class MainMenu: NSObject, NSWindowDelegate, NSMenuDelegate, PhpSwitcherDelegate
|
||||
NSApplication.shared.terminate(nil)
|
||||
}
|
||||
|
||||
@objc func checkForUpdates() {
|
||||
DispatchQueue.global(qos: .userInitiated).async {
|
||||
AppUpdateChecker.checkIfNewerVersionIsAvailable(initiatedFromBackground: false)
|
||||
}
|
||||
@objc func checkForUpdates() async {
|
||||
await AppUpdateChecker.checkIfNewerVersionIsAvailable(initiatedFromBackground: false)
|
||||
}
|
||||
|
||||
// MARK: - Menu Delegate
|
||||
@ -196,7 +194,9 @@ class MainMenu: NSObject, NSWindowDelegate, NSMenuDelegate, PhpSwitcherDelegate
|
||||
func menuWillOpen(_ menu: NSMenu) {
|
||||
// Make sure the shortcut key does not trigger this when the menu is open
|
||||
App.shared.shortcutHotkey?.isPaused = true
|
||||
ServicesManager.shared.loadData()
|
||||
Task {
|
||||
await ServicesManager.loadHomebrewServices()
|
||||
}
|
||||
}
|
||||
|
||||
func menuDidClose(_ menu: NSMenu) {
|
||||
|
@ -74,5 +74,4 @@ class BetterAlertVC: NSViewController {
|
||||
self.view.window?.close()
|
||||
NSApplication.shared.stopModal(withCode: code)
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -19,7 +19,13 @@ extension ActivePhpInstallation {
|
||||
This method actively presents a modal if said checks fails, so don't call this method too many times.
|
||||
*/
|
||||
public func notifyAboutBrokenPhpFpm() {
|
||||
if !self.checkPhpFpmStatus() {
|
||||
Task {
|
||||
let fpmStatusConfiguredCorrectly = await self.checkPhpFpmStatus()
|
||||
|
||||
if fpmStatusConfiguredCorrectly {
|
||||
return
|
||||
}
|
||||
|
||||
DispatchQueue.main.async {
|
||||
BetterAlert()
|
||||
.withInformation(
|
||||
|
@ -26,7 +26,7 @@ struct CustomPrefs: Decodable {
|
||||
return self.environmentVariables != nil && !self.environmentVariables!.keys.isEmpty
|
||||
}
|
||||
|
||||
@available(*, deprecated, message: "Use `setCustomEnvironmentVariables` instead")
|
||||
// TODO: Rework this
|
||||
public func getEnvironmentVariables() -> String {
|
||||
return self.environmentVariables!.map { (key, value) in
|
||||
return "export \(key)=\(value)"
|
||||
@ -42,12 +42,12 @@ struct CustomPrefs: Decodable {
|
||||
}
|
||||
|
||||
extension Preferences {
|
||||
func loadCustomPreferences() {
|
||||
func loadCustomPreferences() async {
|
||||
// Ensure the configuration directory is created if missing
|
||||
LegacyShell.run("mkdir -p ~/.config/phpmon")
|
||||
await Shell.quiet("mkdir -p ~/.config/phpmon")
|
||||
|
||||
// Move the legacy file
|
||||
moveOutdatedConfigurationFile()
|
||||
await moveOutdatedConfigurationFile()
|
||||
|
||||
// Attempt to load the file if it exists
|
||||
let url = URL(fileURLWithPath: "\(Paths.homePath)/.config/phpmon/config.json")
|
||||
@ -60,10 +60,10 @@ extension Preferences {
|
||||
}
|
||||
}
|
||||
|
||||
func moveOutdatedConfigurationFile() {
|
||||
func moveOutdatedConfigurationFile() async {
|
||||
if Filesystem.fileExists("~/.phpmon.conf.json") && !Filesystem.fileExists("~/.config/phpmon/config.json") {
|
||||
Log.info("An outdated configuration file was found. Moving it...")
|
||||
LegacyShell.run("cp ~/.phpmon.conf.json ~/.config/phpmon/config.json")
|
||||
await Shell.quiet("cp ~/.phpmon.conf.json ~/.config/phpmon/config.json")
|
||||
Log.info("The configuration file was copied successfully!")
|
||||
}
|
||||
}
|
||||
@ -87,7 +87,9 @@ extension Preferences {
|
||||
|
||||
if customPreferences.hasEnvironmentVariables() {
|
||||
Log.info("Configuring the additional exports...")
|
||||
LegacyShell.user.exports = customPreferences.getEnvironmentVariables()
|
||||
if let shell = Shell as? SystemShell {
|
||||
shell.exports = customPreferences.getEnvironmentVariables()
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
Log.warn("The ~/.config/phpmon/config.json file seems to be missing or malformed.")
|
||||
|
@ -27,7 +27,10 @@ class Preferences {
|
||||
services: [],
|
||||
environmentVariables: [:]
|
||||
)
|
||||
loadCustomPreferences()
|
||||
|
||||
Task {
|
||||
await loadCustomPreferences()
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - First Time Run
|
||||
|
@ -67,19 +67,19 @@ struct Preset: Codable, Equatable {
|
||||
/**
|
||||
Applies a given preset.
|
||||
*/
|
||||
public func apply() {
|
||||
public func apply() async {
|
||||
Task {
|
||||
// Was this a rollback?
|
||||
let wasRollback = (self.name == "AutomaticRevertSnapshot")
|
||||
|
||||
// Save the preset that would revert this preset
|
||||
self.persistRevert()
|
||||
await self.persistRevert()
|
||||
|
||||
// Apply the PHP version if is considered a valid version
|
||||
if self.version != nil {
|
||||
if await !switchToPhpVersionIfValid() {
|
||||
PresetHelper.rollbackPreset = nil
|
||||
Actions.restartPhpFpm()
|
||||
await Actions.restartPhpFpm()
|
||||
return
|
||||
}
|
||||
}
|
||||
@ -94,7 +94,7 @@ struct Preset: Codable, Equatable {
|
||||
for foundExt in PhpEnv.phpInstall.extensions
|
||||
where foundExt.name == ext.key && foundExt.enabled != ext.value {
|
||||
Log.info("Toggling extension \(foundExt.name) in \(foundExt.file)")
|
||||
foundExt.toggle()
|
||||
await foundExt.toggle()
|
||||
break
|
||||
}
|
||||
}
|
||||
@ -103,7 +103,7 @@ struct Preset: Codable, Equatable {
|
||||
PresetHelper.loadRollbackPresetFromFile()
|
||||
|
||||
// Restart PHP FPM process (also reloads menu, which will show the preset rollback)
|
||||
Actions.restartPhpFpm()
|
||||
await Actions.restartPhpFpm()
|
||||
|
||||
// Show the correct notification
|
||||
if wasRollback {
|
||||
@ -257,10 +257,10 @@ struct Preset: Codable, Equatable {
|
||||
/**
|
||||
Persists the revert as a JSON file, so it can be read from a file after restarting PHP Monitor.
|
||||
*/
|
||||
private func persistRevert() {
|
||||
private func persistRevert() async {
|
||||
let data = try! JSONEncoder().encode(self.revertSnapshot)
|
||||
|
||||
LegacyShell.run("mkdir -p ~/.config/phpmon")
|
||||
await Shell.quiet("mkdir -p ~/.config/phpmon")
|
||||
|
||||
try! String(data: data, encoding: .utf8)!
|
||||
.write(
|
||||
|
@ -91,15 +91,13 @@ struct CheckmarkView: View {
|
||||
return nil
|
||||
}
|
||||
|
||||
public func toggleService() {
|
||||
public func toggleService() async {
|
||||
if active()! {
|
||||
Actions.stopService(name: serviceName, completion: {
|
||||
busy = false
|
||||
})
|
||||
await Actions.stopService(name: serviceName)
|
||||
busy = false
|
||||
} else {
|
||||
Actions.startService(name: serviceName, completion: {
|
||||
busy = false
|
||||
})
|
||||
await Actions.startService(name: serviceName)
|
||||
busy = false
|
||||
}
|
||||
}
|
||||
|
||||
@ -121,7 +119,7 @@ struct CheckmarkView: View {
|
||||
} else {
|
||||
Button {
|
||||
busy = true
|
||||
toggleService()
|
||||
Task { await toggleService() }
|
||||
} label: {
|
||||
Image(systemName: active()! ? "checkmark" : "xmark")
|
||||
.resizable()
|
||||
|
@ -25,7 +25,7 @@ class SystemShell: Shellable {
|
||||
Exports are additional environment variables set by the user via the custom configuration.
|
||||
These are populated when the configuration file is being loaded.
|
||||
*/
|
||||
private(set) var exports: String = ""
|
||||
var exports: String = ""
|
||||
|
||||
/** Retrieves the user's PATH by opening an interactive shell and echoing $PATH. */
|
||||
private static func getPath() -> String {
|
||||
|
@ -33,6 +33,8 @@ public class TestableShell: Shellable {
|
||||
didReceiveOutput: @escaping (String, ShellStream) -> Void,
|
||||
withTimeout timeout: TimeInterval
|
||||
) async throws -> (Process, ShellOutput) {
|
||||
assert(expectations.keys.contains(command), "No response declared for command: \(command)")
|
||||
|
||||
guard let expectation = expectations[command] else {
|
||||
return (Process(), .err("No Expected Output"))
|
||||
}
|
||||
|
Reference in New Issue
Block a user