From 0b05bb44a2ee9d786370e839c335b685bbfe4e75 Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Thu, 23 Dec 2021 12:29:12 +0100 Subject: [PATCH 1/4] =?UTF-8?q?=F0=9F=90=9B=20Fix=20initialization=20of=20?= =?UTF-8?q?Site=20objects=20(#74)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- PHP Monitor.xcodeproj/project.pbxproj | 8 ++++---- phpmon/Constants.swift | 2 -- phpmon/Domain/Integrations/Valet/Valet.swift | 10 ++++++++-- 3 files changed, 12 insertions(+), 8 deletions(-) diff --git a/PHP Monitor.xcodeproj/project.pbxproj b/PHP Monitor.xcodeproj/project.pbxproj index 861747b..0756a56 100644 --- a/PHP Monitor.xcodeproj/project.pbxproj +++ b/PHP Monitor.xcodeproj/project.pbxproj @@ -825,7 +825,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 135; + CURRENT_PROJECT_VERSION = 136; DEVELOPMENT_TEAM = 8M54J5J787; ENABLE_HARDENED_RUNTIME = YES; INFOPLIST_FILE = phpmon/Info.plist; @@ -834,7 +834,7 @@ "@executable_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 11.0; - MARKETING_VERSION = 4.1; + MARKETING_VERSION = 4.1.1; PRODUCT_BUNDLE_IDENTIFIER = com.nicoverbruggen.phpmon; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -850,7 +850,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 135; + CURRENT_PROJECT_VERSION = 136; DEVELOPMENT_TEAM = 8M54J5J787; ENABLE_HARDENED_RUNTIME = YES; INFOPLIST_FILE = phpmon/Info.plist; @@ -859,7 +859,7 @@ "@executable_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 11.0; - MARKETING_VERSION = 4.1; + MARKETING_VERSION = 4.1.1; PRODUCT_BUNDLE_IDENTIFIER = com.nicoverbruggen.phpmon; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; diff --git a/phpmon/Constants.swift b/phpmon/Constants.swift index 50f5c27..6bb3f62 100644 --- a/phpmon/Constants.swift +++ b/phpmon/Constants.swift @@ -50,7 +50,5 @@ class Constants { // dev release. In this case, that means that the version below is detected. "8.2" ] - - } diff --git a/phpmon/Domain/Integrations/Valet/Valet.swift b/phpmon/Domain/Integrations/Valet/Valet.swift index 0759406..f0c1a82 100644 --- a/phpmon/Domain/Integrations/Valet/Valet.swift +++ b/phpmon/Domain/Integrations/Valet/Valet.swift @@ -84,6 +84,12 @@ class Valet { // We can also determine whether the thing at the path is a directory, too let type = attrs[FileAttributeKey.type] as! FileAttributeType + // We should also check that we can interpret the path correctly + if URL(fileURLWithPath: siteDir).lastPathComponent == "" { + print("Warning: could not parse the site: \(siteDir), skipping!") + return + } + if type == FileAttributeType.typeSymbolicLink { sites.append(Site(aliasPath: siteDir, tld: tld)) } else if type == FileAttributeType.typeDirectory { @@ -114,7 +120,7 @@ class Valet { convenience init(absolutePath: String, tld: String) { self.init() self.absolutePath = absolutePath - self.name = URL(string: absolutePath)!.lastPathComponent + self.name = URL(fileURLWithPath: absolutePath).lastPathComponent self.aliasPath = nil determineSecured(tld) determineDriver() @@ -123,7 +129,7 @@ class Valet { convenience init(aliasPath: String, tld: String) { self.init() self.absolutePath = try! FileManager.default.destinationOfSymbolicLink(atPath: aliasPath) - self.name = URL(string: aliasPath)!.lastPathComponent + self.name = URL(fileURLWithPath: aliasPath).lastPathComponent self.aliasPath = aliasPath determineSecured(tld) determineDriver() From 6646ceda76565b0f133b90e30fc0ff0e92f76915 Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Thu, 23 Dec 2021 12:52:10 +0100 Subject: [PATCH 2/4] =?UTF-8?q?=F0=9F=90=9B=20Fix=20preloaded=20site=20log?= =?UTF-8?q?ic?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- phpmon/Domain/Integrations/Valet/Valet.swift | 35 ++++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) diff --git a/phpmon/Domain/Integrations/Valet/Valet.swift b/phpmon/Domain/Integrations/Valet/Valet.swift index f0c1a82..d8b88c8 100644 --- a/phpmon/Domain/Integrations/Valet/Valet.swift +++ b/phpmon/Domain/Integrations/Valet/Valet.swift @@ -37,10 +37,14 @@ class Valet { } public func startPreloadingSites() { - if self.sites.count <= 10 { + let maximumPreload = 10 + let foundSites = self.countPaths() + if foundSites <= maximumPreload { // Preload the sites and their drivers - print("Fewer than or 11 sites found, preloading list of sites...") + print("Fewer than or \(maximumPreload) sites found, preloading list of sites...") self.reloadSites() + } else { + print("\(foundSites) sites found, exceeds \(maximumPreload) for preload at launch!") } } @@ -64,6 +68,19 @@ class Valet { } } + private func countPaths() -> Int { + var count = 0 + for path in config.paths { + let entries = try! FileManager.default.contentsOfDirectory(atPath: path) + for entry in entries { + if resolveSite(entry, forPath: path) { + count += 1 + } + } + } + return count + } + private func resolvePaths(tld: String) { sites = [] @@ -75,6 +92,20 @@ class Valet { } } + private func resolveSite(_ entry: String, forPath path: String) -> Bool { + let siteDir = path + "/" + entry + + let attrs = try! FileManager.default.attributesOfItem(atPath: siteDir) + + let type = attrs[FileAttributeKey.type] as! FileAttributeType + + if type == FileAttributeType.typeSymbolicLink || type == FileAttributeType.typeDirectory { + return true + } + + return false + } + private func resolvePath(_ entry: String, forPath path: String, tld: String) { let siteDir = path + "/" + entry From 5af1f09ee11ae74252093e339450af32ecfa9ad6 Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Thu, 23 Dec 2021 13:27:19 +0100 Subject: [PATCH 3/4] =?UTF-8?q?=F0=9F=90=9B=20Ensure=20app=20can=20handle?= =?UTF-8?q?=20interactions=20with=20path=20w/=20spaces=20(#74)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- phpmon/Domain/Helpers/Application.swift | 2 +- phpmon/Domain/Integrations/Valet/Valet.swift | 5 +++-- phpmon/Domain/SiteList/SiteListVC.swift | 21 +++++++++++++++----- phpmon/Localizable.strings | 3 +++ 4 files changed, 23 insertions(+), 8 deletions(-) diff --git a/phpmon/Domain/Helpers/Application.swift b/phpmon/Domain/Helpers/Application.swift index 6bd64f1..6632a9c 100644 --- a/phpmon/Domain/Helpers/Application.swift +++ b/phpmon/Domain/Helpers/Application.swift @@ -34,7 +34,7 @@ class Application { (This will open the app if it isn't open yet.) */ @objc public func openDirectory(file: String) { - return Shell.run("/usr/bin/open -a \"\(name)\" \(file)") + return Shell.run("/usr/bin/open -a \"\(name)\" \"\(file)\"") } /** Checks if the app is installed. */ diff --git a/phpmon/Domain/Integrations/Valet/Valet.swift b/phpmon/Domain/Integrations/Valet/Valet.swift index d8b88c8..128371d 100644 --- a/phpmon/Domain/Integrations/Valet/Valet.swift +++ b/phpmon/Domain/Integrations/Valet/Valet.swift @@ -167,11 +167,12 @@ class Valet { } public func determineSecured(_ tld: String) { - secured = Shell.fileExists("~/.config/valet/Certificates/\(self.name!).\(tld).key") + let name = self.name!.replacingOccurrences(of: " ", with: "\\ ") + secured = Shell.fileExists("~/.config/valet/Certificates/\(name).\(tld).key") } public func determineDriver() { - let driver = Shell.pipe("cd \(absolutePath!) && valet which", requiresPath: true) + let driver = Shell.pipe("cd \"\(absolutePath!)\" && valet which", requiresPath: true) if driver.contains("This site is served by") { self.driver = driver // TODO: Use a regular expression to retrieve the driver instead? diff --git a/phpmon/Domain/SiteList/SiteListVC.swift b/phpmon/Domain/SiteList/SiteListVC.swift index b03fad1..77815ab 100644 --- a/phpmon/Domain/SiteList/SiteListVC.swift +++ b/phpmon/Domain/SiteList/SiteListVC.swift @@ -171,7 +171,7 @@ class SiteListVC: NSViewController, NSTableViewDelegate, NSTableViewDataSource { let originalSecureStatus = selectedSite!.secured let action = selectedSite!.secured ? "unsecure" : "secure" let selectedSite = selectedSite! - let command = "cd \(selectedSite.absolutePath!) && sudo \(Paths.valet) \(action) && exit;" + let command = "cd '\(selectedSite.absolutePath!)' && sudo \(Paths.valet) \(action) && exit;" waitAndExecute { Shell.run(command, requiresPath: true) @@ -204,16 +204,20 @@ class SiteListVC: NSViewController, NSTableViewDelegate, NSTableViewDataSource { @objc public func openInBrowser() { let prefix = selectedSite!.secured ? "https://" : "http://" - let url = "\(prefix)\(selectedSite!.name!).\(Valet.shared.config.tld)" - NSWorkspace.shared.open(URL(string: url)!) + let url = URL(string: "\(prefix)\(selectedSite!.name!).\(Valet.shared.config.tld)") + if url != nil { + NSWorkspace.shared.open(url!) + } else { + warnAboutInvalidFolderAction() + } } @objc public func openInFinder() { - Shell.run("open \(selectedSite!.absolutePath!)") + Shell.run("open '\(selectedSite!.absolutePath!)'") } @objc public func openInTerminal() { - Shell.run("open -b com.apple.terminal \(selectedSite!.absolutePath!)") + Shell.run("open -b com.apple.terminal '\(selectedSite!.absolutePath!)'") } @objc public func unlinkSite() { @@ -239,6 +243,13 @@ class SiteListVC: NSViewController, NSTableViewDelegate, NSTableViewDataSource { ) } + private func warnAboutInvalidFolderAction() { + _ = Alert.present( + messageText: "site_list.alert.invalid_folder_name".localized, + informativeText: "site_list.alert.invalid_folder_name_desc".localized + ) + } + // MARK: - (Search) Text Field Delegate func searchedFor(text: String) { diff --git a/phpmon/Localizable.strings b/phpmon/Localizable.strings index 42050fc..cf9ccda 100644 --- a/phpmon/Localizable.strings +++ b/phpmon/Localizable.strings @@ -74,6 +74,9 @@ "site_list.detected_apps" = "Detected Applications"; "site_list.system_apps" = "System Applications"; +"site_list.alert.invalid_folder_name" = "Invalid folder name"; +"site_list.alert.invalid_folder_name_desc" = "This folder could not be resolved to a valid URL. This is usually because there’s a space in the folder name. Please rename the folder, reload the list of sites, and try again."; + // EDITORS "editors.alert.try_again" = "Try Again"; From b7766aeec2742b8e70b79e28604ab94d71a78cc2 Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Thu, 23 Dec 2021 20:09:23 +0100 Subject: [PATCH 4/4] =?UTF-8?q?=F0=9F=91=8C=20Improve=20UI=20and=20warn=20?= =?UTF-8?q?about=20spaces=20in=20folder=20names?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- PHP Monitor.xcodeproj/project.pbxproj | 6 ++ phpmon/Domain/Core/Base.lproj/Main.storyboard | 29 +++++- phpmon/Domain/Integrations/Valet/Valet.swift | 19 +++- phpmon/Domain/SiteList/SiteListCell.swift | 8 ++ .../Domain/SiteList/SiteListVC+Actions.swift | 97 +++++++++++++++++++ phpmon/Domain/SiteList/SiteListVC.swift | 97 +------------------ phpmon/Domain/Terminal/Shell.swift | 3 +- phpmon/Localizable.strings | 2 + 8 files changed, 161 insertions(+), 100 deletions(-) create mode 100644 phpmon/Domain/SiteList/SiteListVC+Actions.swift diff --git a/PHP Monitor.xcodeproj/project.pbxproj b/PHP Monitor.xcodeproj/project.pbxproj index 0756a56..1460c78 100644 --- a/PHP Monitor.xcodeproj/project.pbxproj +++ b/PHP Monitor.xcodeproj/project.pbxproj @@ -34,6 +34,8 @@ C41C1B4922B00A9800E7CF16 /* MenuBarImageGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41C1B4822B00A9800E7CF16 /* MenuBarImageGenerator.swift */; }; C41C1B4B22B019FF00E7CF16 /* ActivePhpInstallation.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41C1B4A22B019FF00E7CF16 /* ActivePhpInstallation.swift */; }; C41C1B4D22B0215A00E7CF16 /* Actions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41C1B4C22B0215A00E7CF16 /* Actions.swift */; }; + C41CA5ED2774F8EE00A2C80E /* SiteListVC+Actions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41CA5EC2774F8EE00A2C80E /* SiteListVC+Actions.swift */; }; + C41CA5EE2774F8EE00A2C80E /* SiteListVC+Actions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41CA5EC2774F8EE00A2C80E /* SiteListVC+Actions.swift */; }; C41CD0292628D8EE0065BBED /* GlobalKeybindPreference.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41CD0282628D8EE0065BBED /* GlobalKeybindPreference.swift */; }; C41E871A2763D42300161EE0 /* SiteListVC+ContextMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41E87192763D42300161EE0 /* SiteListVC+ContextMenu.swift */; }; C41E871B2763D42300161EE0 /* SiteListVC+ContextMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41E87192763D42300161EE0 /* SiteListVC+ContextMenu.swift */; }; @@ -155,6 +157,7 @@ C41C1B4822B00A9800E7CF16 /* MenuBarImageGenerator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MenuBarImageGenerator.swift; sourceTree = ""; }; C41C1B4A22B019FF00E7CF16 /* ActivePhpInstallation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActivePhpInstallation.swift; sourceTree = ""; }; C41C1B4C22B0215A00E7CF16 /* Actions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Actions.swift; sourceTree = ""; }; + C41CA5EC2774F8EE00A2C80E /* SiteListVC+Actions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SiteListVC+Actions.swift"; sourceTree = ""; }; C41CD0282628D8EE0065BBED /* GlobalKeybindPreference.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GlobalKeybindPreference.swift; sourceTree = ""; }; C41E87192763D42300161EE0 /* SiteListVC+ContextMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SiteListVC+ContextMenu.swift"; sourceTree = ""; }; C42295DC2358D02000E263B2 /* Command.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Command.swift; sourceTree = ""; }; @@ -337,6 +340,7 @@ C464ADAB275A7A3F003FCD53 /* SiteListWC.swift */, C464ADAE275A7A69003FCD53 /* SiteListVC.swift */, C41E87192763D42300161EE0 /* SiteListVC+ContextMenu.swift */, + C41CA5EC2774F8EE00A2C80E /* SiteListVC+Actions.swift */, C464ADB1275A87CA003FCD53 /* SiteListCell.swift */, ); path = SiteList; @@ -597,6 +601,7 @@ C48D0C9325CC804200CC7490 /* XibLoadable.swift in Sources */, 54FCFD2A276C8AA4004CE748 /* CheckboxPreferenceView.swift in Sources */, C4811D2A22D70F9A00B5F6B3 /* MainMenu.swift in Sources */, + C41CA5ED2774F8EE00A2C80E /* SiteListVC+Actions.swift in Sources */, C412E5FC25700D5300A1FB67 /* HomebrewPackage.swift in Sources */, 54AB03262763858F00A29D5F /* Timer.swift in Sources */, C41C1B3722B0097F00E7CF16 /* AppDelegate.swift in Sources */, @@ -624,6 +629,7 @@ buildActionMask = 2147483647; files = ( 54EAC806262F212B0092D14E /* GlobalKeybindPreference.swift in Sources */, + C41CA5EE2774F8EE00A2C80E /* SiteListVC+Actions.swift in Sources */, C4F780C425D80B75000DBC97 /* MainMenu.swift in Sources */, 54AB03272763858F00A29D5F /* Timer.swift in Sources */, 54FCFD2B276C8AA4004CE748 /* CheckboxPreferenceView.swift in Sources */, diff --git a/phpmon/Domain/Core/Base.lproj/Main.storyboard b/phpmon/Domain/Core/Base.lproj/Main.storyboard index 3182914..64ecce7 100644 --- a/phpmon/Domain/Core/Base.lproj/Main.storyboard +++ b/phpmon/Domain/Core/Base.lproj/Main.storyboard @@ -497,13 +497,35 @@ + + + + + + + + + + + + @@ -515,15 +537,19 @@ + + + + @@ -573,12 +599,13 @@ - + + diff --git a/phpmon/Domain/Integrations/Valet/Valet.swift b/phpmon/Domain/Integrations/Valet/Valet.swift index 128371d..6910758 100644 --- a/phpmon/Domain/Integrations/Valet/Valet.swift +++ b/phpmon/Domain/Integrations/Valet/Valet.swift @@ -68,6 +68,9 @@ class Valet { } } + /** + Returns a count of how many sites are linked and parked. + */ private func countPaths() -> Int { var count = 0 for path in config.paths { @@ -81,6 +84,9 @@ class Valet { return count } + /** + Resolves all paths and creates linked or parked site instances that can be referenced later. + */ private func resolvePaths(tld: String) { sites = [] @@ -92,6 +98,10 @@ class Valet { } } + /** + Determines whether the site can be resolved as a symbolic link or as a directory. + Regular files are ignored. Returns true if the path can be parsed. + */ private func resolveSite(_ entry: String, forPath path: String) -> Bool { let siteDir = path + "/" + entry @@ -106,6 +116,10 @@ class Valet { return false } + /** + Determines whether the site can be resolved as a symbolic link or as a directory. + Regular files are ignored, and the site is added to Valet's list of sites. + */ private func resolvePath(_ entry: String, forPath path: String, tld: String) { let siteDir = path + "/" + entry @@ -167,12 +181,11 @@ class Valet { } public func determineSecured(_ tld: String) { - let name = self.name!.replacingOccurrences(of: " ", with: "\\ ") - secured = Shell.fileExists("~/.config/valet/Certificates/\(name).\(tld).key") + secured = Shell.fileExists("~/.config/valet/Certificates/\(self.name!).\(tld).key") } public func determineDriver() { - let driver = Shell.pipe("cd \"\(absolutePath!)\" && valet which", requiresPath: true) + let driver = Shell.pipe("cd '\(absolutePath!)' && valet which", requiresPath: true) if driver.contains("This site is served by") { self.driver = driver // TODO: Use a regular expression to retrieve the driver instead? diff --git a/phpmon/Domain/SiteList/SiteListCell.swift b/phpmon/Domain/SiteList/SiteListCell.swift index 1bcf0f3..4af0450 100644 --- a/phpmon/Domain/SiteList/SiteListCell.swift +++ b/phpmon/Domain/SiteList/SiteListCell.swift @@ -19,6 +19,9 @@ class SiteListCell: NSTableCellView @IBOutlet weak var labelDriver: NSTextField! + @IBOutlet weak var buttonWarning: NSButton! + @IBOutlet weak var labelWarning: NSTextField! + override func draw(_ dirtyRect: NSRect) { super.draw(dirtyRect) } @@ -27,6 +30,11 @@ class SiteListCell: NSTableCellView // Make sure to show the TLD labelSiteName.stringValue = "\(site.name!).\(Valet.shared.config.tld)" + let isProblematic = site.name.contains(" ") + buttonWarning.isHidden = !isProblematic + labelWarning.isHidden = !isProblematic + labelWarning.stringValue = "site_list.warning.spaces".localized + // Show the absolute path, except make sure to replace the /Users/username segment with ~ for readability labelPathName.stringValue = site.absolutePath .replacingOccurrences(of: "/Users/\(Paths.whoami)", with: "~") diff --git a/phpmon/Domain/SiteList/SiteListVC+Actions.swift b/phpmon/Domain/SiteList/SiteListVC+Actions.swift new file mode 100644 index 0000000..b386571 --- /dev/null +++ b/phpmon/Domain/SiteList/SiteListVC+Actions.swift @@ -0,0 +1,97 @@ +// +// SiteListVC+Actions.swift +// PHP Monitor +// +// Created by Nico Verbruggen on 23/12/2021. +// Copyright © 2021 Nico Verbruggen. All rights reserved. +// + +import Foundation +import Cocoa + +extension SiteListVC { + + @objc func toggleSecure() { + let rowToReload = tableView.selectedRow + let originalSecureStatus = selectedSite!.secured + let action = selectedSite!.secured ? "unsecure" : "secure" + let selectedSite = selectedSite! + let command = "cd '\(selectedSite.absolutePath!)' && sudo \(Paths.valet) \(action) && exit;" + + waitAndExecute { + Shell.run(command, requiresPath: true) + } completion: { [self] in + selectedSite.determineSecured(Valet.shared.config.tld) + if selectedSite.secured == originalSecureStatus { + Alert.notify( + message: "site_list.alerts_status_not_changed.title".localized, + info: "site_list.alerts_status_not_changed.desc".localized(command) + ) + } else { + let newState = selectedSite.secured ? "secured" : "unsecured" + LocalNotification.send( + title: "site_list.alerts_status_changed.title".localized, + subtitle: "site_list.alerts_status_changed.desc" + .localized( + "\(selectedSite.name!).\(Valet.shared.config.tld)", + newState + ) + ) + } + + tableView.reloadData(forRowIndexes: [rowToReload], columnIndexes: [0]) + tableView.deselectRow(rowToReload) + tableView.selectRowIndexes([rowToReload], byExtendingSelection: true) + } + } + + @objc func openInBrowser() { + let prefix = selectedSite!.secured ? "https://" : "http://" + let url = URL(string: "\(prefix)\(selectedSite!.name!).\(Valet.shared.config.tld)") + if url != nil { + NSWorkspace.shared.open(url!) + } else { + _ = Alert.present( + messageText: "site_list.alert.invalid_folder_name".localized, + informativeText: "site_list.alert.invalid_folder_name_desc".localized + ) + } + } + + @objc func openInFinder() { + Shell.run("open '\(selectedSite!.absolutePath!)'") + } + + @objc func openInTerminal() { + Shell.run("open -b com.apple.terminal '\(selectedSite!.absolutePath!)'") + } + + @objc func openWithEditor(sender: EditorMenuItem) { + guard let editor = sender.editor else { return } + editor.openDirectory(file: selectedSite!.absolutePath!) + } + + @objc func unlinkSite() { + guard let site = selectedSite else { + return + } + + if site.aliasPath == nil { + return + } + + Alert.confirm( + onWindow: view.window!, + messageText: "site_list.confirm_unlink".localized(site.name), + informativeText: "site_link.confirm_link".localized, + buttonTitle: "site_list.unlink".localized, + secondButtonTitle: "Cancel", + style: .critical, + onFirstButtonPressed: { + Shell.run("valet unlink '\(site.name!)'", requiresPath: true) + self.reloadSites() + } + ) + } + +} diff --git a/phpmon/Domain/SiteList/SiteListVC.swift b/phpmon/Domain/SiteList/SiteListVC.swift index 77815ab..78ea030 100644 --- a/phpmon/Domain/SiteList/SiteListVC.swift +++ b/phpmon/Domain/SiteList/SiteListVC.swift @@ -112,12 +112,11 @@ class SiteListVC: NSViewController, NSTableViewDelegate, NSTableViewDataSource { - Parameter execute: Callback of the work that needs to happen. - Parameter completion: Callback that is fired when the work is done. */ - private func waitAndExecute(_ execute: @escaping () -> Void, completion: @escaping () -> Void = {}) + internal func waitAndExecute(_ execute: @escaping () -> Void, completion: @escaping () -> Void = {}) { setUIBusy() DispatchQueue.global(qos: .userInitiated).async { [unowned self] in execute() - DispatchQueue.main.async { [self] in completion() setUINotBusy() @@ -164,92 +163,6 @@ class SiteListVC: NSViewController, NSTableViewDelegate, NSTableViewDataSource { self.openInBrowser() } - // MARK: Secure & Unsecure - - @objc public func toggleSecure() { - let rowToReload = tableView.selectedRow - let originalSecureStatus = selectedSite!.secured - let action = selectedSite!.secured ? "unsecure" : "secure" - let selectedSite = selectedSite! - let command = "cd '\(selectedSite.absolutePath!)' && sudo \(Paths.valet) \(action) && exit;" - - waitAndExecute { - Shell.run(command, requiresPath: true) - } completion: { [self] in - selectedSite.determineSecured(Valet.shared.config.tld) - if selectedSite.secured == originalSecureStatus { - Alert.notify( - message: "site_list.alerts_status_not_changed.title".localized, - info: "site_list.alerts_status_not_changed.desc".localized(command) - ) - } else { - let newState = selectedSite.secured ? "secured" : "unsecured" - LocalNotification.send( - title: "site_list.alerts_status_changed.title".localized, - subtitle: "site_list.alerts_status_changed.desc" - .localized( - "\(selectedSite.name!).\(Valet.shared.config.tld)", - newState - ) - ) - } - - tableView.reloadData(forRowIndexes: [rowToReload], columnIndexes: [0]) - tableView.deselectRow(rowToReload) - tableView.selectRowIndexes([rowToReload], byExtendingSelection: true) - } - } - - // MARK: Open in Browser & Finder - - @objc public func openInBrowser() { - let prefix = selectedSite!.secured ? "https://" : "http://" - let url = URL(string: "\(prefix)\(selectedSite!.name!).\(Valet.shared.config.tld)") - if url != nil { - NSWorkspace.shared.open(url!) - } else { - warnAboutInvalidFolderAction() - } - } - - @objc public func openInFinder() { - Shell.run("open '\(selectedSite!.absolutePath!)'") - } - - @objc public func openInTerminal() { - Shell.run("open -b com.apple.terminal '\(selectedSite!.absolutePath!)'") - } - - @objc public func unlinkSite() { - guard let site = selectedSite else { - return - } - - if site.aliasPath == nil { - return - } - - Alert.confirm( - onWindow: view.window!, - messageText: "site_list.confirm_unlink".localized(site.name), - informativeText: "site_link.confirm_link".localized, - buttonTitle: "site_list.unlink".localized, - secondButtonTitle: "Cancel", - style: .critical, - onFirstButtonPressed: { - Shell.run("valet unlink \(site.name!)", requiresPath: true) - self.reloadSites() - } - ) - } - - private func warnAboutInvalidFolderAction() { - _ = Alert.present( - messageText: "site_list.alert.invalid_folder_name".localized, - informativeText: "site_list.alert.invalid_folder_name_desc".localized - ) - } - // MARK: - (Search) Text Field Delegate func searchedFor(text: String) { @@ -269,13 +182,7 @@ class SiteListVC: NSViewController, NSTableViewDelegate, NSTableViewDataSource { tableView.reloadData() } - - // MARK: - Context Menu - - @objc func openWithEditor(sender: EditorMenuItem) { - guard let editor = sender.editor else { return } - editor.openDirectory(file: selectedSite!.absolutePath!) - } + // MARK: - Deinitialization deinit { diff --git a/phpmon/Domain/Terminal/Shell.swift b/phpmon/Domain/Terminal/Shell.swift index ddfeb02..1c22259 100644 --- a/phpmon/Domain/Terminal/Shell.swift +++ b/phpmon/Domain/Terminal/Shell.swift @@ -116,7 +116,8 @@ class Shell { Uses `/bin/echo` instead of the `builtin` (which does not support `-n`). */ public static func fileExists(_ path: String) -> Bool { - return Shell.pipe("if [ -f \(path) ]; then /bin/echo -n \"0\"; fi") == "0" + let escapedPath = path.replacingOccurrences(of: " ", with: "\\ ") + return Shell.pipe("if [ -f \(escapedPath) ]; then /bin/echo -n \"0\"; fi") == "0" } } diff --git a/phpmon/Localizable.strings b/phpmon/Localizable.strings index cf9ccda..30031fa 100644 --- a/phpmon/Localizable.strings +++ b/phpmon/Localizable.strings @@ -74,6 +74,8 @@ "site_list.detected_apps" = "Detected Applications"; "site_list.system_apps" = "System Applications"; +"site_list.warning.spaces" = "Warning! This site has a space in its folder.\nThe site will not be reachable via the browser."; + "site_list.alert.invalid_folder_name" = "Invalid folder name"; "site_list.alert.invalid_folder_name_desc" = "This folder could not be resolved to a valid URL. This is usually because there’s a space in the folder name. Please rename the folder, reload the list of sites, and try again.";