Skip to content

Commit

Permalink
Include serverVersionAndHash into server response
Browse files Browse the repository at this point in the history
Motivation:
1. Small optimization. One less client-server hoop.
   #104

   Before the commit

      (debug, idle)
      time aerospace list-workspaces --all
      real    0m0.041s
      user    0m0.002s
      sys     0m0.001s

      (debug, busy, 8 windows)
      alt-w = ['workspace W', 'exec-and-forget bash -c "time /Users/bobko/a/AeroSpace/LocalPackage/.build/debug/aerospace list-workspaces --all" 2> ~/log']
      real    0m0.165s
      user    0m0.002s
      sys     0m0.001s

   After the commit

      (debug, idle)
      time aerospace list-workspaces --all
      real    0m0.024s
      user    0m0.002s
      sys     0m0.002s

      (debug, busy, 8 windows)
      alt-w = ['workspace W', 'exec-and-forget bash -c "time /Users/bobko/a/AeroSpace/LocalPackage/.build/debug/aerospace list-workspaces --all" 2> ~/log']
      real    0m0.145s
      user    0m0.004s
      sys     0m0.004s

2. Now if server and client have different versions, but understand each
   other, the error is not reported
  • Loading branch information
nikitabobko committed Jan 7, 2024
1 parent 5183687 commit 057dff1
Show file tree
Hide file tree
Showing 3 changed files with 55 additions and 58 deletions.
71 changes: 26 additions & 45 deletions LocalPackage/Sources/Cli/main.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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()
Expand All @@ -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)
Expand All @@ -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)
5 changes: 4 additions & 1 deletion LocalPackage/Sources/Common/model/clientServer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}

Expand Down
37 changes: 25 additions & 12 deletions src/server.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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())
}
Expand All @@ -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<ServerAnswer, Error> = 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
}
Expand Down

0 comments on commit 057dff1

Please sign in to comment.