Skip to content

Commit

Permalink
ServiceController protocol
Browse files Browse the repository at this point in the history
  • Loading branch information
tominsam committed Dec 28, 2021
1 parent bf26f87 commit 0a5366c
Show file tree
Hide file tree
Showing 4 changed files with 133 additions and 121 deletions.
9 changes: 2 additions & 7 deletions flametouch/Base UI/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ import SafariServices
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {

let serviceController = ServiceController()
let serviceController: ServiceController = ServiceControllerImpl()
var serviceControllerRefCount: Int = 0
var serviceRefreshTimer: Timer? = nil
var serviceRefreshTimer: Timer?

static var instance: AppDelegate {
// swiftlint:disable:next force_cast
Expand Down Expand Up @@ -97,11 +97,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
}

func sceneDelegateDidEnterBackground(_ sceneDelegate: SceneDelegate) {
#if targetEnvironment(macCatalyst)
// Don't pause the browser on catalyst
return
#endif

// If there are no more forground scenes, stop the service browser
for scene in UIApplication.shared.connectedScenes {
ELog("State is \(scene.activationState.rawValue)")
Expand Down
131 changes: 19 additions & 112 deletions flametouch/Service Discovery/ServiceController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,80 +2,15 @@

import Foundation

// Callers can observe changes. We hold them responsible for retaining
// the callback block, so it'll auto-cleanup. This is the observer object.
class ServiceControllerObserver: NSObject {
let block: ([Host]) -> Void
init(block : @escaping ([Host]) -> Void) {
self.block = block
super.init()
}
}

// Wrap the observer weakly so that we don't retain anything.
struct WeakObserver {
weak var observer: ServiceControllerObserver?
}

// Remove all released observers
extension Array where Element == WeakObserver {
mutating func reap () {
self = self.filter { nil != $0.observer }
}
protocol ServiceController {
var hosts: [Host] { get }
func start()
func stop()
func restart()
func observeServiceChanges(_ block: @escaping ([Host]) -> Void) -> ServiceControllerObserver
}

class ServiceController: NSObject {

private let browser: ServiceBrowser

private var observers = [WeakObserver]()

private var stoppedDate: Date? = Date()

#if DEBUG
static let maxStopTime: TimeInterval = 10
#else
static let maxStopTime: TimeInterval = 180
#endif

/// list of sets of addresses assigned to a single machine
var hosts = [Host]()

override init() {
browser = ServiceBrowser()
super.init()
browser.delegate = self
}

func start() {
// nil stoped date means we're running
guard let stoppedDate = stoppedDate else { return }

// If we were stopped for a while, reset all services,
// otherwise we'll allow the old services list to persist
// even though we restarted the browser
let stoppedTime: TimeInterval = -stoppedDate.timeIntervalSinceNow
ELog("Stopped for \(stoppedTime) (compared to \(ServiceController.maxStopTime))")
if stoppedTime > ServiceController.maxStopTime {
ELog("Resetting service list")
browser.reset()
}
browser.start()
self.stoppedDate = nil
}

func stop() {
browser.stop()
stoppedDate = Date()
}

func restart() {
stop()
browser.reset()
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { [weak self] in
self?.start()
}
}
extension ServiceController {

func hostFor(addresses: Set<String>) -> Host? {
return hosts.first { $0.hasAnyAddress(addresses) }
Expand All @@ -86,40 +21,7 @@ class ServiceController: NSObject {
return host.services.first { $0.type == type && $0.name == name }
}

func observeServiceChanges(_ block: @escaping ([Host]) -> Void) -> ServiceControllerObserver {
let observer = ServiceControllerObserver(block: block)
let weakRef = WeakObserver(observer: observer)
observers.append(weakRef)
block(self.hosts)
return observer
}
}

extension ServiceController: ServiceBrowserDelegate {

private func convertToServices(_ netServices: Set<NetService>) -> Set<Service> {
return Set(netServices.compactMap { ns -> Service? in
if ns.stringAddresses.isEmpty {
// not resolved yet
return nil
}
return Service(
name: ns.name,
type: ns.type,
domain: ns.domain == "local." ? nil : ns.domain,
hostname: ns.hostName,
addresses: ns.stringAddresses,
port: ns.port,
data: ns.txtDict,
lastSeen: Date(),
alive: true)
})
}

func serviceBrowser(_ serviceBrowser: ServiceBrowser, didChangeServices newServices: Set<NetService>) {

let services = convertToServices(newServices)

func groupServices(_ services: Set<Service>) -> [Host] {
// Group the services by IP address - all services that share any address
// are in a group, and a group is a "host".
var groups = [Set<String>: Set<Service>]()
Expand All @@ -138,12 +40,17 @@ extension ServiceController: ServiceBrowserDelegate {

let hosts = groups.map { Host(services: $0.value) }

self.hosts = hosts.sorted { $0.name.lowercased() < $1.name.lowercased() }

observers.reap()
for weakObserver in observers {
weakObserver.observer?.block(self.hosts)
}
return hosts.sorted { $0.name.lowercased() < $1.name.lowercased() }
}

}

// Callers can observe changes. We hold them responsible for retaining
// the callback block, so it'll auto-cleanup. This is the observer object.
class ServiceControllerObserver: NSObject {
let block: ([Host]) -> Void
init(block : @escaping ([Host]) -> Void) {
self.block = block
super.init()
}
}
108 changes: 108 additions & 0 deletions flametouch/Service Discovery/ServiceControllerImpl.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
// Copyright 2021 Thomas Insam <[email protected]>

import Foundation

// Wrap the observer weakly so that we don't retain anything.
private struct WeakObserver {
weak var observer: ServiceControllerObserver?
}

// Remove all released observers
private extension Array where Element == WeakObserver {
mutating func reap () {
self = self.filter { nil != $0.observer }
}
}

class ServiceControllerImpl: NSObject, ServiceController {

private let browser: ServiceBrowser

private var observers = [WeakObserver]()

private var stoppedDate: Date? = Date()

#if DEBUG
static let maxStopTime: TimeInterval = 10
#else
static let maxStopTime: TimeInterval = 180
#endif

/// list of sets of addresses assigned to a single machine
var hosts = [Host]()

override init() {
browser = ServiceBrowser()
super.init()
browser.delegate = self
}

func start() {
// nil stoped date means we're running
guard let stoppedDate = stoppedDate else { return }

// If we were stopped for a while, reset all services,
// otherwise we'll allow the old services list to persist
// even though we restarted the browser
let stoppedTime: TimeInterval = -stoppedDate.timeIntervalSinceNow
ELog("Stopped for \(stoppedTime) (compared to \(ServiceControllerImpl.maxStopTime))")
if stoppedTime > ServiceControllerImpl.maxStopTime {
ELog("Resetting service list")
browser.reset()
}
browser.start()
self.stoppedDate = nil
}

func stop() {
browser.stop()
stoppedDate = Date()
}

func restart() {
stop()
browser.reset()
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { [weak self] in
self?.start()
}
}

func observeServiceChanges(_ block: @escaping ([Host]) -> Void) -> ServiceControllerObserver {
let observer = ServiceControllerObserver(block: block)
let weakRef = WeakObserver(observer: observer)
observers.append(weakRef)
block(self.hosts)
return observer
}
}

extension ServiceControllerImpl: ServiceBrowserDelegate {

private func convertToServices(_ netServices: Set<NetService>) -> Set<Service> {
return Set(netServices.compactMap { ns -> Service? in
if ns.stringAddresses.isEmpty {
// not resolved yet
return nil
}
return Service(
name: ns.name,
type: ns.type,
domain: ns.domain == "local." ? nil : ns.domain,
hostname: ns.hostName,
addresses: ns.stringAddresses,
port: ns.port,
data: ns.txtDict,
lastSeen: Date(),
alive: true)
})
}

func serviceBrowser(_ serviceBrowser: ServiceBrowser, didChangeServices newServices: Set<NetService>) {
hosts = groupServices(convertToServices(newServices))
observers.reap()
for weakObserver in observers {
weakObserver.observer?.block(self.hosts)
}
}

}
6 changes: 4 additions & 2 deletions flametouch/Service Discovery/ServiceExporter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,11 @@ class ServiceExporter {
addressesJson.append(address)
}
serviceJson["addresses"] = addressesJson
var txtData = [String: String]()
for (key, value) in service.data {
serviceJson[key] = value
txtData[key] = value
}
serviceJson["txtData"] = txtData
servicesJson.append(serviceJson)
}
groupJson["services"] = servicesJson
Expand All @@ -52,7 +54,7 @@ class ServiceExporter {
NSLog("path is %@", path.path)
let output = OutputStream(toFileAtPath: path.path, append: false)!
output.open()
JSONSerialization.writeJSONObject(groupsJson, to: output, options: JSONSerialization.WritingOptions.prettyPrinted, error: nil)
JSONSerialization.writeJSONObject(groupsJson, to: output, options: [.prettyPrinted, .sortedKeys], error: nil)
output.close()

return path
Expand Down

0 comments on commit 0a5366c

Please sign in to comment.