1
1
mirror of https://github.com/qvacua/vimr.git synced 2024-12-24 14:23:34 +03:00
vimr/RxPack/RxMsgpackRpc.swift

301 lines
7.8 KiB
Swift
Raw Normal View History

2019-03-27 10:19:25 +03:00
/**
* Tae Won Ha - http://taewon.de - @hataewon
* See LICENSE
*/
import Foundation
import RxSwift
import MessagePack
import Socket
2019-03-27 11:16:29 +03:00
public final class RxMsgpackRpc {
2019-03-27 10:19:25 +03:00
public typealias Value = MessagePackValue
enum MessageType: UInt64 {
case request = 0
case response = 1
case notification = 2
}
public enum Message {
case response(msgid: UInt32, error: Value, result: Value)
case notification(method: String, params: [Value])
case error(value: Value, msg: String)
}
public struct Response {
public let msgid: UInt32
public let error: Value
public let result: Value
}
public struct Error: Swift.Error {
var msg: String
var cause: Swift.Error?
init(msg: String, cause: Swift.Error? = nil) {
self.msg = msg
self.cause = cause
}
}
/**
Streams `Message.notification`s and `Message.error`s by default.
When `streamResponses` is set to `true`, then also `Message.response`s.
*/
2020-01-25 20:16:24 +03:00
public var stream: Observable<Message> { self.streamSubject.asObservable() }
2019-03-27 10:19:25 +03:00
/**
When `true`, all messages of type `MessageType.response` are also streamed
to `stream` as `Message.response`. When `false`, only the `Single`s
you get from `request(msgid, method, params, expectsReturnValue)` will
get the response as `Response`.
*/
public var streamResponses = false
public func run(at path: String) -> Completable {
2020-01-25 20:16:24 +03:00
Completable.create { completable in
2019-03-27 10:19:25 +03:00
self.queue.async {
2020-01-26 23:14:59 +03:00
self.stopped = false
do {
try self.socket = Socket.create(
family: .unix,
type: .stream,
proto: .unix
)
try self.socket?.connect(to: path)
self.setUpThreadAndStartReading()
} catch {
self.streamSubject.onError(
Error(msg: "Could not get socket", cause: error)
)
completable(.error(
Error(msg: "Could not get socket at \(path)", cause: error)
))
2019-03-27 10:19:25 +03:00
}
completable(.completed)
}
return Disposables.create()
}
}
public func stop() -> Completable {
2020-01-25 20:16:24 +03:00
Completable.create { completable in
2019-03-27 10:19:25 +03:00
self.queue.async {
2020-01-26 23:14:59 +03:00
self.streamSubject.onCompleted()
self.cleanUpAndCloseSocket()
2019-03-27 10:19:25 +03:00
2020-01-26 23:14:59 +03:00
completable(.completed)
2019-03-27 10:19:25 +03:00
}
return Disposables.create()
}
}
public func request(
method: String,
params: [Value],
expectsReturnValue: Bool
) -> Single<Response> {
2020-01-25 20:16:24 +03:00
Single.create { single in
2019-03-27 10:19:25 +03:00
self.queue.async {
2020-01-26 23:25:33 +03:00
let msgid = self.nextMsgid
self.nextMsgid += 1
2019-03-27 10:19:25 +03:00
let packed = pack(
[
.uint(MessageType.request.rawValue),
.uint(UInt64(msgid)),
.string(method),
.array(params),
]
)
2020-01-26 23:14:59 +03:00
if self.stopped {
single(.error(Error(msg: "Connection stopped, " +
"but trying to send a request with msg id \(msgid)")))
return
}
2019-03-27 10:19:25 +03:00
2020-01-26 23:14:59 +03:00
guard let socket = self.socket else {
single(.error(Error(msg: "Socket is invalid, " +
"but trying to send a request with msg id \(msgid)")))
return
}
2019-03-27 10:19:25 +03:00
2020-01-26 23:25:33 +03:00
if expectsReturnValue { self.singles[msgid] = single }
2019-03-27 10:19:25 +03:00
2020-01-26 23:14:59 +03:00
do {
let writtenBytes = try socket.write(from: packed)
if writtenBytes < packed.count {
2019-03-27 10:19:25 +03:00
single(.error(Error(
2020-01-26 23:14:59 +03:00
msg: "(Written) = \(writtenBytes) < \(packed.count) = " +
"(requested) for msg id: \(msgid)"
)))
2019-03-27 10:19:25 +03:00
return
}
2020-01-26 23:14:59 +03:00
} catch {
self.streamSubject.onError(Error(
msg: "Could not write to socket for msg id: \(msgid)", cause: error))
2019-03-27 10:19:25 +03:00
2020-01-26 23:14:59 +03:00
single(.error(Error(
msg: "Could not write to socket for msg id: \(msgid)", cause: error)))
return
}
if !expectsReturnValue {
single(.success(self.nilResponse(with: msgid)))
2019-03-27 10:19:25 +03:00
}
}
return Disposables.create()
}
}
2020-01-26 23:14:59 +03:00
// MARK: - Private
2019-03-27 10:19:25 +03:00
private var nextMsgid: UInt32 = 0
2020-01-26 23:14:59 +03:00
private var socket: Socket?
private var thread: Thread?
2020-01-26 22:31:10 +03:00
private let queue = DispatchQueue(
label: String(reflecting: RxMsgpackRpc.self),
qos: .userInitiated
)
2019-03-27 10:19:25 +03:00
private var stopped = true
2020-01-26 23:25:33 +03:00
private var singles: [UInt32: SingleResponseObserver] = [:]
2019-03-27 10:19:25 +03:00
private let streamSubject = PublishSubject<Message>()
private func nilResponse(with msgid: UInt32) -> Response {
2020-01-25 20:16:24 +03:00
Response(msgid: msgid, error: .nil, result: .nil)
2019-03-27 10:19:25 +03:00
}
private func cleanUpAndCloseSocket() {
2020-01-26 23:25:33 +03:00
self.singles.forEach { msgid, single in single(.success(self.nilResponse(with: msgid))) }
2019-03-27 10:19:25 +03:00
self.singles.removeAll()
self.stopped = true
self.socket?.close()
}
private func setUpThreadAndStartReading() {
self.thread = Thread {
2020-01-25 20:16:24 +03:00
guard let socket = self.socket else { return }
2019-03-27 10:19:25 +03:00
var readData = Data(capacity: 10240)
repeat {
do {
let readBytes = try socket.read(into: &readData)
defer { readData.count = 0 }
if readBytes > 0 {
let values = try unpackAll(readData)
values.forEach(self.processMessage)
}
} catch let error as Socket.Error {
2020-01-25 20:16:24 +03:00
self.streamSubject.onError(Error(msg: "Could not read from socket", cause: error))
2020-01-26 23:14:59 +03:00
self.queue.async { self.cleanUpAndCloseSocket() }
2019-03-27 10:19:25 +03:00
return
} catch {
self.streamSubject.onNext(
.error(value: .nil, msg: "Data from socket could not be unpacked")
)
return
}
} while !self.stopped
}
self.thread?.start()
}
private func processMessage(_ unpacked: Value) {
guard let array = unpacked.arrayValue else {
self.streamSubject.onNext(.error(
value: unpacked,
msg: "Could not get the array from the message"
))
return
}
2019-12-26 11:22:43 +03:00
guard let rawType = array[0].uint64Value,
2019-03-27 10:19:25 +03:00
let type = MessageType(rawValue: rawType)
else {
self.streamSubject.onNext(.error(
value: unpacked, msg: "Could not get the type of the message"
))
return
}
switch type {
case .response:
guard array.count == 4 else {
self.streamSubject.onNext(.error(
value: unpacked,
2020-01-25 20:16:24 +03:00
msg: "Got an array of length \(array.count) for a message type response"
2019-03-27 10:19:25 +03:00
))
return
}
2019-12-26 11:22:43 +03:00
guard let msgid64 = array[1].uint64Value else {
2020-01-25 20:16:24 +03:00
self.streamSubject.onNext(.error(value: unpacked, msg: "Could not get the msgid"))
2019-03-27 10:19:25 +03:00
return
}
2020-01-26 23:25:33 +03:00
self.queue.async {
self.processResponse(msgid: UInt32(msgid64), error: array[2], result: array[3])
}
2019-03-27 10:19:25 +03:00
case .notification:
guard array.count == 3 else {
self.streamSubject.onNext(.error(
value: unpacked,
2020-01-25 20:16:24 +03:00
msg: "Got an array of length \(array.count) for a message type notification"
2019-03-27 10:19:25 +03:00
))
return
}
guard let method = array[1].stringValue,
let params = array[2].arrayValue
else {
self.streamSubject.onNext(.error(
value: unpacked,
msg: "Could not get the method and params"
))
return
}
self.streamSubject.onNext(.notification(method: method, params: params))
case .request:
self.streamSubject.onNext(.error(
value: unpacked,
msg: "Got message type request from remote"
))
return
}
}
private func processResponse(msgid: UInt32, error: Value, result: Value) {
if self.streamResponses {
2020-01-25 20:16:24 +03:00
self.streamSubject.onNext(.response(msgid: msgid, error: error, result: result))
2019-03-27 10:19:25 +03:00
}
2020-01-26 23:25:33 +03:00
guard let single: SingleResponseObserver = self.singles[msgid] else { return }
2019-03-27 10:19:25 +03:00
single(.success(Response(msgid: msgid, error: error, result: result)))
2020-01-26 23:25:33 +03:00
self.singles.removeValue(forKey: msgid)
2019-03-27 10:19:25 +03:00
}
}
2020-01-25 20:16:24 +03:00
private typealias SingleResponseObserver = (SingleEvent<RxMsgpackRpc.Response>) -> Void