Separate bonjour concerns in PrinterRequestManager into BonjourBrowser, add unit test for BonjourBrowser

This commit is contained in:
Madeline
2023-03-24 14:42:17 -04:00
parent 0a8670ed71
commit d21fd9d331
9 changed files with 214 additions and 137 deletions

View File

@@ -0,0 +1,75 @@
//
// BonjourBrowser.swift
// Soyuz
//
// Created by maddiefuzz on 3/20/23.
//
import Foundation
import Network
// Protocol defining minimal API for network discovery
// MARK: Net Discovery Protocol
protocol NetworkDiscoveryEngine {
func startScan(queue: DispatchQueue)
func setBrowseResultsChangedHandler(_ handler: @escaping ((Set<NWBrowser.Result>, Set<NWBrowser.Result.Change>) -> Void))
func setStateUpdateHandler(_ handler: @escaping ((NWBrowser.State) -> Void))
}
extension NWBrowser: NetworkDiscoveryEngine {
func startScan(queue: DispatchQueue) {
start(queue: queue)
}
func setBrowseResultsChangedHandler(_ handler: @escaping ((Set<NWBrowser.Result>, Set<NWBrowser.Result.Change>) -> Void)) {
self.browseResultsChangedHandler = handler
}
func setStateUpdateHandler(_ handler: @escaping ((State) -> Void)) {
self.stateUpdateHandler = handler
}
}
// MARK: BonjourBrowser
class BonjourBrowser: ObservableObject {
@Published var NDEngineResults: [NWBrowser.Result] = []
private let nwBrowser: NetworkDiscoveryEngine
var connection: NWConnection!
// TEMPORARY
// var bonjourListener: NWListener?
init(browser: NetworkDiscoveryEngine = NWBrowser(for: .bonjourWithTXTRecord(type: "_moonraker._tcp", domain: "local."), using: .tcp)) {
nwBrowser = browser
// Bonjour browser results changed handler
nwBrowser.setBrowseResultsChangedHandler({ (newResults, changes) in
print("[update] Results changed.")
self.NDEngineResults.removeAll()
newResults.forEach { result in
print(result)
self.NDEngineResults.append(result)
}
})
// Bonjour browser state update handler
nwBrowser.setStateUpdateHandler({ newState in
switch newState {
case .failed(let error):
print("[error] nwbrowser: \(error)")
case .ready:
print("[ready] nwbrowser")
case .setup:
print("[setup] nwbrowser")
default:
break
}
})
nwBrowser.startScan(queue: DispatchQueue.main)
}
}

View File

