// // GSConnection.swift // ListenerGS // // Created by Jeremy Rand on 2022-03-14. // import Foundation import os struct GSConnectionErrorMessage: Identifiable { var id: String { message } let title: String let message: String } enum GSConnectionState { case disconnected case connecting case connected case listening case stoplistening } extension GSConnectionState: CustomStringConvertible { var description: String { switch self { case .disconnected: return "disconnected" case .connecting: return "connecting" case .connected: return "connected" case .listening: return "listening" case .stoplistening: return "stop listening" } } } protocol SpeechForwarderProtocol { func startListening(connection: GSConnection) -> Bool func stopListening() } class GSConnection : ObservableObject { @Published var state = GSConnectionState.disconnected @Published var textHeard = "" @Published var errorMessage : GSConnectionErrorMessage? var speechForwarder : SpeechForwarderProtocol? static let LISTEN_STATE_MSG = 1 static let LISTEN_TEXT_MSG = 2 static let LISTEN_SEND_MORE = 3 static let port = 19026 private var destination = "" private var client: TCPClient? private let logger = Logger() private let readQueue = OperationQueue() private let writeQueue = OperationQueue() private var mainQueue = OperationQueue.main private var condition = NSCondition() private var canSend = true private func changeState(newState : GSConnectionState) { let oldState = state if (oldState == newState) { return; } var legalTransition = false switch (newState) { case .disconnected: legalTransition = ((oldState == .connected) || (oldState == .connecting) || (oldState == .stoplistening)) case .connecting: legalTransition = (oldState == .disconnected) case .connected: legalTransition = ((oldState == .connecting) || (oldState == .listening) || (oldState == .stoplistening)) case .listening: legalTransition = (oldState == .connected) case .stoplistening: legalTransition = ((oldState == .connected) || (oldState == .listening)) } if (!legalTransition) { logger.error("Illegal requested state transition from \(oldState) to \(newState)") errorOccurred(title: "Bad State Change", message: "Illegal state transition from \(oldState) to \(newState)") } else { state = newState } } func errorOccurred(title: String, message : String) { mainQueue.addOperation { self.errorMessage = GSConnectionErrorMessage(title: title, message: message) } } private func connectionFailed() { errorOccurred(title: "Connect Error", message: "Failed to connect to \(destination)") changeState(newState:.disconnected) } private func connectionSuccessful() { changeState(newState:.connected) logger.debug("Connected to \(self.destination)") } private func doConnect() { logger.debug("Attempting to connect to \(self.destination)") client = TCPClient(address: destination, port: Int32(GSConnection.port)) guard let client = client else { mainQueue.addOperation { self.connectionFailed() } return } switch client.connect(timeout: 10) { case .success: mainQueue.addOperation { self.connectionSuccessful() } case .failure(let error): client.close() self.client = nil logger.error("Failed to connect to \(self.destination): \(String(describing: error))") mainQueue.addOperation { self.connectionFailed() } return } while (true) { guard let byteArray = client.read(2) else { break } if (byteArray.count != 2) { break } let data = Data(byteArray) do { let unpacked = try unpack(" Bool { guard let client = client else { return false } switch (client.send(data: pack(" Bool { guard let client = client else { return false } var commonChars = lastSent.count while (commonChars > 0) { if (latestText.prefix(commonChars) == lastSent.prefix(commonChars)) { break } commonChars -= 1 } var stringToSend = "" if (commonChars < lastSent.count) { stringToSend = String(repeating: "\u{7f}", count: lastSent.count - commonChars) } stringToSend.append(contentsOf: latestText.suffix(latestText.count - commonChars).replacingOccurrences(of: "\n", with: "\r")) if (stringToSend.count == 0) { return false } // JSR_TODO - Handle strings to send that are longer than 64K (doubt that would happen though) let nsEnc = CFStringConvertEncodingToNSStringEncoding(CFStringEncoding(CFStringBuiltInEncodings.macRoman.rawValue)) let encoding = String.Encoding(rawValue: nsEnc) // String.Encoding if let bytes = stringToSend.data(using: encoding) { switch (client.send(data: pack("