Files
Xaibatsu-Control-Panel/XIONControlPanel/Servers/ServerMultiplex.swift

179 lines
5.8 KiB
Swift

//
// 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<AnyDevice>()
private var servers: [Server] = []
private var devicesByServer: [ObjectIdentifier: Set<AnyDevice>] = [:]
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<AnyDevice>(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)
}
}
}