From e2ab7f08edcc787bed69173205c4886ddd1316cb Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Thu, 6 Oct 2022 22:55:05 +0200 Subject: [PATCH] =?UTF-8?q?=F0=9F=8F=97=20Remove=20LegacyShell=20entirely?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- PHP Monitor.xcodeproj/project.pbxproj | 20 -- phpmon/Common/Core/Actions.swift | 64 +++---- phpmon/Common/Core/Helpers.swift | 20 +- phpmon/Common/Core/Paths.swift | 8 +- phpmon/Common/PHP/ActivePhpInstallation.swift | 5 +- phpmon/Common/PHP/PHP Version/PhpEnv.swift | 28 +-- phpmon/Common/PHP/PHP Version/PhpHelper.swift | 108 +++++------ phpmon/Common/PHP/PhpExtension.swift | 4 +- .../PHP/Switcher/InternalSwitcher.swift | 46 ++--- .../Pending Removal/LegacyShell+PATH.swift | 33 ---- .../Common/Pending Removal/LegacyShell.swift | 175 ------------------ .../Domain/App/AppDelegate+MenuOutlets.swift | 18 +- phpmon/Domain/App/AppDelegate.swift | 16 +- phpmon/Domain/App/AppUpdateChecker.swift | 10 +- phpmon/Domain/App/InterAppHandler.swift | 8 +- phpmon/Domain/App/ServicesManager.swift | 53 +++--- phpmon/Domain/DomainList/AddProxyVC.swift | 6 +- phpmon/Domain/DomainList/AddSiteVC.swift | 8 +- .../DomainList/DomainListVC+Actions.swift | 27 ++- phpmon/Domain/DomainList/DomainListVC.swift | 23 ++- .../DomainListWindowController.swift | 4 +- .../Composer/ComposerWindow.swift | 9 +- .../Homebrew/HomebrewDiagnostics.swift | 10 +- phpmon/Domain/Integrations/Valet/Valet.swift | 6 +- phpmon/Domain/Menu/MainMenu+Actions.swift | 55 +++--- phpmon/Domain/Menu/MainMenu+Async.swift | 2 +- phpmon/Domain/Menu/MainMenu+Startup.swift | 21 +-- phpmon/Domain/Menu/MainMenu+Switcher.swift | 80 ++++---- phpmon/Domain/Menu/MainMenu.swift | 14 +- phpmon/Domain/Notice/BetterAlertVC.swift | 1 - .../PHP/ActivePhpInstallation+Checks.swift | 8 +- phpmon/Domain/Preferences/CustomPrefs.swift | 16 +- phpmon/Domain/Preferences/Preferences.swift | 5 +- phpmon/Domain/Presets/Preset.swift | 14 +- phpmon/Domain/SwiftUI/Menu/ServicesView.swift | 14 +- phpmon/Next/SystemShell.swift | 2 +- phpmon/Next/TestableShell.swift | 2 + 37 files changed, 359 insertions(+), 584 deletions(-) delete mode 100644 phpmon/Common/Pending Removal/LegacyShell+PATH.swift delete mode 100644 phpmon/Common/Pending Removal/LegacyShell.swift diff --git a/PHP Monitor.xcodeproj/project.pbxproj b/PHP Monitor.xcodeproj/project.pbxproj index 9eed9bf..deb4580 100644 --- a/PHP Monitor.xcodeproj/project.pbxproj +++ b/PHP Monitor.xcodeproj/project.pbxproj @@ -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 = ""; }; C42CFB1727DFDFDC00862737 /* nginx-site-isolated.test */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "nginx-site-isolated.test"; sourceTree = ""; }; C42CFB1927DFE8BD00862737 /* NginxConfigurationTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NginxConfigurationTest.swift; sourceTree = ""; }; - C42E3BF328A9BF5100AFECFC /* LegacyShell+PATH.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "LegacyShell+PATH.swift"; sourceTree = ""; }; C42F26722805B4B400938AC7 /* DomainListable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DomainListable.swift; sourceTree = ""; }; C42F26752805FEE200938AC7 /* nginx-secure-proxy.test */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "nginx-secure-proxy.test"; sourceTree = ""; }; C436039F275E67610028EFC6 /* AppDelegate+Notifications.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AppDelegate+Notifications.swift"; sourceTree = ""; }; @@ -470,7 +465,6 @@ C4B5635D276AB09000F12CCB /* VersionExtractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VersionExtractor.swift; sourceTree = ""; }; C4B56360276AB0A500F12CCB /* VersionExtractorTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VersionExtractorTest.swift; sourceTree = ""; }; C4B5853B2770FE3900DA4FBE /* Paths.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Paths.swift; sourceTree = ""; }; - C4B5853C2770FE3900DA4FBE /* LegacyShell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LegacyShell.swift; sourceTree = ""; }; C4B5853D2770FE3900DA4FBE /* Command.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Command.swift; sourceTree = ""; }; C4B609192853AAD300C95265 /* SectionHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SectionHeaderView.swift; sourceTree = ""; }; C4B6091C2853AB9700C95265 /* ServicesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServicesView.swift; sourceTree = ""; }; @@ -897,15 +891,6 @@ path = Next; sourceTree = ""; }; - C46EBC4C28DB9F43007ACC74 /* Pending Removal */ = { - isa = PBXGroup; - children = ( - C4B5853C2770FE3900DA4FBE /* LegacyShell.swift */, - C42E3BF328A9BF5100AFECFC /* LegacyShell+PATH.swift */, - ); - path = "Pending Removal"; - sourceTree = ""; - }; 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 */, diff --git a/phpmon/Common/Core/Actions.swift b/phpmon/Common/Core/Actions.swift index 30131ee..0960a79 100644 --- a/phpmon/Common/Core/Actions.swift +++ b/phpmon/Common/Core/Actions.swift @@ -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! " /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() + } }) } } diff --git a/phpmon/Common/Core/Helpers.swift b/phpmon/Common/Core/Helpers.swift index 43fa168..ba20a29 100644 --- a/phpmon/Common/Core/Helpers.swift +++ b/phpmon/Common/Core/Helpers.swift @@ -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") } diff --git a/phpmon/Common/Core/Paths.swift b/phpmon/Common/Core/Paths.swift index ac34932..bdf7610 100644 --- a/phpmon/Common/Core/Paths.swift +++ b/phpmon/Common/Core/Paths.swift @@ -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() { diff --git a/phpmon/Common/PHP/ActivePhpInstallation.swift b/phpmon/Common/PHP/ActivePhpInstallation.swift index b3cd72d..00a0c4a 100644 --- a/phpmon/Common/PHP/ActivePhpInstallation.swift +++ b/phpmon/Common/PHP/ActivePhpInstallation.swift @@ -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 :) diff --git a/phpmon/Common/PHP/PHP Version/PhpEnv.swift b/phpmon/Common/PHP/PHP Version/PhpEnv.swift index 8eb7be3..7d78d5f 100644 --- a/phpmon/Common/PHP/PHP Version/PhpEnv.swift +++ b/phpmon/Common/PHP/PHP Version/PhpEnv.swift @@ -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 diff --git a/phpmon/Common/PHP/PHP Version/PhpHelper.swift b/phpmon/Common/PHP/PHP Version/PhpHelper.swift index 1165fa1..b0a8064 100644 --- a/phpmon/Common/PHP/PHP Version/PhpHelper.swift +++ b/phpmon/Common/PHP/PHP Version/PhpHelper.swift @@ -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 } diff --git a/phpmon/Common/PHP/PhpExtension.swift b/phpmon/Common/PHP/PhpExtension.swift index 84a63a9..b6e7d7e 100644 --- a/phpmon/Common/PHP/PhpExtension.swift +++ b/phpmon/Common/PHP/PhpExtension.swift @@ -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() } diff --git a/phpmon/Common/PHP/Switcher/InternalSwitcher.swift b/phpmon/Common/PHP/Switcher/InternalSwitcher.swift index b125140..58a1d67 100644 --- a/phpmon/Common/PHP/Switcher/InternalSwitcher.swift +++ b/phpmon/Common/PHP/Switcher/InternalSwitcher.swift @@ -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).") } diff --git a/phpmon/Common/Pending Removal/LegacyShell+PATH.swift b/phpmon/Common/Pending Removal/LegacyShell+PATH.swift deleted file mode 100644 index 3d7e2ee..0000000 --- a/phpmon/Common/Pending Removal/LegacyShell+PATH.swift +++ /dev/null @@ -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) ?? "" - } -} diff --git a/phpmon/Common/Pending Removal/LegacyShell.swift b/phpmon/Common/Pending Removal/LegacyShell.swift deleted file mode 100644 index f364d76..0000000 --- a/phpmon/Common/Pending Removal/LegacyShell.swift +++ /dev/null @@ -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 - } - } -} diff --git a/phpmon/Domain/App/AppDelegate+MenuOutlets.swift b/phpmon/Domain/App/AppDelegate+MenuOutlets.swift index acd41bc..0d30925 100644 --- a/phpmon/Domain/App/AppDelegate+MenuOutlets.swift +++ b/phpmon/Domain/App/AppDelegate+MenuOutlets.swift @@ -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() + } } } diff --git a/phpmon/Domain/App/AppDelegate.swift b/phpmon/Domain/App/AppDelegate.swift index 8348b12..c34fc74 100644 --- a/phpmon/Domain/App/AppDelegate.swift +++ b/phpmon/Domain/App/AppDelegate.swift @@ -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() + } } } diff --git a/phpmon/Domain/App/AppUpdateChecker.swift b/phpmon/Domain/App/AppUpdateChecker.swift index b2d614d..c25ec1f 100644 --- a/phpmon/Domain/App/AppUpdateChecker.swift +++ b/phpmon/Domain/App/AppUpdateChecker.swift @@ -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!") diff --git a/phpmon/Domain/App/InterAppHandler.swift b/phpmon/Domain/App/InterAppHandler.swift index 73e6477..5c0b365 100644 --- a/phpmon/Domain/App/InterAppHandler.swift +++ b/phpmon/Domain/App/InterAppHandler.swift @@ -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() diff --git a/phpmon/Domain/App/ServicesManager.swift b/phpmon/Domain/App/ServicesManager.swift index d274764..00d4fd0 100644 --- a/phpmon/Domain/App/ServicesManager.swift +++ b/phpmon/Domain/App/ServicesManager.swift @@ -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) } + ) } /** diff --git a/phpmon/Domain/DomainList/AddProxyVC.swift b/phpmon/Domain/DomainList/AddProxyVC.swift index 0733fd1..d926e64 100644 --- a/phpmon/Domain/DomainList/AddProxyVC.swift +++ b/phpmon/Domain/DomainList/AddProxyVC.swift @@ -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() diff --git a/phpmon/Domain/DomainList/AddSiteVC.swift b/phpmon/Domain/DomainList/AddSiteVC.swift index 8275d27..1af3f1d 100644 --- a/phpmon/Domain/DomainList/AddSiteVC.swift +++ b/phpmon/Domain/DomainList/AddSiteVC.swift @@ -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, diff --git a/phpmon/Domain/DomainList/DomainListVC+Actions.swift b/phpmon/Domain/DomainList/DomainListVC+Actions.swift index 23f17ed..59b4823 100644 --- a/phpmon/Domain/DomainList/DomainListVC+Actions.swift +++ b/phpmon/Domain/DomainList/DomainListVC+Actions.swift @@ -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]) diff --git a/phpmon/Domain/DomainList/DomainListVC.swift b/phpmon/Domain/DomainList/DomainListVC.swift index d3a26cd..b482b27 100644 --- a/phpmon/Domain/DomainList/DomainListVC.swift +++ b/phpmon/Domain/DomainList/DomainListVC.swift @@ -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) } diff --git a/phpmon/Domain/DomainList/DomainListWindowController.swift b/phpmon/Domain/DomainList/DomainListWindowController.swift index df92f75..947bb54 100644 --- a/phpmon/Domain/DomainList/DomainListWindowController.swift +++ b/phpmon/Domain/DomainList/DomainListWindowController.swift @@ -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?) { diff --git a/phpmon/Domain/Integrations/Composer/ComposerWindow.swift b/phpmon/Domain/Integrations/Composer/ComposerWindow.swift index 81a67a3..010959f 100644 --- a/phpmon/Domain/Integrations/Composer/ComposerWindow.swift +++ b/phpmon/Domain/Integrations/Composer/ComposerWindow.swift @@ -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 { diff --git a/phpmon/Domain/Integrations/Homebrew/HomebrewDiagnostics.swift b/phpmon/Domain/Integrations/Homebrew/HomebrewDiagnostics.swift index 39ac784..a424bf8 100644 --- a/phpmon/Domain/Integrations/Homebrew/HomebrewDiagnostics.swift +++ b/phpmon/Domain/Integrations/Homebrew/HomebrewDiagnostics.swift @@ -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) + } } } diff --git a/phpmon/Domain/Integrations/Valet/Valet.swift b/phpmon/Domain/Integrations/Valet/Valet.swift index f023662..9dcea5e 100644 --- a/phpmon/Domain/Integrations/Valet/Valet.swift +++ b/phpmon/Domain/Integrations/Valet/Valet.swift @@ -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 { diff --git a/phpmon/Domain/Menu/MainMenu+Actions.swift b/phpmon/Domain/Menu/MainMenu+Actions.swift index 3f696b6..0e66a57 100644 --- a/phpmon/Domain/Menu/MainMenu+Actions.swift +++ b/phpmon/Domain/Menu/MainMenu+Actions.swift @@ -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) + } } } diff --git a/phpmon/Domain/Menu/MainMenu+Async.swift b/phpmon/Domain/Menu/MainMenu+Async.swift index 8c82f3c..b81c9f3 100644 --- a/phpmon/Domain/Menu/MainMenu+Async.swift +++ b/phpmon/Domain/Menu/MainMenu+Async.swift @@ -74,7 +74,7 @@ extension MainMenu { } if behaviours.contains(.broadcastServicesUpdate) { - ServicesManager.shared.loadData() + Task { await ServicesManager.loadHomebrewServices() } } if error != nil { diff --git a/phpmon/Domain/Menu/MainMenu+Startup.swift b/phpmon/Domain/Menu/MainMenu+Startup.swift index 901f14e..c20b650 100644 --- a/phpmon/Domain/Menu/MainMenu+Startup.swift +++ b/phpmon/Domain/Menu/MainMenu+Startup.swift @@ -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, diff --git a/phpmon/Domain/Menu/MainMenu+Switcher.swift b/phpmon/Domain/Menu/MainMenu+Switcher.swift index fc55377..e8427a0 100644 --- a/phpmon/Domain/Menu/MainMenu+Switcher.swift +++ b/phpmon/Domain/Menu/MainMenu+Switcher.swift @@ -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() } } } diff --git a/phpmon/Domain/Menu/MainMenu.swift b/phpmon/Domain/Menu/MainMenu.swift index f1ca90b..e41fcfc 100644 --- a/phpmon/Domain/Menu/MainMenu.swift +++ b/phpmon/Domain/Menu/MainMenu.swift @@ -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) { diff --git a/phpmon/Domain/Notice/BetterAlertVC.swift b/phpmon/Domain/Notice/BetterAlertVC.swift index c6c3bdc..aad5ff4 100644 --- a/phpmon/Domain/Notice/BetterAlertVC.swift +++ b/phpmon/Domain/Notice/BetterAlertVC.swift @@ -74,5 +74,4 @@ class BetterAlertVC: NSViewController { self.view.window?.close() NSApplication.shared.stopModal(withCode: code) } - } diff --git a/phpmon/Domain/PHP/ActivePhpInstallation+Checks.swift b/phpmon/Domain/PHP/ActivePhpInstallation+Checks.swift index fb30e73..c126f20 100644 --- a/phpmon/Domain/PHP/ActivePhpInstallation+Checks.swift +++ b/phpmon/Domain/PHP/ActivePhpInstallation+Checks.swift @@ -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( diff --git a/phpmon/Domain/Preferences/CustomPrefs.swift b/phpmon/Domain/Preferences/CustomPrefs.swift index 12f6b82..66e323f 100644 --- a/phpmon/Domain/Preferences/CustomPrefs.swift +++ b/phpmon/Domain/Preferences/CustomPrefs.swift @@ -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.") diff --git a/phpmon/Domain/Preferences/Preferences.swift b/phpmon/Domain/Preferences/Preferences.swift index d60b9f0..02a5e1e 100644 --- a/phpmon/Domain/Preferences/Preferences.swift +++ b/phpmon/Domain/Preferences/Preferences.swift @@ -27,7 +27,10 @@ class Preferences { services: [], environmentVariables: [:] ) - loadCustomPreferences() + + Task { + await loadCustomPreferences() + } } // MARK: - First Time Run diff --git a/phpmon/Domain/Presets/Preset.swift b/phpmon/Domain/Presets/Preset.swift index f88d20a..bd7eeae 100644 --- a/phpmon/Domain/Presets/Preset.swift +++ b/phpmon/Domain/Presets/Preset.swift @@ -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( diff --git a/phpmon/Domain/SwiftUI/Menu/ServicesView.swift b/phpmon/Domain/SwiftUI/Menu/ServicesView.swift index db87a04..1b09703 100644 --- a/phpmon/Domain/SwiftUI/Menu/ServicesView.swift +++ b/phpmon/Domain/SwiftUI/Menu/ServicesView.swift @@ -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() diff --git a/phpmon/Next/SystemShell.swift b/phpmon/Next/SystemShell.swift index ec8c3b8..17a214a 100644 --- a/phpmon/Next/SystemShell.swift +++ b/phpmon/Next/SystemShell.swift @@ -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 { diff --git a/phpmon/Next/TestableShell.swift b/phpmon/Next/TestableShell.swift index 85eadd2..1a97a10 100644 --- a/phpmon/Next/TestableShell.swift +++ b/phpmon/Next/TestableShell.swift @@ -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")) }