// // ServerMultiplex.swift // XIONControlPanel // // Created by James Magahern on 3/14/20. // Copyright © 2020 XION. All rights reserved. // import Foundation protocol ServerMultiplexDelegate { func serverMultiplex(_ multiplex: ServerMultiplex, didAddDevices devices: [AnyDevice]) func serverMultiplex(_ multiplex: ServerMultiplex, devicesStateChanged devices: [AnyDevice]) func serverMultiplex(_ multiplex: ServerMultiplex, didReceiveAcknowledgementFromServer server: Server) func serverMultiplex(_ multiplex: ServerMultiplex, didEncounterError error: Error) } enum ServerMultiplexError : Error { case unknownDevice } class ServerMultiplex { public var delegate: ServerMultiplexDelegate? public private(set) var devices = Set() private var servers: [Server] = [] public var numServers: Int { return servers.count } public func addServer(_ server: Server) { servers.append(server) } /// Returns a status that best represents the status of the whole group, as if it were one device public func groupConnectionStatus() -> ConnectionStatus { let relevancyRank: (ConnectionStatus) -> Int = { status in switch status { case .disconnected: return 0 case .connected: return 1 case .connecting: return 2 case .error: return 3 } } var groupStatus = ConnectionStatus.connected servers.forEach { server in if relevancyRank(server.connectionStatus) > relevancyRank(groupStatus) { groupStatus = server.connectionStatus } } return groupStatus } public func toggleDeviceState(_ device: AnyDevice, state: DeviceState, completion: @escaping (Error?) -> Void) { if let server = (servers.first { $0.responsibleForDevice(device) }) { server.toggleDevice(device, state: state, completion: completion) // Update internal state if let index = devices.firstIndex(of: device) { devices[index].state = state } } else { completion(ServerMultiplexError.unknownDevice) } } public func refreshDevices() { self.servers.forEach { server in server.connect { error in if let error { print("Multiplexer: error connecting server \(server): \(error)") } } } self.servers.forEach { (server: Server) in server.fetchDevices { (result: Result<[AnyDevice], Error>) in self.handleServerFetchResult(forServer: server, result: result) } } } } extension ServerMultiplex { private func handleServerFetchResult(forServer server: Server, result: Result<[AnyDevice], Error>) { switch result { case .success(let devices): handleDevicesChanged(forServer: server, devicesChanged: devices) case .failure(let error): handleError(forServer: server, error: error) } } private func handleDevicesChanged(forServer server: Server, devicesChanged: [AnyDevice]) { // First send server acknowledgement self.delegate?.serverMultiplex(self, didReceiveAcknowledgementFromServer: server) // Then, optionally notify about new devices or device state changes let newDevicesSet = Set(devicesChanged) let additions = newDevicesSet.subtracting(self.devices) let changed = newDevicesSet.filter { (device: AnyDevice) in if let existing = (devices.first { $0.hashValue == device.hashValue }) { return existing.state != device.state } return false } self.devices = self.devices.union(newDevicesSet) if additions.count > 0 { DispatchQueue.main.async { self.delegate?.serverMultiplex(self, didAddDevices: Array(additions)) } } if changed.count > 0 { DispatchQueue.main.async { self.delegate?.serverMultiplex(self, devicesStateChanged: Array(changed)) } } } private func handleError(forServer server: Server, error: Error) { DispatchQueue.main.async { self.delegate?.serverMultiplex(self, didEncounterError: error) } } }