diff --git a/phpmon/Domain/Helpers/Application.swift b/phpmon/Domain/Helpers/Application.swift index 6bd64f1..e337ed5 100644 --- a/phpmon/Domain/Helpers/Application.swift +++ b/phpmon/Domain/Helpers/Application.swift @@ -40,10 +40,9 @@ class Application { /** Checks if the app is installed. */ func isInstalled() -> Bool { // If this script does not complain, the app exists! - return Shell.user.execute( + return Shell.user.executeSynchronously( "/usr/bin/open -Ra \"\(name)\"", - requiresPath: false, - waitUntilExit: true + requiresPath: false ).task.terminationStatus == 0 } diff --git a/phpmon/Domain/Menu/MainMenu.swift b/phpmon/Domain/Menu/MainMenu.swift index d2b71e2..fb4e431 100644 --- a/phpmon/Domain/Menu/MainMenu.swift +++ b/phpmon/Domain/Menu/MainMenu.swift @@ -329,9 +329,25 @@ class MainMenu: NSObject, NSWindowDelegate, NSMenuDelegate { @objc func updateComposerDependencies() { DispatchQueue.global(qos: .userInitiated).async { - let output = Shell.user.execute( - "composer global update", requiresPath: true, waitUntilExit: true + let output = Shell.user.executeSynchronously( + "composer global update", requiresPath: true ) + + let task = Shell.user.createTask(for: "composer global update", requiresPath: true) + + Shell.captureOutput( + task, + didReceiveStdOutData: { string in + print("\(string)") + }, + didReceiveStdErrData: { string in + print("\(string)") + } + ) + + task.launch() + task.waitUntilExit() + DispatchQueue.main.async { if output.task.terminationStatus > 0 { // Error code means > 0 diff --git a/phpmon/Domain/Terminal/Shell.swift b/phpmon/Domain/Terminal/Shell.swift index ddfeb02..f3e6270 100644 --- a/phpmon/Domain/Terminal/Shell.swift +++ b/phpmon/Domain/Terminal/Shell.swift @@ -62,7 +62,7 @@ class Shell { _ command: String, requiresPath: Bool = false ) -> String { - let shellOutput = self.execute(command, requiresPath: requiresPath) + let shellOutput = self.executeSynchronously(command, requiresPath: requiresPath) let hasError = ( shellOutput.standardOutput == "" && shellOutput.errorOutput.lengthOfBytes(using: .utf8) > 0 @@ -77,35 +77,28 @@ class Shell { - 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( + func executeSynchronously( _ command: String, - requiresPath: Bool = false, - waitUntilExit: Bool = false + requiresPath: Bool = false ) -> ShellOutput { - let task = Process() + let outputPipe = Pipe() let errorPipe = Pipe() - let tailoredCommand = requiresPath - ? "export PATH=\(Paths.binPath):$PATH && \(command)" - : command - - task.launchPath = self.shell - task.arguments = ["--login", "-c", tailoredCommand] + let task = self.createTask(for: command, requiresPath: requiresPath) task.standardOutput = outputPipe task.standardError = errorPipe task.launch() - - if waitUntilExit { - task.waitUntilExit() - } + task.waitUntilExit() return ShellOutput( standardOutput: String( - data: outputPipe.fileHandleForReading.readDataToEndOfFile(), encoding: .utf8 + data: outputPipe.fileHandleForReading.readDataToEndOfFile(), + encoding: .utf8 )!, errorOutput: String( - data: errorPipe.fileHandleForReading.readDataToEndOfFile(), encoding: .utf8 + data: errorPipe.fileHandleForReading.readDataToEndOfFile(), + encoding: .utf8 )!, task: task ) @@ -118,6 +111,47 @@ class Shell { public static func fileExists(_ path: String) -> Bool { return Shell.pipe("if [ -f \(path) ]; then /bin/echo -n \"0\"; fi") == "0" } + + /** + Creates a new process with the correct PATH and shell. + */ + func createTask(for command: String, requiresPath: Bool) -> Process { + let tailoredCommand = requiresPath + ? "export PATH=\(Paths.binPath):$PATH && \(command)" + : command + + let task = Process() + task.launchPath = self.shell + task.arguments = ["--login", "-c", tailoredCommand] + + return task + } + + static func captureOutput( + _ task: Process, + didReceiveStdOutData: @escaping (String) -> Void, + didReceiveStdErrData: @escaping (String) -> Void + ) { + let outputPipe = Pipe() + let errorPipe = Pipe() + + task.standardOutput = outputPipe + task.standardError = errorPipe + + outputPipe.fileHandleForReading.waitForDataInBackgroundAndNotify() + NotificationCenter.default.addObserver(forName: NSNotification.Name.NSFileHandleDataAvailable, object: outputPipe.fileHandleForReading, queue: nil) { notification in + let outputString = String(data: outputPipe.fileHandleForReading.availableData, encoding: String.Encoding.utf8) ?? "" + didReceiveStdOutData(outputString) + outputPipe.fileHandleForReading.waitForDataInBackgroundAndNotify() + } + + errorPipe.fileHandleForReading.waitForDataInBackgroundAndNotify() + NotificationCenter.default.addObserver(forName: NSNotification.Name.NSFileHandleDataAvailable, object: errorPipe.fileHandleForReading, queue: nil) { notification in + let outputString = String(data: errorPipe.fileHandleForReading.availableData, encoding: String.Encoding.utf8) ?? "" + didReceiveStdErrData(outputString) + errorPipe.fileHandleForReading.waitForDataInBackgroundAndNotify() + } + } } class ShellOutput {