diff --git a/LocalPackage/Sources/Cli/main.swift b/LocalPackage/Sources/Cli/main.swift index 497e75d1..1b798ab9 100644 --- a/LocalPackage/Sources/Cli/main.swift +++ b/LocalPackage/Sources/Cli/main.swift @@ -41,7 +41,7 @@ let argsAsString = args.joined(separator: " ") if !isVersion { switch parseCmdArgs(argsAsString) { - case .cmd(let cmdArgs): + case .cmd: break case .help(let help): print(help) @@ -56,16 +56,6 @@ let socket = Result { try Socket.create(family: .unix, type: .stream, proto: .un defer { socket.close() } -let socketFile = "/tmp/\(appId).sock" - -if let e: Error = Result(catching: { try socket.connect(to: socketFile) }).errorOrNil { - if isVersion { - printVersionAndExit(serverVersion: nil) - } else { - prettyError("Can't connect to AeroSpace server. Is AeroSpace.app running?\n\(e.localizedDescription)") - } -} - func run(_ command: String, stdin: String) -> ServerAnswer { let request = Result { try JSONEncoder().encode(ClientRequest(command: command, stdin: stdin)) }.getOrThrow() Result { try socket.write(from: request) }.getOrThrow() @@ -75,31 +65,40 @@ func run(_ command: String, stdin: String) -> ServerAnswer { Result { try socket.read(into: &answer) }.getOrThrow() return Result { try JSONDecoder().decode(ServerAnswer.self, from: answer) }.getOrThrow() } +let socketFile = "/tmp/\(appId).sock" -let serverVersionAns = run("server-version-internal-command", stdin: "") -if serverVersionAns.exitCode != 0 { - prettyError( - """ - Client-server miscommunication error. - - Server stdout: \(serverVersionAns.stdout) - Server stderr: \(serverVersionAns.stderr) +if let e: Error = Result(catching: { try socket.connect(to: socketFile) }).errorOrNil { + if isVersion { + printVersionAndExit(serverVersion: nil) + } else { + prettyError("Can't connect to AeroSpace server. Is AeroSpace.app running?\n\(e.localizedDescription)") + } +} - Possible cause: client/server version mismatch +var stdin = "" +if hasStdin() { + var index = 0 + while let line = readLine(strippingNewline: false) { + stdin += line + index += 1 + if index > 1000 { + prettyError("stdin number of lines limit is exceeded") + } + } +} - Possible fixes: - - Restart AeroSpace.app (restart is required after each update) - - Reinstall and restart AeroSpace (corrupted installation) - """ - ) +let ans = isVersion ? run("server-version-internal-command", stdin: stdin) : run(argsAsString, stdin: stdin) +if ans.exitCode == 0 && isVersion { + printVersionAndExit(serverVersion: ans.serverVersionAndHash) } -if serverVersionAns.stdout != cliClientVersionAndHash { + +if ans.exitCode != 0 && ans.serverVersionAndHash != cliClientVersionAndHash { prettyError( """ AeroSpace client/server version mismatch - aerospace CLI client version: \(cliClientVersionAndHash) - - AeroSpace.app server version: \(serverVersionAns.stdout) + - AeroSpace.app server version: \(ans.serverVersionAndHash) Possible fixes: - Restart AeroSpace.app (restart is required after each update) @@ -108,24 +107,6 @@ if serverVersionAns.stdout != cliClientVersionAndHash { ) } -if isVersion { - printVersionAndExit(serverVersion: serverVersionAns.stdout) -} - -var stdin = "" -if hasStdin() { - var index = 0 - while let line = readLine(strippingNewline: false) { - stdin += line - index += 1 - if index > 1000 { - prettyError("stdin number of lines limit is exceeded") - } - } -} - -let ans = run(argsAsString, stdin: stdin) - if !ans.stdout.isEmpty { print(ans.stdout) } if !ans.stderr.isEmpty { printStderr(ans.stderr) } exit(ans.exitCode) diff --git a/LocalPackage/Sources/Common/model/clientServer.swift b/LocalPackage/Sources/Common/model/clientServer.swift index e1c7bfef..2e7a779b 100644 --- a/LocalPackage/Sources/Common/model/clientServer.swift +++ b/LocalPackage/Sources/Common/model/clientServer.swift @@ -4,15 +4,18 @@ public struct ServerAnswer: Codable { public let exitCode: Int32 public let stdout: String public let stderr: String + public let serverVersionAndHash: String public init( exitCode: Int32, stdout: String = "", - stderr: String = "" + stderr: String = "", + serverVersionAndHash: String ) { self.exitCode = exitCode self.stdout = stdout self.stderr = stderr + self.serverVersionAndHash = serverVersionAndHash } } diff --git a/src/server.swift b/src/server.swift index 15b86044..bb49ed19 100644 --- a/src/server.swift +++ b/src/server.swift @@ -30,7 +30,13 @@ func sendCommandToReleaseServer(command: String) { _ = try? socket.readString() } +private let serverVersionAndHash = "\(Bundle.appVersion) \(gitHash)" + private func newConnection(_ socket: Socket) async { // todo add exit codes + func answerToClient(exitCode: Int32, stdout: String = "", stderr: String = "") { + let ans = ServerAnswer(exitCode: exitCode, stdout: stderr, stderr: stderr, serverVersionAndHash: serverVersionAndHash) + answerToClient(ans) + } func answerToClient(_ ans: ServerAnswer) { _ = try? socket.write(from: Result { try JSONEncoder().encode(ans) }.getOrThrow()) } @@ -42,57 +48,64 @@ private func newConnection(_ socket: Socket) async { // todo add exit codes _ = try? Socket.wait(for: [socket], timeout: 0, waitForever: true) var rawRequest = Data() if (try? socket.read(into: &rawRequest)) ?? 0 == 0 { - answerToClient(ServerAnswer(exitCode: 1, stderr: "Empty request")) + answerToClient(exitCode: 1, stderr: "Empty request") return } let _request = tryCatch(body: { try JSONDecoder().decode(ClientRequest.self, from: rawRequest) }) guard let request: ClientRequest = _request.getOrNil() else { - answerToClient(ServerAnswer( + answerToClient( exitCode: 1, stderr: """ Can't parse request '\(String(describing: String(data: rawRequest, encoding: .utf8)))'. Error: \(String(describing: _request.errorOrNil)) """ - )) + ) continue } let (command, help, err) = parseCommand(request.command).unwrap() guard let isEnabled = await Task(operation: { @MainActor in TrayMenuModel.shared.isEnabled }).result.getOrNil() else { - answerToClient(ServerAnswer(exitCode: 1, stderr: "Unknown failure during isEnabled server state access")) + answerToClient(exitCode: 1, stderr: "Unknown failure during isEnabled server state access") continue } if !isEnabled && !isAllowedToRunWhenDisabled(command) { - answerToClient(ServerAnswer( + answerToClient( exitCode: 1, stderr: "\(Bundle.appName) server is disabled and doesn't accept commands. " + "You can use 'aerospace enable on' to enable the server" - )) + ) continue } if let help { - answerToClient(ServerAnswer(exitCode: 0, stdout: help)) + answerToClient(exitCode: 0, stdout: help) continue } if let err { - answerToClient(ServerAnswer(exitCode: 1, stderr: err)) + answerToClient(exitCode: 1, stderr: err) continue } if command?.isExec == true { - answerToClient(ServerAnswer(exitCode: 1, stderr: "exec commands are prohibited in CLI")) + answerToClient(exitCode: 1, stderr: "exec commands are prohibited in CLI") continue } if let command { - let answer = await Task { @MainActor in + let _answer: Result = await Task { @MainActor in refreshSession(forceFocus: true) { let state: CommandMutableState = .focused // todo restore subject from "exec session" let success = command.run(state, stdin: request.stdin) return ServerAnswer( exitCode: success ? 0 : 1, stdout: state.stdout.joined(separator: "\n"), - stderr: state.stderr.joined(separator: "\n") + stderr: state.stderr.joined(separator: "\n"), + serverVersionAndHash: serverVersionAndHash ) } - }.result.getOrNil() ?? ServerAnswer(exitCode: 1, stderr: "Fail to await main thread") + }.result + let answer = _answer.getOrNil() ?? + ServerAnswer( + exitCode: 1, + stderr: "Fail to await main thread. \(_answer.errorOrNil?.localizedDescription)", + serverVersionAndHash: serverVersionAndHash + ) answerToClient(answer) continue }