This fixes the problem where either internet connectivity issues on the device or intermittant server issues can cause a persistent "OFFLINE" indicator even though that is not the case. This requires the server protocol to be more granular about what their connection status is, and adds a new signal from the multiplexer to the MainViewController allowing it to notify the delegate that an acknowledgement was heard from one of the servers, allowing the main view controller to update its UI according to whatever status best represents the group of servers as a whole. Testing - This can be tested by using the network link conditioner to simulate connection issues *after* the control panel has connected to all servers in the multiplex. - Tested losing internet connection and regaining internet connection - Tested starting off with no connection and regaining connection later
133 lines
4.2 KiB
Swift
133 lines
4.2 KiB
Swift
//
|
|
// 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<AnyDevice>()
|
|
|
|
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: 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<AnyDevice>(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)
|
|
}
|
|
}
|
|
}
|