2020-03-14 18:33:02 -07:00
|
|
|
//
|
|
|
|
|
// ServerMultiplex.swift
|
|
|
|
|
// XIONControlPanel
|
|
|
|
|
//
|
|
|
|
|
// Created by James Magahern on 3/14/20.
|
|
|
|
|
// Copyright © 2020 XION. All rights reserved.
|
|
|
|
|
//
|
|
|
|
|
|
|
|
|
|
import Foundation
|
|
|
|
|
|
2026-07-27 16:23:40 -07:00
|
|
|
protocol ServerMultiplexDelegate: AnyObject
|
2020-03-14 18:33:02 -07:00
|
|
|
{
|
|
|
|
|
func serverMultiplex(_ multiplex: ServerMultiplex, didAddDevices devices: [AnyDevice])
|
|
|
|
|
func serverMultiplex(_ multiplex: ServerMultiplex, devicesStateChanged devices: [AnyDevice])
|
2020-07-04 19:43:50 -07:00
|
|
|
func serverMultiplex(_ multiplex: ServerMultiplex, didReceiveAcknowledgementFromServer server: Server)
|
2026-07-27 16:23:40 -07:00
|
|
|
func serverMultiplexConnectionStatusDidChange(_ multiplex: ServerMultiplex)
|
2020-03-14 18:33:02 -07:00
|
|
|
func serverMultiplex(_ multiplex: ServerMultiplex, didEncounterError error: Error)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
enum ServerMultiplexError : Error
|
|
|
|
|
{
|
2020-05-01 00:16:10 -07:00
|
|
|
case unknownDevice
|
2020-03-14 18:33:02 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
class ServerMultiplex
|
|
|
|
|
{
|
2026-07-27 16:23:40 -07:00
|
|
|
public weak var delegate: ServerMultiplexDelegate?
|
2020-03-14 18:33:02 -07:00
|
|
|
public private(set) var devices = Set<AnyDevice>()
|
|
|
|
|
|
|
|
|
|
private var servers: [Server] = []
|
2026-07-27 16:23:40 -07:00
|
|
|
private var devicesByServer: [ObjectIdentifier: Set<AnyDevice>] = [:]
|
2020-05-01 00:16:10 -07:00
|
|
|
public var numServers: Int { return servers.count }
|
2020-03-14 18:33:02 -07:00
|
|
|
|
|
|
|
|
public func addServer(_ server: Server)
|
|
|
|
|
{
|
2025-06-19 20:43:05 -07:00
|
|
|
server.delegate = self
|
2020-03-14 18:33:02 -07:00
|
|
|
servers.append(server)
|
|
|
|
|
}
|
|
|
|
|
|
2020-07-04 19:43:50 -07:00
|
|
|
/// Returns a status that best represents the status of the whole group, as if it were one device
|
|
|
|
|
public func groupConnectionStatus() -> ConnectionStatus
|
2020-03-14 18:33:02 -07:00
|
|
|
{
|
2020-07-04 19:43:50 -07:00
|
|
|
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
|
2020-03-14 18:33:02 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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 {
|
2020-05-01 00:16:10 -07:00
|
|
|
completion(ServerMultiplexError.unknownDevice)
|
2020-03-14 18:33:02 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public func refreshDevices()
|
|
|
|
|
{
|
2026-07-27 16:23:40 -07:00
|
|
|
servers.forEach { server in
|
2025-06-19 01:02:51 -07:00
|
|
|
server.connect { error in
|
|
|
|
|
if let error {
|
|
|
|
|
print("Multiplexer: error connecting server \(server): \(error)")
|
2026-07-27 16:23:40 -07:00
|
|
|
self.handleError(forServer: server, error: error)
|
2025-06-19 01:02:51 -07:00
|
|
|
}
|
|
|
|
|
}
|
2026-07-27 16:23:40 -07:00
|
|
|
|
2020-04-30 23:36:37 -07:00
|
|
|
server.fetchDevices { (result: Result<[AnyDevice], Error>) in
|
2020-07-04 19:43:50 -07:00
|
|
|
self.handleServerFetchResult(forServer: server, result: result)
|
2020-03-14 18:33:02 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-07-27 16:23:40 -07:00
|
|
|
|
|
|
|
|
public func disconnect()
|
|
|
|
|
{
|
|
|
|
|
servers.forEach { server in
|
|
|
|
|
server.disconnect { error in
|
|
|
|
|
if let error {
|
|
|
|
|
self.handleError(forServer: server, error: error)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2020-03-14 18:33:02 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
extension ServerMultiplex
|
|
|
|
|
{
|
2020-07-04 19:43:50 -07:00
|
|
|
private func handleServerFetchResult(forServer server: Server, result: Result<[AnyDevice], Error>)
|
2020-03-14 18:33:02 -07:00
|
|
|
{
|
2026-07-27 16:23:40 -07:00
|
|
|
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)
|
|
|
|
|
}
|
2020-03-14 18:33:02 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-07-04 19:43:50 -07:00
|
|
|
private func handleDevicesChanged(forServer server: Server, devicesChanged: [AnyDevice])
|
2020-03-14 18:33:02 -07:00
|
|
|
{
|
2020-07-04 19:43:50 -07:00
|
|
|
// First send server acknowledgement
|
|
|
|
|
self.delegate?.serverMultiplex(self, didReceiveAcknowledgementFromServer: server)
|
|
|
|
|
|
|
|
|
|
// Then, optionally notify about new devices or device state changes
|
2026-07-27 16:23:40 -07:00
|
|
|
let serverID = ObjectIdentifier(server)
|
|
|
|
|
let previousDevices = devicesByServer[serverID] ?? []
|
2020-03-14 18:33:02 -07:00
|
|
|
let newDevicesSet = Set<AnyDevice>(devicesChanged)
|
2026-07-27 16:23:40 -07:00
|
|
|
let additions = newDevicesSet.subtracting(previousDevices)
|
|
|
|
|
let removals = previousDevices.subtracting(newDevicesSet)
|
2020-03-14 18:33:02 -07:00
|
|
|
let changed = newDevicesSet.filter { (device: AnyDevice) in
|
2026-07-27 16:23:40 -07:00
|
|
|
if let existing = previousDevices.first(where: { $0.serial == device.serial }) {
|
2020-03-14 18:33:02 -07:00
|
|
|
return existing.state != device.state
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false
|
|
|
|
|
}
|
2026-07-27 16:23:40 -07:00
|
|
|
|
|
|
|
|
devicesByServer[serverID] = newDevicesSet
|
|
|
|
|
devices.subtract(previousDevices)
|
|
|
|
|
devices.formUnion(newDevicesSet)
|
|
|
|
|
|
|
|
|
|
if !additions.isEmpty || !removals.isEmpty {
|
|
|
|
|
delegate?.serverMultiplex(self, didAddDevices: Array(additions))
|
2020-03-14 19:53:18 -07:00
|
|
|
}
|
|
|
|
|
|
2026-07-27 16:23:40 -07:00
|
|
|
if !changed.isEmpty {
|
|
|
|
|
delegate?.serverMultiplex(self, devicesStateChanged: Array(changed))
|
2020-03-14 18:33:02 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-07-04 19:43:50 -07:00
|
|
|
private func handleError(forServer server: Server, error: Error)
|
2020-03-14 18:33:02 -07:00
|
|
|
{
|
|
|
|
|
DispatchQueue.main.async {
|
|
|
|
|
self.delegate?.serverMultiplex(self, didEncounterError: error)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-06-19 20:43:05 -07:00
|
|
|
|
|
|
|
|
extension ServerMultiplex: ServerDelegate
|
|
|
|
|
{
|
|
|
|
|
func server(_ server: any Server, deviceChangedState subjectDevice: AnyDevice) {
|
2026-07-27 16:23:40 -07:00
|
|
|
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)
|
|
|
|
|
}
|
2025-06-19 20:43:05 -07:00
|
|
|
}
|
|
|
|
|
}
|