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

♻️ Change app detection, detect apps upfront

This commit is contained in:
2021-12-10 17:10:36 +01:00
parent b6b5a94bbd
commit a9f140fabc
9 changed files with 135 additions and 174 deletions

View File

@ -9,6 +9,8 @@
/* Begin PBXBuildFile section */
5420395926135DC100FB00FA /* PrefsVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5420395826135DC100FB00FA /* PrefsVC.swift */; };
5420395F2613607600FB00FA /* Preferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5420395E2613607600FB00FA /* Preferences.swift */; };
54AB03262763858F00A29D5F /* Timer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54AB03252763858F00A29D5F /* Timer.swift */; };
54AB03272763858F00A29D5F /* Timer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54AB03252763858F00A29D5F /* Timer.swift */; };
54B48B5F275F66AE006D90C5 /* Application.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54B48B5E275F66AE006D90C5 /* Application.swift */; };
54B48B60275F66AE006D90C5 /* Application.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54B48B5E275F66AE006D90C5 /* Application.swift */; };
54EAC806262F212B0092D14E /* GlobalKeybindPreference.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41CD0282628D8EE0065BBED /* GlobalKeybindPreference.swift */; };
@ -120,6 +122,7 @@
/* Begin PBXFileReference section */
5420395826135DC100FB00FA /* PrefsVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrefsVC.swift; sourceTree = "<group>"; };
5420395E2613607600FB00FA /* Preferences.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Preferences.swift; sourceTree = "<group>"; };
54AB03252763858F00A29D5F /* Timer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Timer.swift; sourceTree = "<group>"; };
54B48B5E275F66AE006D90C5 /* Application.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Application.swift; sourceTree = "<group>"; };
C405A4CE24B9B9130062FAFA /* InternetAccessPolicy.strings */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; path = InternetAccessPolicy.strings; sourceTree = "<group>"; };
C405A4CF24B9B9140062FAFA /* InternetAccessPolicy.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = InternetAccessPolicy.plist; sourceTree = "<group>"; };
@ -328,6 +331,7 @@
C474B00524C0E98C00066A22 /* LocalNotification.swift */,
C41C1B4822B00A9800E7CF16 /* MenuBarImageGenerator.swift */,
C4CCBA6B275C567B008C7055 /* PMWindowController.swift */,
54AB03252763858F00A29D5F /* Timer.swift */,
);
path = Helpers;
sourceTree = "<group>";
@ -552,6 +556,7 @@
C48D0C9325CC804200CC7490 /* XibLoadable.swift in Sources */,
C4811D2A22D70F9A00B5F6B3 /* MainMenu.swift in Sources */,
C412E5FC25700D5300A1FB67 /* HomebrewPackage.swift in Sources */,
54AB03262763858F00A29D5F /* Timer.swift in Sources */,
C41C1B3722B0097F00E7CF16 /* AppDelegate.swift in Sources */,
C42759672627662800093CAE /* NSMenuExtension.swift in Sources */,
C464ADAF275A7A69003FCD53 /* SiteListVC.swift in Sources */,
@ -577,6 +582,7 @@
files = (
54EAC806262F212B0092D14E /* GlobalKeybindPreference.swift in Sources */,
C4F780C425D80B75000DBC97 /* MainMenu.swift in Sources */,
54AB03272763858F00A29D5F /* Timer.swift in Sources */,
54B48B60275F66AE006D90C5 /* Application.swift in Sources */,
C4F780C825D80B75000DBC97 /* DateExtension.swift in Sources */,
C4F780CC25D80B75000DBC97 /* ActivePhpInstallation.swift in Sources */,

View File

@ -57,10 +57,13 @@ class App {
var currentInstall: ActivePhpInstallation? = nil
/** All available versions of PHP. */
var availablePhpVersions : [String] = []
var availablePhpVersions: [String] = []
/** Cached information about the PHP installations. */
var cachedPhpInstallations : [String: PhpInstallation] = [:]
var cachedPhpInstallations: [String: PhpInstallation] = [:]
/** List of detected (installed) applications that PHP Monitor can work with. */
var detectedApplications: [Application] = []
/** Timer that will periodically reload info about the user's PHP installation. */
var timer: Timer?

View File

@ -8,43 +8,26 @@
import Foundation
/// An application that is capable of opening a particular directory (usually of a PHP project).
/// In most cases this is going to be a code editor, but it could also be another application
/// that supports opening those directories, like a visual Git client or a terminal app.
class Application {
/// Name of the app. Used for display purposes.
enum AppType {
case editor, browser, gitgui, terminal
}
/// Name of the app. Used for display purposes and to determine `name.app` exists.
let name: String
/// Paths to check whether the application is actually installed.
/// If the app finds any of these, the app is considered installed.
let pathsToVerifyInstalled: [String]
/// Application type. Depending on the type, a different action might occur.
let type: AppType
/// Path to the binary that actually opens the directory.
let pathToBinary: String
/// Instruction that needs to be followed in order to ensure that the app can open with this editor.
var missingBinaryInstruction: String? = nil
/// Callback that is executed to open a particular folder.
/// Must open the directory in the requested app, usually by using `pathToBinary`.
@objc let openCallback: (String) -> Void
/**
- Parameter name: Name of the application.
- Parameter installPath: Files to verify, if any file exists here the app is considered present on the system.
- Parameter binaryPath: Additional file that is used to open a specific path.
- Parameter open: Callback used to open a specific directory in the editor in question.
- Parameter instruction: Instruction for end user that needs to be followed in order to ensure the `binaryPath exists.
*/
init(name: String, installPaths: [String], binaryPath: String, open: @escaping ((String) -> Void), instruction: String? = nil) {
/// Initializer.
init(_ name: String, _ type: AppType) {
self.name = name
self.pathsToVerifyInstalled = installPaths.map({ path in
return path.replacingOccurrences(of: " ", with: "\\ ")
})
self.pathToBinary = binaryPath.replacingOccurrences(of: " ", with: "\\ ")
self.openCallback = open
self.missingBinaryInstruction = instruction
self.type = type
}
/**
@ -52,22 +35,17 @@ class Application {
This will open the editor if it isn't open yet.
*/
@objc public func openDirectory(file: String) {
self.openCallback(file)
return Shell.run("/usr/bin/open -a \(self.name) \(file)")
}
/** Checks if the app is installed. */
func isInstalled() -> Bool {
// TODO: Alternative way to detect if an app is installed:
// mdfind "kMDItemKind == 'Application'" | grep AppName.app
// This will return the path to the application. Worth a refactor?
self.pathsToVerifyInstalled.map({ path in
Shell.fileExists(path)
}).contains(true)
}
/** Checks if the correct binary required to open directories and/or files exists. */
func hasBinary() -> Bool {
return Shell.fileExists(self.pathToBinary)
// If this script does not complain, the app exists!
return Shell.user.execute(
"/usr/bin/open -Ra \"\(self.name)\"",
requiresPath: false,
waitUntilExit: true
).task.terminationStatus == 0
}
/**
@ -75,66 +53,13 @@ class Application {
*/
static public func detectPresetApplications() -> [Application] {
return [
Application(
name: "PhpStorm",
installPaths: [
"/Applications/PhpStorm.app/Contents/Info.plist",
"/usr/local/bin/pstorm"
],
binaryPath: "/usr/local/bin/pstorm",
open: { path in
Shell.run("/usr/local/bin/pstorm \(path)")
},
instruction: "editors.pstorm_binary_not_linked.desc".localized
),
Application(
name: "PhpStorm (via Toolbox)",
installPaths: [
"~/Applications/JetBrains Toolbox/PhpStorm.app/Contents/Info.plist",
"/usr/local/bin/phpstorm"
],
binaryPath: "/usr/local/bin/phpstorm",
open: { path in
Shell.run("/usr/local/bin/phpstorm \(path)")
},
instruction: "editors.phpstorm_binary_not_linked.desc".localized
),
Application(
name: "Visual Studio Code",
installPaths: [
"/Applications/Visual Studio Code.app/Contents/Info.plist",
"/usr/local/bin/code"
],
binaryPath: "/usr/local/bin/code",
open: { path in
Shell.run("/usr/local/bin/code \(path)")
},
instruction: "editors.code_binary_not_linked.desc".localized
),
Application(
name: "Sublime Text",
installPaths: ["/Applications/Sublime Text.app/Contents/SharedSupport/bin/subl"],
binaryPath: "/Applications/Sublime Text.app/Contents/SharedSupport/bin/subl",
open: { path in
Shell.run("/Applications/Sublime\\ Text.app/Contents/SharedSupport/bin/subl \(path)")
}
),
Application(
name: "Sublime Merge",
installPaths: ["/Applications/Sublime Merge.app/Contents/SharedSupport/bin/smerge"],
binaryPath: "/Applications/Sublime Merge.app/Contents/SharedSupport/bin/smerge",
open: { path in
Shell.run("/Applications/Sublime\\ Merge.app/Contents/SharedSupport/bin/smerge \(path)")
}
),
Application(
name: "iTerm",
installPaths: ["/Applications/iTerm.app/Contents/Info.plist"],
binaryPath: "/Applications/iTerm.app/Contents/Info.plist",
open: { path in
Shell.run("open -a iTerm \(path)")
}
)
].filter { return $0.isInstalled() }
Application("PhpStorm", .editor),
Application("Visual Studio Code", .editor),
Application("Sublime Text", .editor),
Application("Sublime Merge", .gitgui),
Application("iTerm", .terminal)
].filter {
return $0.isInstalled()
}
}
}

View File

@ -0,0 +1,32 @@
//
// BenchmarkTimer.swift
// PHP Monitor
//
// Created by Nico Verbruggen on 10/12/2021.
// Copyright © 2021 Nico Verbruggen. All rights reserved.
//
import Foundation
class BenchmarkTimer {
let startTime: CFAbsoluteTime
var endTime: CFAbsoluteTime?
init() {
startTime = CFAbsoluteTimeGetCurrent()
}
func stop() -> CFAbsoluteTime {
endTime = CFAbsoluteTimeGetCurrent()
return duration!
}
var duration: CFAbsoluteTime? {
if let endTime = endTime {
return endTime - startTime
} else {
return nil
}
}
}

View File

@ -55,10 +55,19 @@ class MainMenu: NSObject, NSWindowDelegate, NSMenuDelegate {
updatePhpVersionInStatusBar()
print("Determining broken PHP-FPM...")
// Attempt to find out if PHP-FPM is broken
let installation = App.phpInstall!
installation.notifyAboutBrokenPhpFpm()
print("Detecting applications...")
// Attempt to load list of applications
App.shared.detectedApplications = Application.detectPresetApplications()
let appNames = App.shared.detectedApplications.map { app in
return app.name
}
print("Detected applications: \(appNames)")
// Attempt to find out more info about Valet
print("PHP Monitor has extracted the version number of Valet: \(Valet.shared.version)")
print("PHP Monitor is ready to serve!")

View File

@ -41,7 +41,9 @@ class SiteListCell: NSTableCellView
// Show the green or red lock based on whether the site was secured
imageViewLock.image = NSImage(named: site.secured ? "Lock" : "LockUnlocked")
imageViewLock.contentTintColor = site.secured ? NSColor.systemGreen : NSColor.systemRed
imageViewLock.contentTintColor = site.secured ?
NSColor.init(red: 63/255, green: 195/255, blue: 128/255, alpha: 1.0) // green
: NSColor.init(red: 246/255, green: 71/255, blue: 71/255, alpha: 1.0) // red
// Show the current driver
labelDriver.stringValue = site.driver ?? "???"

View File

@ -23,7 +23,9 @@ class SiteListVC: NSViewController, NSTableViewDelegate, NSTableViewDataSource {
var sites: [Valet.Site] = []
/// Array that contains various apps that might open a particular site directory.
var editors: [Application] = Application.detectPresetApplications()
var applications: [Application] {
return App.shared.detectedApplications
}
/// String that was last searched for. Empty by default.
var lastSearchedFor = ""
@ -283,11 +285,11 @@ class SiteListVC: NSViewController, NSTableViewDelegate, NSTableViewDataSource {
keyEquivalent: "L"
)
if (editors.count > 0) {
if (applications.count > 0) {
menu.addItem(NSMenuItem.separator())
menu.addItem(withTitle: "site_list.detected_apps".localized, action: nil, keyEquivalent: "")
for (index, editor) in editors.enumerated() {
for (index, editor) in applications.enumerated() {
let editorMenuItem = EditorMenuItem(
title: "Open with \(editor.name)",
action: #selector(self.openWithEditor(sender:)),
@ -296,10 +298,9 @@ class SiteListVC: NSViewController, NSTableViewDelegate, NSTableViewDataSource {
editorMenuItem.editor = editor
menu.addItem(editorMenuItem)
}
menu.addItem(NSMenuItem.separator())
}
menu.addItem(NSMenuItem.separator())
menu.addItem(withTitle: "site_list.system_apps".localized, action: nil, keyEquivalent: "")
menu.addItem(
withTitle: "site_list.open_in_finder".localized,
@ -322,29 +323,8 @@ class SiteListVC: NSViewController, NSTableViewDelegate, NSTableViewDataSource {
@objc func openWithEditor(sender: EditorMenuItem) {
guard let editor = sender.editor else { return }
if editor.hasBinary() {
editor.openDirectory(file: selectedSite!.absolutePath!)
} else {
presentAlertForMissingEditorBinary(editor, sender)
}
editor.openDirectory(file: selectedSite!.absolutePath!)
}
private func presentAlertForMissingEditorBinary(_ editor: Application, _ sender: EditorMenuItem) {
Alert.confirm(
onWindow: self.view.window!,
messageText: "editors.binary_missing.title".localized(editor.pathToBinary),
informativeText:
editor.missingBinaryInstruction
?? "editors.binary_missing.desc".localized(editor.pathToBinary),
buttonTitle: "editors.alert.try_again".localized,
secondButtonTitle: "editors.alert.cancel".localized,
onFirstButtonPressed: {
self.openWithEditor(sender: sender)
}
)
}
// MARK: - Deinitialization
deinit {

View File

@ -62,6 +62,26 @@ class Shell {
_ command: String,
requiresPath: Bool = false
) -> String {
let shellOutput = self.execute(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`
*/
func execute(
_ command: String,
requiresPath: Bool = false,
waitUntilExit: Bool = false
) -> ShellOutput {
let task = Process()
let outputPipe = Pipe()
let errorPipe = Pipe()
@ -76,14 +96,19 @@ class Shell {
task.standardError = errorPipe
task.launch()
let error = String(data: errorPipe.fileHandleForReading.readDataToEndOfFile(), encoding: .utf8)!
let output = String(data: outputPipe.fileHandleForReading.readDataToEndOfFile(), encoding: .utf8)!
if (output == "" && error.lengthOfBytes(using: .utf8) > 0) {
return error
if waitUntilExit {
task.waitUntilExit()
}
return output
return ShellOutput(
standardOutput: String(
data: outputPipe.fileHandleForReading.readDataToEndOfFile(), encoding: .utf8
)!,
errorOutput: String(
data: errorPipe.fileHandleForReading.readDataToEndOfFile(), encoding: .utf8
)!,
task: task
)
}
/**
@ -94,3 +119,17 @@ class Shell {
return Shell.pipe("if [ -f \(path) ]; then /bin/echo -n \"0\"; fi") == "0"
}
}
class ShellOutput {
let standardOutput: String
let errorOutput: String
let task: Process
init(standardOutput: String,
errorOutput: String,
task: Process) {
self.standardOutput = standardOutput
self.errorOutput = errorOutput
self.task = task
}
}

View File

@ -75,45 +75,10 @@
"site_list.system_apps" = "System Applications";
// EDITORS
"editors.binary_missing.title" = "`%@` missing";
"editors.binary_missing.desc" = "The associated binary (`%@`) is missing. Because of this, PHP Monitor cannot open the domains folder.";
"editors.alert.try_again" = "Try Again";
"editors.alert.cancel" = "Cancel";
// - PHPSTORM + TOOLBOX
"editors.phpstorm_binary_not_linked.desc" =
"PHP Monitor makes use of PhpStorms launcher to open a specific directory.
The required launcher does not seem to be found in the usual location. In JetBrains Toolbox go to Settings, and select 'Generate shell scripts'.
As the directory, enter `/usr/local/bin`.
After this is done, PHP Monitor should be able to open the domains folder in PhpStorm.";
// - PHPSTORM (standalone)
"editors.pstorm_binary_not_linked.desc" =
"PHP Monitor makes use of PhpStorms launcher to open a specific directory.
The required launcher does not seem to be found in the usual location. Please set up the launcher by going to 'Tools > Create command-line launcher' in PhpStorm.
After this is done, PHP Monitor should be able to open the domains folder in PhpStorm.";
"editors.code_binary_not_linked.desc" =
"PHP Monitor makes use of Visual Studio Codes helper binary, `code`, but it seems not to exist on your system.
You can fix this by selecting 'Install `code` command in PATH' in Visual Studio Codes command palette.
(Type `>code` to find it.)
After this is done, PHP Monitor should be able to open the domains folder in PhpStorm.";
// - VS CODE
"editors.code_binary_not_linked.desc" =
"PHP Monitor makes use of Visual Studio Codes helper binary, `code`, but it seems not to exist on your system.
You can fix this by selecting 'Install `code` command in PATH' in Visual Studio Codes command palette.
(Type `>code` to find it.)
After this is done, PHP Monitor should be able to open the domains folder in PhpStorm.";
// PREFERENCES
"prefs.title" = "PHP Monitor";