Compare commits

...

2 Commits

8 changed files with 178 additions and 117 deletions

View File

@ -17,6 +17,21 @@
</dict> </dict>
<key>SuppressBuildableAutocreation</key> <key>SuppressBuildableAutocreation</key>
<dict> <dict>
<key>9D5155EE26A1EF7B0075EBC7</key>
<dict>
<key>primary</key>
<true/>
</dict>
<key>9D5155FF26A1EF7C0075EBC7</key>
<dict>
<key>primary</key>
<true/>
</dict>
<key>9D51560A26A1EF7C0075EBC7</key>
<dict>
<key>primary</key>
<true/>
</dict>
<key>9D62FC5F27C494D700AEE01F</key> <key>9D62FC5F27C494D700AEE01F</key>
<dict> <dict>
<key>primary</key> <key>primary</key>

View File

@ -68,8 +68,8 @@ class GSConnection : ObservableObject {
private let writeQueue = OperationQueue() private let writeQueue = OperationQueue()
private var mainQueue = OperationQueue.main private var mainQueue = OperationQueue.main
private var condition = NSCondition()
private var canSend = true private var canSend = true
private var lastSent = ""
private func changeState(newState : GSConnectionState) private func changeState(newState : GSConnectionState)
{ {
@ -123,64 +123,61 @@ class GSConnection : ObservableObject {
logger.debug("Connected to \(self.destination)") 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("<h", data)
if (unpacked[0] as? Int == GSConnection.LISTEN_SEND_MORE) {
condition.lock()
canSend = true
condition.broadcast()
condition.unlock()
} else {
logger.error("Unexpected message on socket from \(self.destination)")
errorOccurred(title: "Protocol Error", message: "Unexpected message from the GS")
break
}
}
catch {
logger.error("Unable to unpack message on socket from \(self.destination)")
errorOccurred(title: "Protocol Error", message: "Unexpected message from the GS")
break
}
}
client.close()
self.client = nil
mainQueue.addOperation { self.disconnect() }
}
func connect(destination : String) { func connect(destination : String) {
self.destination = destination self.destination = destination
changeState(newState: .connecting) changeState(newState: .connecting)
readQueue.addOperation { readQueue.addOperation { [weak self, destination] in
self.doConnect() self?.logger.debug("Attempting to connect to \(destination)")
let client = TCPClient(address: destination, port: Int32(GSConnection.port))
switch client.connect(timeout: 10) {
case .success:
self?.mainQueue.addOperation {
self?.client = client
self?.connectionSuccessful()
}
case .failure(let error):
client.close()
self?.logger.error("Failed to connect to \(destination): \(String(describing: error))")
self?.mainQueue.addOperation {
self?.connectionFailed()
}
return
}
while (true) {
guard let byteArray = client.read(2) else {
break
}
if (byteArray.count != 2) {
break
}
guard let self = self else {
break
}
let data = Data(byteArray)
do {
let unpacked = try unpack("<h", data)
if (unpacked[0] as? Int == GSConnection.LISTEN_SEND_MORE) {
self.mainQueue.addOperation {
self.canSend = true
self.trySend()
}
} else {
self.logger.error("Unexpected message on socket from \(destination)")
self.errorOccurred(title: "Protocol Error", message: "Unexpected message from the GS")
break
}
}
catch {
self.logger.error("Unable to unpack message on socket from \(destination)")
self.errorOccurred(title: "Protocol Error", message: "Unexpected message from the GS")
break
}
}
self?.mainQueue.addOperation { self?.disconnect() }
} }
} }
@ -193,13 +190,10 @@ class GSConnection : ObservableObject {
stopListening() stopListening()
} }
condition.lock()
if (client != nil) { if (client != nil) {
client!.close() client!.close()
self.client = nil self.client = nil
} }
condition.broadcast()
condition.unlock()
waitForWriteQueue() waitForWriteQueue()
waitForReadQueue() waitForReadQueue()
@ -212,12 +206,11 @@ class GSConnection : ObservableObject {
speechForwarder.stopListening() speechForwarder.stopListening()
self.speechForwarder = nil self.speechForwarder = nil
} }
condition.lock()
if (state == .listening) { if (state == .listening) {
changeState(newState: .stoplistening) changeState(newState: .stoplistening)
condition.broadcast() trySend()
} }
condition.unlock()
} }
private func sendListenMsg(isListening: Bool) -> Bool { private func sendListenMsg(isListening: Bool) -> Bool {
@ -236,6 +229,7 @@ class GSConnection : ObservableObject {
func listen(speechForwarder: SpeechForwarderProtocol) { func listen(speechForwarder: SpeechForwarderProtocol) {
textHeard = "" textHeard = ""
lastSent = ""
writeQueue.addOperation { writeQueue.addOperation {
if (!self.sendListenMsg(isListening: true)) { if (!self.sendListenMsg(isListening: true)) {
self.errorOccurred(title: "Write Error", message: "Unable to send data to the GS") self.errorOccurred(title: "Write Error", message: "Unable to send data to the GS")
@ -252,57 +246,39 @@ class GSConnection : ObservableObject {
} }
self.speechForwarder = speechForwarder self.speechForwarder = speechForwarder
} }
}
self.send() }
_ = self.sendListenMsg(isListening: false) private func trySend() {
if (textHeard == lastSent) {
self.mainQueue.addOperation { if (state == .stoplistening) {
if (self.state == .stoplistening) { writeQueue.addOperation {
self.changeState(newState: .connected) _ = self.sendListenMsg(isListening: false)
}
changeState(newState: .connected)
}
return
}
if (!canSend) {
return
}
canSend = false
let stringToSend = textHeard
writeQueue.addOperation {
if self.send(latestText: stringToSend, lastSent: self.lastSent) {
self.mainQueue.addOperation {
self.lastSent = stringToSend
self.trySend()
} }
} }
} }
} }
func set(text:String) func set(text:String) {
{
condition.lock()
textHeard = text textHeard = text
condition.broadcast() trySend()
condition.unlock()
}
private func send() {
var stringLastSent = ""
var stringToSend = ""
while true {
condition.lock()
guard client != nil else {
condition.unlock()
return
}
if ((stringLastSent == textHeard) && (state == .stoplistening)) {
condition.unlock()
return
}
if ((!canSend) ||
(stringLastSent == textHeard)) {
condition.wait()
condition.unlock()
continue
}
stringToSend = textHeard
condition.unlock()
if send(latestText: stringToSend, lastSent: stringLastSent) {
stringLastSent = stringToSend
condition.lock()
canSend = false
condition.unlock()
}
}
} }
private func send(latestText : String, lastSent: String) -> Bool { private func send(latestText : String, lastSent: String) -> Bool {

View File

@ -19,7 +19,7 @@
<key>CFBundleShortVersionString</key> <key>CFBundleShortVersionString</key>
<string>1.0</string> <string>1.0</string>
<key>CFBundleVersion</key> <key>CFBundleVersion</key>
<string>598</string> <string>645</string>
<key>LSApplicationCategoryType</key> <key>LSApplicationCategoryType</key>
<string>public.app-category.utilities</string> <string>public.app-category.utilities</string>
<key>LSRequiresIPhoneOS</key> <key>LSRequiresIPhoneOS</key>

View File

@ -80,7 +80,6 @@ struct Webview: UIViewRepresentable {
decisionHandler(WKNavigationActionPolicy.cancel) decisionHandler(WKNavigationActionPolicy.cancel)
return return
} }
print("no link")
decisionHandler(WKNavigationActionPolicy.allow) decisionHandler(WKNavigationActionPolicy.allow)
} }
} }

