// // 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() -> Bool func stopListening() } class GSConnection : ObservableObject { @Published var state = GSConnectionState.disconnected @Published var textHeard = "" @Published var errorMessage : GSConnectionErrorMessage? var speechForwarder : SpeechForwarderProtocol? let LISTEN_STATE_MSG = 1 let LISTEN_TEXT_MSG = 2 let LISTEN_SEND_MORE = 3 let port = 19026 private var destination = "" private var client: TCPClient? private let logger = Logger() private let readQueue = OperationQueue() private let writeQueue = OperationQueue() private var condition = NSCondition() private var stopListeningFlag = false private var canSend = true func changeState(newState : GSConnectionState) { if (state == newState) { return; } var legalTransition = false switch (newState) { case .disconnected: legalTransition = ((state == .connected) || (state == .connecting)) case .connecting: legalTransition = (state == .disconnected) case .connected: legalTransition = ((state == .connecting) || (state == .listening) || (state == .stoplistening)) case .listening: legalTransition = (state == .connected) case .stoplistening: legalTransition = ((state == .connected) || (state == .listening)) } if (!legalTransition) { logger.error("Illegal requested state transition from \(self.state) to \(newState)") errorOccurred(title: "Bad State Change", message: "Illegal state transition from \(self.state) to \(newState)") } else { state = newState } } func errorOccurred(title: String, message : String) { OperationQueue.main.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(port)) guard let client = client else { OperationQueue.main.addOperation { self.connectionFailed() } return } switch client.connect(timeout: 10) { case .success: OperationQueue.main.addOperation { self.connectionSuccessful() } case .failure(let error): client.close() self.client = nil logger.error("Failed to connect to \(self.destination): \(String(describing: error))") OperationQueue.main.addOperation { self.connectionFailed() } return } while (true) { guard let byteArray = client.read(2) else { 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("