@@ -11,78 +11,11 @@ import AppKit
import Starscream
// Protocol defining minimal API for network discovery
// MARK: Net Discovery Protocol
protocol NetworkDiscoveryEngine {
func startScan(queue: DispatchQueue)
func setBrowseResultsChangedHandler(_ handler: @escaping ((Set<NWBrowser.Result>, Set<NWBrowser.Result.Change>) -> Void))
func setStateUpdateHandler(_ handler: @escaping ((NWBrowser.State) -> Void))
}
extension NWBrowser: NetworkDiscoveryEngine {
func startScan(queue: DispatchQueue) {
start(queue: queue)
}
func setBrowseResultsChangedHandler(_ handler: @escaping ((Set<NWBrowser.Result>, Set<NWBrowser.Result.Change>) -> Void)) {
self.browseResultsChangedHandler = handler
}
func setStateUpdateHandler(_ handler: @escaping ((State) -> Void)) {
self.stateUpdateHandler = handler
}
}
// Properly formatted JSON-RPC Request for use with Starscream
// MARK: JSON-RPC Request Codable
struct JsonRpcRequest: Codable {
var jsonrpc = "2.0"
let method: String
let params: [String: [String: String?]]
var id = 1
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(jsonrpc, forKey: .jsonrpc)
try container.encode(method, forKey: .method)
try container.encode(params, forKey: .params)
try container.encode(id, forKey: .id)
}
}
// MARK: PrinterRequestManager
//@MainActor
class PrinterRequestManager: ObservableObject, WebSocketDelegate {
let WEBSOCKET_TIMEOUT_INTERVAL: TimeInterval = 60.0
// Debug timestamp stuff
let startDate = Date()
let startDateString: String
let filename: URL
// Debug file writing stuff
func writeToDebugLog(_ output: String) {
do {
let fileHandle = try FileHandle(forWritingTo: filename)
defer {
fileHandle.closeFile()
}
fileHandle.seekToEndOfFile()
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SS"
let debugOutput = String("\(dateFormatter.string(from: Date())) - \(output)\n")
fileHandle.write(debugOutput.data(using: .utf8)!)
} catch {
print("[error] writeToDebugLog - \(error)")
}
}
// Websocket JSON-RPC endpoints discovered via bonjour
@Published var nwBrowserDiscoveredItems: [NWBrowser.Result] = []
// Websocket JSON-RPC published data
@Published var state: String
@Published var progress: Double
@@ -94,53 +27,21 @@ class PrinterRequestManager: ObservableObject, WebSocketDelegate {
@Published var socketHost: String
@Published var socketPort: String
let nwBrowser: NetworkDiscoveryEngine
var connection: NWConnection!
// Published NWConnection for listing connection information
@Published var connection: NWConnection?
private var socket: WebSocket?
private var lastPingDate = Date()
var socket: WebSocket?
var lastPingDate = Date()
// MARK: PRM init()
init(browser: NetworkDiscoveryEngine = NWBrowser(for: .bonjourWithTXTRecord(type: "_moonraker._tcp", domain: "local."), using: .tcp)) {
init() {
state = ""
progress = 0.0
extruderTemperature = 0.0
bedTemperature = 0.0
socketHost = ""
socketPort = ""
nwBrowser = browser
// Debug output-to-file functionality
startDateString = "\(startDate)\n\n"
filename = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0].appendingPathComponent("klippermon-debug-\(startDateString).txt")
do {
try startDateString.write(to: filename, atomically: true, encoding: .utf8)
} catch {
print("[error] Couldn't write to \(filename) - \(error)")
}
// Bonjour browser results changed handler
nwBrowser.setBrowseResultsChangedHandler({ (newResults, changes) in
print("[update] Results changed.")
newResults.forEach { result in
print(result)
self.nwBrowserDiscoveredItems.append(result)
}
})
// Bonjour browser state update handler
nwBrowser.setStateUpdateHandler({ newState in
switch newState {
case .failed(let error):
print("[error] nwbrowser: \(error)")
case .ready:
print("[ready] nwbrowser")
case .setup:
print("[setup] nwbrowser")
default:
break
}
})
// Set up sleep/wake notification observers
let center = NSWorkspace.shared.notificationCenter;
@@ -153,9 +54,6 @@ class PrinterRequestManager: ObservableObject, WebSocketDelegate {
center.addObserver(forName: NSWorkspace.screensDidSleepNotification, object: nil, queue: mainQueue) { notification in
self.screenChangedSleepState(notification)
}
// Start up the bonjour browser, get results and process them in the update handler
nwBrowser.startScan(queue: DispatchQueue.main)
}
// Called from the UI with an endpoint.
@@ -167,15 +65,17 @@ class PrinterRequestManager: ObservableObject, WebSocketDelegate {
print("\(key): \(value)")
})
connection = NWConnection(to: endpoint, using: .tcp)
connection.stateUpdateHandler = { [self] state in
if connection == nil {
connection = NWConnection(to: endpoint, using: .tcp)
}
connection?.stateUpdateHandler = { [self] state in
switch state {
case .ready:
if let innerEndpoint = connection.currentPath?.remoteEndpoint, case .hostPort(let host, let port) = innerEndpoint {
if let innerEndpoint = connection?.currentPath?.remoteEndpoint, case .hostPort(let host, let port) = innerEndpoint {
let hostPortDebugOutput = "Connected to \(host):\(port)"
print(hostPortDebugOutput)
writeToDebugLog(hostPortDebugOutput)
let hostString = "\(host)"
let regex = try! Regex("%(.+)")
@@ -184,7 +84,7 @@ class PrinterRequestManager: ObservableObject, WebSocketDelegate {
print("[sanitized] Resolved \(sanitizedHost):\(port)")
connection.cancel()
connection?.cancel()
DispatchQueue.main.async {
self.socketHost = sanitizedHost
@@ -196,7 +96,11 @@ class PrinterRequestManager: ObservableObject, WebSocketDelegate {
break
}
}
connection.start(queue: .global())
connection?.start(queue: .global())
}
func disconnect() {
socket?.disconnect()
}
@@ -238,7 +142,6 @@ class PrinterRequestManager: ObservableObject, WebSocketDelegate {
case .connected(let headers):
isConnected = true
print("websocket is connected: \(headers)")
writeToDebugLog("Connected to WebSocket")
let jsonRpcRequest = JsonRpcRequest(method: "printer.objects.subscribe",
params: ["objects":
["extruder": nil,
@@ -254,9 +157,7 @@ class PrinterRequestManager: ObservableObject, WebSocketDelegate {
case .disconnected(let reason, let code):
isConnected = false
print("websocket is disconnected: \(reason) with code: \(code)")
writeToDebugLog("Websocket is disconnected: \(reason) with code: \(code)")
case .text(let string):
self.writeToDebugLog(string)
// Check for initial RPC response
let statusResponse = try? JSONDecoder().decode(jsonRpcResponse.self, from: Data(string.utf8))
if let statusResponseSafe = statusResponse {
@@ -267,7 +168,6 @@ class PrinterRequestManager: ObservableObject, WebSocketDelegate {
self.parse_update(updateResponse)
}
case .binary(let data):
self.writeToDebugLog(String(data: data, encoding: .utf8)!)
print("Received data: \(data.count)")
case .ping(_):
print("PING! \(Date())")
@@ -320,3 +220,20 @@ class PrinterRequestManager: ObservableObject, WebSocketDelegate {
}
}
}
// Properly formatted JSON-RPC Request for use with Starscream
// MARK: JSON-RPC Request Codable
struct JsonRpcRequest: Codable {
var jsonrpc = "2.0"
let method: String
let params: [String: [String: String?]]
var id = 1
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(jsonrpc, forKey: .jsonrpc)
try container.encode(method, forKey: .method)
try container.encode(params, forKey: .params)
try container.encode(id, forKey: .id)
}
}