// // ServerMultiplex.swift // XIONControlPanel // // Created by James Magahern on 3/14/20. // Copyright © 2020 XION. All rights reserved. // import Foundation protocol ServerMultiplexDelegate: AnyObject { func serverMultiplex(_ multiplex: ServerMultiplex, didAddDevices devices: [AnyDevice]) func serverMultiplex(_ multiplex: ServerMultiplex, devicesStateChanged devices: [AnyDevice]) func serverMultiplex(_ multiplex: ServerMultiplex, didReceiveAcknowledgementFromServer server: Server) func serverMultiplexConnectionStatusDidChange(_ multiplex: ServerMultiplex) func serverMultiplex(_ multiplex: ServerMultiplex, didEncounterError error: Error) } enum ServerMultiplexError : Error { case unknownDevice } class ServerMultiplex { public weak var delegate: ServerMultiplexDelegate? public private(set) var devices = Set() private var servers: [Server] = [] private var devicesByServer: [ObjectIdentifier: Set] = [:] public var numServers: Int { return servers.count } public func addServer(_ server: Server) { server.delegate = self 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() { servers.forEach { server in server.connect { error in if let error { print("Multiplexer: error connecting server \(server): \(error)") self.handleError(forServer: server, error: error) } } server.fetchDevices { (result: Result<[AnyDevice], Error>) in self.handleServerFetchResult(forServer: server, result: result) } } } public func disconnect() { servers.forEach { server in server.disconnect { error in if let error { self.handleError(forServer: server, error: error) } } } } } extension ServerMultiplex { private func handleServerFetchResult(forServer server: Server, result: Result<[AnyDevice], Error>) { DispatchQueue.main.async { switch result { case .success(let devices): self.handleDevicesChanged(forServer: server, devicesChanged: devices) case .failure(let error): self.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 serverID = ObjectIdentifier(server) let previousDevices = devicesByServer[serverID] ?? [] let newDevicesSet = Set(devicesChanged) let additions = newDevicesSet.subtracting(previousDevices) let removals = previousDevices.subtracting(newDevicesSet) let changed = newDevicesSet.filter { (device: AnyDevice) in if let existing = previousDevices.first(where: { $0.serial == device.serial }) { return existing.state != device.state } return false } devicesByServer[serverID] = newDevicesSet devices.subtract(previousDevices) devices.formUnion(newDevicesSet) if !additions.isEmpty || !removals.isEmpty { delegate?.serverMultiplex(self, didAddDevices: Array(additions)) } if !changed.isEmpty { delegate?.serverMultiplex(self, devicesStateChanged: Array(changed)) } } private func handleError(forServer server: Server, error: Error) { DispatchQueue.main.async { self.delegate?.serverMultiplex(self, didEncounterError: error) } } } extension ServerMultiplex: ServerDelegate { func server(_ server: any Server, deviceChangedState subjectDevice: AnyDevice) { DispatchQueue.main.async { guard let device = self.devices.first(where: { $0.serial == subjectDevice.serial }) else { return } device.state = subjectDevice.state self.delegate?.serverMultiplex(self, devicesStateChanged: [device]) } } func server(_ server: any Server, connectionStatusChanged status: ConnectionStatus) { DispatchQueue.main.async { self.delegate?.serverMultiplexConnectionStatusDidChange(self) } } }