gh-XcodesOrg-XcodesApp/Xcodes/Backend/HelperClient.swift
Brandon Evans bfb3fd9ea5
Add install functionality
I'm omitting aria2 support for now.
2021-01-07 21:25:07 -07:00

226 lines
7.6 KiB
Swift

import Combine
import Foundation
final class HelperClient {
private var connection: NSXPCConnection?
private func currentConnection() -> NSXPCConnection? {
guard self.connection == nil else {
return self.connection
}
let connection = NSXPCConnection(machServiceName: machServiceName, options: .privileged)
connection.remoteObjectInterface = NSXPCInterface(with: HelperXPCProtocol.self)
connection.invalidationHandler = {
self.connection?.invalidationHandler = nil
DispatchQueue.main.async {
self.connection = nil
}
}
self.connection = connection
connection.resume()
return self.connection
}
private func helper(errorSubject: PassthroughSubject<String, Error>) -> HelperXPCProtocol? {
guard
let helper = self.currentConnection()?.remoteObjectProxyWithErrorHandler({ error in
errorSubject.send(completion: .failure(error))
}) as? HelperXPCProtocol
else { return nil }
return helper
}
func checkIfLatestHelperIsInstalled() -> AnyPublisher<Bool, Never> {
let helperURL = Bundle.main.bundleURL.appendingPathComponent("Contents/Library/LaunchServices/" + machServiceName)
guard
let helperBundleInfo = CFBundleCopyInfoDictionaryForURL(helperURL as CFURL) as? [String: Any],
let bundledHelperVersion = helperBundleInfo["CFBundleShortVersionString"] as? String
else {
return Just(false).eraseToAnyPublisher()
}
return getVersion()
.map { installedHelperVersion in installedHelperVersion == bundledHelperVersion }
.catch { _ in Just(false) }
.eraseToAnyPublisher()
}
func getVersion() -> AnyPublisher<String, Error> {
let connectionErrorSubject = PassthroughSubject<String, Error>()
guard
let helper = self.helper(errorSubject: connectionErrorSubject)
else {
return Fail(error: NSError())
.eraseToAnyPublisher()
}
return Deferred {
Future { promise in
helper.getVersion { version in
promise(.success(version))
}
}
}
// Take values, but fail when connectionErrorSubject fails
.zip(
connectionErrorSubject
.prepend("")
.map { _ in Void() }
)
.map { $0.0 }
.eraseToAnyPublisher()
}
func switchXcodePath(_ absolutePath: String) -> AnyPublisher<Void, Error> {
let connectionErrorSubject = PassthroughSubject<String, Error>()
guard
let helper = self.helper(errorSubject: connectionErrorSubject)
else {
return Fail(error: NSError())
.eraseToAnyPublisher()
}
return Deferred {
Future { promise in
helper.xcodeSelect(absolutePath: absolutePath, completion: { (possibleError) in
if let error = possibleError {
promise(.failure(error))
} else {
promise(.success(()))
}
})
}
}
// Take values, but fail when connectionErrorSubject fails
.zip(
connectionErrorSubject
.prepend("")
.map { _ in Void() }
)
.map { $0.0 }
.eraseToAnyPublisher()
}
func devToolsSecurityEnable() -> AnyPublisher<Void, Error> {
let connectionErrorSubject = PassthroughSubject<String, Error>()
guard
let helper = self.helper(errorSubject: connectionErrorSubject)
else {
return Fail(error: NSError())
.eraseToAnyPublisher()
}
return Deferred {
Future { promise in
helper.devToolsSecurityEnable(completion: { (possibleError) in
if let error = possibleError {
promise(.failure(error))
} else {
promise(.success(()))
}
})
}
}
// Take values, but fail when connectionErrorSubject fails
.zip(
connectionErrorSubject
.prepend("")
.map { _ in Void() }
)
.map { $0.0 }
.eraseToAnyPublisher()
}
func addStaffToDevelopersGroup() -> AnyPublisher<Void, Error> {
let connectionErrorSubject = PassthroughSubject<String, Error>()
guard
let helper = self.helper(errorSubject: connectionErrorSubject)
else {
return Fail(error: NSError())
.eraseToAnyPublisher()
}
return Deferred {
Future { promise in
helper.addStaffToDevelopersGroup(completion: { (possibleError) in
if let error = possibleError {
promise(.failure(error))
} else {
promise(.success(()))
}
})
}
}
// Take values, but fail when connectionErrorSubject fails
.zip(
connectionErrorSubject
.prepend("")
.map { _ in Void() }
)
.map { $0.0 }
.eraseToAnyPublisher()
}
func acceptXcodeLicense(absoluteXcodePath: String) -> AnyPublisher<Void, Error> {
let connectionErrorSubject = PassthroughSubject<String, Error>()
guard
let helper = self.helper(errorSubject: connectionErrorSubject)
else {
return Fail(error: NSError())
.eraseToAnyPublisher()
}
return Deferred {
Future { promise in
helper.acceptXcodeLicense(absoluteXcodePath: absoluteXcodePath, completion: { (possibleError) in
if let error = possibleError {
promise(.failure(error))
} else {
promise(.success(()))
}
})
}
}
// Take values, but fail when connectionErrorSubject fails
.zip(
connectionErrorSubject
.prepend("")
.map { _ in Void() }
)
.map { $0.0 }
.eraseToAnyPublisher()
}
func runFirstLaunch(absoluteXcodePath: String) -> AnyPublisher<Void, Error> {
let connectionErrorSubject = PassthroughSubject<String, Error>()
guard
let helper = self.helper(errorSubject: connectionErrorSubject)
else {
return Fail(error: NSError())
.eraseToAnyPublisher()
}
return Deferred {
Future { promise in
helper.runFirstLaunch(absoluteXcodePath: absoluteXcodePath, completion: { (possibleError) in
if let error = possibleError {
promise(.failure(error))
} else {
promise(.success(()))
}
})
}
}
// Take values, but fail when connectionErrorSubject fails
.zip(
connectionErrorSubject
.prepend("")
.map { _ in Void() }
)
.map { $0.0 }
.eraseToAnyPublisher()
}
}