View File

@ -53,6 +53,7 @@ class SpeechForwarder : SpeechForwarderProtocol {
audioEngine.stop() audioEngine.stop()
audioEngine.inputNode.removeTap(onBus: 0) audioEngine.inputNode.removeTap(onBus: 0)
recognitionTask?.cancel() recognitionTask?.cancel()
recognitionTask?.finish()
recognitionRequest = nil recognitionRequest = nil
recognitionTask = nil recognitionTask = nil
@ -84,12 +85,15 @@ class SpeechForwarder : SpeechForwarderProtocol {
// Create a recognition task for the speech recognition session. // Create a recognition task for the speech recognition session.
// Keep a reference to the task so that it can be canceled. // Keep a reference to the task so that it can be canceled.
recognitionTask = speechRecognizer.recognitionTask(with: recognitionRequest) { result, error in recognitionTask = speechRecognizer.recognitionTask(with: recognitionRequest) { [weak connection] result, error in
var isFinal = false var isFinal = false
if let result = result { if let result = result {
// Update the text view with the results. // Update the text view with the results.
OperationQueue.main.addOperation { connection.set(text: result.bestTranscription.formattedString) } OperationQueue.main.addOperation {
guard let connection = connection else { return }
connection.set(text: result.bestTranscription.formattedString)
}
isFinal = result.isFinal isFinal = result.isFinal
} }
@ -99,6 +103,7 @@ class SpeechForwarder : SpeechForwarderProtocol {
if error != nil || isFinal { if error != nil || isFinal {
OperationQueue.main.addOperation { OperationQueue.main.addOperation {
guard let connection = connection else { return }
connection.stopListening() connection.stopListening()
} }
} }

View File

@ -116,7 +116,7 @@ open class UDPClient: Socket {
guard let fd = self.fd else { guard let fd = self.fd else {
return (nil, "no ip", 0) return (nil, "no ip", 0)
} }
var buff: [Byte] = [Byte](repeating: 0x0, count: expectlen) let buff: [Byte] = [Byte](repeating: 0x0, count: expectlen)
var remoteipbuff: [Int8] = [Int8](repeating: 0x0, count: 16) var remoteipbuff: [Int8] = [Int8](repeating: 0x0, count: 16)
var remoteport: Int32 = 0 var remoteport: Int32 = 0
let readLen: Int32 = c_yudpsocket_recive(fd, buff: buff, len: Int32(expectlen), ip: &remoteipbuff, port: &remoteport) let readLen: Int32 = c_yudpsocket_recive(fd, buff: buff, len: Int32(expectlen), ip: &remoteipbuff, port: &remoteport)
@ -154,7 +154,7 @@ open class UDPServer: Socket {
//TODO add multycast and boardcast //TODO add multycast and boardcast
open func recv(_ expectlen: Int) -> ([Byte]?, String, Int) { open func recv(_ expectlen: Int) -> ([Byte]?, String, Int) {
if let fd = self.fd { if let fd = self.fd {
var buff: [Byte] = [Byte](repeating: 0x0,count: expectlen) let buff: [Byte] = [Byte](repeating: 0x0,count: expectlen)
var remoteipbuff: [Int8] = [Int8](repeating: 0x0,count: 16) var remoteipbuff: [Int8] = [Int8](repeating: 0x0,count: 16)
var remoteport: Int32 = 0 var remoteport: Int32 = 0
let readLen: Int32 = c_yudpsocket_recive(fd, buff: buff, len: Int32(expectlen), ip: &remoteipbuff, port: &remoteport) let readLen: Int32 = c_yudpsocket_recive(fd, buff: buff, len: Int32(expectlen), ip: &remoteipbuff, port: &remoteport)

View File

@ -90,7 +90,7 @@ int yudpsocket_close(int socket_fd) {
} }
//return socket fd //return socket fd
int yudpsocket_client() { int yudpsocket_client(void) {
//create socket //create socket
int socketfd = socket(AF_INET, SOCK_DGRAM, 0); int socketfd = socket(AF_INET, SOCK_DGRAM, 0);
int reuseon = 1; int reuseon = 1;

View File

@ -103,9 +103,10 @@ class ListenerGSTests: XCTestCase {
XCTAssertEqual(connection.textHeard, "Hello, everyone!") XCTAssertEqual(connection.textHeard, "Hello, everyone!")
XCTAssert(server.sendMore()) XCTAssert(server.sendMore())
connection.waitForMain()
connection.waitForWriteQueue()
XCTAssertEqual(server.getText(), "\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}everyone!") XCTAssertEqual(server.getText(), "\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}everyone!")
connection.waitForWriteQueue()
connection.waitForMain() connection.waitForMain()
XCTAssert(server.getListenState(isListening: false)) XCTAssert(server.getListenState(isListening: false))
@ -336,8 +337,73 @@ class ListenerGSTests: XCTestCase {
XCTAssertEqual(connection.textHeard, "Hello, everyone!") XCTAssertEqual(connection.textHeard, "Hello, everyone!")
} }
// Other tests: func testDestructWhileConnected() throws {
// - Connection deconstruction - When I remove the reference to the connection, it stays up. I think self has references because of closures that are still running in the queues. let server = GSServerMock()
if (true) {
let connection = GSConnection()
connection.setMainQueueForTest()
XCTAssertEqual(connection.state, .disconnected)
XCTAssertNil(connection.errorMessage)
XCTAssertEqual(connection.textHeard, "")
connection.connect(destination: "127.0.0.1")
XCTAssertEqual(connection.state, .connecting)
XCTAssert(server.accept())
XCTAssert(server.hasClient())
connection.waitForMain()
waitForConnection(connection: connection)
XCTAssertEqual(connection.state, .connected)
XCTAssertNil(connection.errorMessage)
XCTAssertEqual(connection.textHeard, "")
}
XCTAssert(server.getDisconnect())
}
/* This test hangs at the getDisconnect() line at the end. Something is holding a connection reference.
func testDestructWhileListening() throws {
let server = GSServerMock()
if (true) {
let connection = GSConnection()
connection.setMainQueueForTest()
XCTAssertEqual(connection.state, .disconnected)
XCTAssertNil(connection.errorMessage)
XCTAssertEqual(connection.textHeard, "")
connection.connect(destination: "127.0.0.1")
XCTAssertEqual(connection.state, .connecting)
XCTAssert(server.accept())
XCTAssert(server.hasClient())
connection.waitForMain()
waitForConnection(connection: connection)
XCTAssertEqual(connection.state, .connected)
XCTAssertNil(connection.errorMessage)
XCTAssertEqual(connection.textHeard, "")
let speechForwarder = SpeechForwarderMock()
XCTAssert(!speechForwarder.isListening)
connection.listen(speechForwarder: speechForwarder)
XCTAssert(server.getListenState(isListening: true))
connection.waitForMain()
XCTAssert(speechForwarder.isListening)
XCTAssertEqual(connection.state, .listening)
XCTAssertNil(connection.errorMessage)
XCTAssertEqual(connection.textHeard, "")
}
XCTAssert(server.getDisconnect())
}
*/
/* /*
func testPerformanceExample() throws { func testPerformanceExample() throws {