// retoor <retoor@molodetz.nl>
import Foundation
import Logging
let logger = Logger(label: "molouptime.main")
@main
struct Molouptime {
static let engine = CheckEngine()
static func main() async {
logger.info("molouptime engine starting")
let stdin = FileHandle.standardInput
let stdout = FileHandle.standardOutput
var buffer = Data()
// Set stdin to read line-by-line
stdin.readabilityHandler = { handle in
let data = handle.availableData
guard !data.isEmpty else { return }
buffer.append(data)
while let newlineRange = buffer.firstIndex(of: UInt8(ascii: "\n")) {
let lineData = buffer[..<newlineRange]
buffer = buffer[buffer.index(after: newlineRange)...]
guard let line = String(data: lineData, encoding: .utf8)?.trimmingCharacters(in: .whitespacesAndNewlines),
!line.isEmpty else {
continue
}
Task {
await handleLine(line, stdout: stdout)
}
}
}
// Keep the process alive
dispatchMain()
}
static func handleLine(_ line: String, stdout: FileHandle) async {
guard let request = IpcProtocol.readRequest(from: line) else {
let errorResp = IpcResponse(
reqId: 0, kind: "error",
payload: "Invalid JSON", checks: nil, metrics: nil,
error: "Failed to parse request"
)
writeResponse(errorResp, to: stdout)
return
}
switch request.command {
case .ping:
let resp = IpcResponse(reqId: request.reqId, kind: "pong", payload: "ok", checks: nil, metrics: nil, error: nil)
writeResponse(resp, to: stdout)
case .addCheck:
guard let payload = request.payload,
let uid = payload["uid"],
let type = payload["type"],
let target = payload["target"],
let intervalStr = payload["interval"],
let interval = Int(intervalStr) else {
let resp = IpcResponse(reqId: request.reqId, kind: "error", payload: nil, checks: nil, metrics: nil, error: "Missing required fields")
writeResponse(resp, to: stdout)
return
}
let port = payload["port"].flatMap { Int($0) }
let protocolType = payload["protocol"]
let check = CheckTarget(
uid: uid, type: type, target: target,
intervalSeconds: interval, port: port,
protocolType: protocolType
)
await engine.addCheck(check)
let resp = IpcResponse(reqId: request.reqId, kind: "added", payload: uid, checks: nil, metrics: nil, error: nil)
writeResponse(resp, to: stdout)
case .removeCheck:
let uid = request.payload?["uid"] ?? ""
await engine.removeCheck(uid)
let resp = IpcResponse(reqId: request.reqId, kind: "removed", payload: uid, checks: nil, metrics: nil, error: nil)
writeResponse(resp, to: stdout)
case .updateCheck:
guard let payload = request.payload,
let uid = payload["uid"] else {
let resp = IpcResponse(reqId: request.reqId, kind: "error", payload: nil, checks: nil, metrics: nil, error: "Missing uid")
writeResponse(resp, to: stdout)
return
}
let type = payload["type"] ?? "http"
let target = payload["target"] ?? ""
let interval = Int(payload["interval"] ?? "60") ?? 60
let port = payload["port"].flatMap { Int($0) }
let protocolType = payload["protocol"]
let check = CheckTarget(
uid: uid, type: type, target: target,
intervalSeconds: interval, port: port,
protocolType: protocolType
)
await engine.updateCheck(check)
let resp = IpcResponse(reqId: request.reqId, kind: "updated", payload: uid, checks: nil, metrics: nil, error: nil)
writeResponse(resp, to: stdout)
case .listChecks:
let checks = await engine.listChecks()
let resp = IpcResponse(reqId: request.reqId, kind: "checks", payload: nil, checks: checks, metrics: nil, error: nil)
writeResponse(resp, to: stdout)
case .getMetrics:
let metrics = await MetricSample?.none // Placeholder
let resp = IpcResponse(reqId: request.reqId, kind: "metrics", payload: nil, checks: nil, metrics: nil, error: nil)
writeResponse(resp, to: stdout)
case .shutdown:
await engine.stop()
let resp = IpcResponse(reqId: request.reqId, kind: "shutdown", payload: "bye", checks: nil, metrics: nil, error: nil)
writeResponse(resp, to: stdout)
exit(0)
}
}
static func writeResponse(_ response: IpcResponse, to handle: FileHandle) {
guard let json = IpcProtocol.writeResponse(response) else { return }
handle.write(Data("\(json)\n".utf8))
}
}