diff --git a/AltStore/AppDelegate.swift b/AltStore/AppDelegate.swift index cf00ad84..db144987 100644 --- a/AltStore/AppDelegate.swift +++ b/AltStore/AppDelegate.swift @@ -11,6 +11,7 @@ import UserNotifications import AVFoundation import AltSign +import AltKit import Roxas private enum RefreshError: LocalizedError @@ -68,6 +69,8 @@ class AppDelegate: UIResponder, UIApplicationDelegate { UserDefaults.standard.firstLaunch = Date() } + UserDefaults.standard.preferredServerID = Bundle.main.object(forInfoDictionaryKey: Bundle.Info.serverID) as? String + self.prepareForBackgroundFetch() return true diff --git a/AltStore/Extensions/UserDefaults+AltStore.swift b/AltStore/Extensions/UserDefaults+AltStore.swift index f563bfc0..00421dab 100644 --- a/AltStore/Extensions/UserDefaults+AltStore.swift +++ b/AltStore/Extensions/UserDefaults+AltStore.swift @@ -8,7 +8,11 @@ import Foundation +import Roxas + extension UserDefaults { @NSManaged var firstLaunch: Date? + + @NSManaged var preferredServerID: String? } diff --git a/AltStore/Info.plist b/AltStore/Info.plist index 5555c76d..98452456 100644 --- a/AltStore/Info.plist +++ b/AltStore/Info.plist @@ -4,6 +4,8 @@ ALTDeviceID 1c3416b7b0ab68773e6e7eb7f0d110f7c9353acc + ALTServerID + 1AAAB6FD-E8CE-4B70-8F26-4073215C03B0 CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleExecutable diff --git a/AltStore/Managing Apps/AppManager.swift b/AltStore/Managing Apps/AppManager.swift index 007d55c6..0b113968 100644 --- a/AltStore/Managing Apps/AppManager.swift +++ b/AltStore/Managing Apps/AppManager.swift @@ -175,8 +175,8 @@ private extension AppManager { // Authenticate -> Download (if necessary) -> Resign -> Send -> Install. let group = group ?? OperationGroup() - - guard let server = ServerManager.shared.discoveredServers.first else { + + guard let server = ServerManager.shared.discoveredServers.first(where: { $0.isPreferred }) ?? ServerManager.shared.discoveredServers.first else { DispatchQueue.main.async { group.completionHandler?(.failure(ConnectionError.serverNotFound)) } @@ -330,7 +330,24 @@ private extension AppManager if let error = context.error { - context.group.results[context.bundleIdentifier] = .failure(error) + switch error + { + case let error as ALTServerError where error.code == .deviceNotFound || error.code == .lostConnection: + if let server = context.group.server, server.isPreferred + { + // Preferred server, so report errors normally. + context.group.results[context.bundleIdentifier] = .failure(error) + } + else + { + // Not preferred server, so ignore these specific errors and throw serverNotFound instead. + context.group.results[context.bundleIdentifier] = .failure(ConnectionError.serverNotFound) + } + + case let error: + context.group.results[context.bundleIdentifier] = .failure(error) + } + } else if let installedApp = context.installedApp { diff --git a/AltStore/Server/Server.swift b/AltStore/Server/Server.swift index c10868e9..451306ff 100644 --- a/AltStore/Server/Server.swift +++ b/AltStore/Server/Server.swift @@ -44,8 +44,20 @@ enum ConnectionError: LocalizedError struct Server: Equatable { + var identifier: String var service: NetService + var isPreferred = false + + init?(service: NetService, txtData: Data) + { + let txtDictionary = NetService.dictionary(fromTXTRecord: txtData) + guard let identifierData = txtDictionary["serverID"], let identifier = String(data: identifierData, encoding: .utf8) else { return nil } + + self.identifier = identifier + self.service = service + } + func send(_ payload: T, via connection: NWConnection, prependSize: Bool = true, completionHandler: @escaping (Result) -> Void) { do diff --git a/AltStore/Server/ServerManager.swift b/AltStore/Server/ServerManager.swift index a1cbb47b..a11d1550 100644 --- a/AltStore/Server/ServerManager.swift +++ b/AltStore/Server/ServerManager.swift @@ -20,6 +20,8 @@ class ServerManager: NSObject private let serviceBrowser = NetServiceBrowser() + private var services = Set() + private override init() { super.init() @@ -45,10 +47,24 @@ extension ServerManager self.isDiscovering = false self.discoveredServers.removeAll() + self.services.removeAll() self.serviceBrowser.stop() } } +private extension ServerManager +{ + func addDiscoveredServer(_ server: Server) + { + var server = server + server.isPreferred = (server.identifier == UserDefaults.standard.preferredServerID) + + guard !self.discoveredServers.contains(server) else { return } + + self.discoveredServers.append(server) + } +} + extension ServerManager: NetServiceBrowserDelegate { func netServiceBrowserWillSearch(_ browser: NetServiceBrowser) @@ -68,19 +84,46 @@ extension ServerManager: NetServiceBrowserDelegate func netServiceBrowser(_ browser: NetServiceBrowser, didFind service: NetService, moreComing: Bool) { - let server = Server(service: service) - guard !self.discoveredServers.contains(server) else { return } + service.delegate = self - self.discoveredServers.append(server) + if let txtData = service.txtRecordData(), let server = Server(service: service, txtData: txtData) + { + self.addDiscoveredServer(server) + } + else + { + service.resolve(withTimeout: 3) + self.services.insert(service) + } } func netServiceBrowser(_ browser: NetServiceBrowser, didRemove service: NetService, moreComing: Bool) { - let server = Server(service: service) - - if let index = self.discoveredServers.firstIndex(of: server) + if let index = self.discoveredServers.firstIndex(where: { $0.service == service }) { self.discoveredServers.remove(at: index) } + + self.services.remove(service) + } +} + +extension ServerManager: NetServiceDelegate +{ + func netServiceDidResolveAddress(_ service: NetService) + { + guard let data = service.txtRecordData(), let server = Server(service: service, txtData: data) else { return } + self.addDiscoveredServer(server) + } + + func netService(_ sender: NetService, didNotResolve errorDict: [String : NSNumber]) + { + print("Error resolving net service \(sender).", errorDict) + } + + func netService(_ sender: NetService, didUpdateTXTRecord data: Data) + { + let txtDict = NetService.dictionary(fromTXTRecord: data) + print("Service \(sender) updated TXT Record:", txtDict) } }