mirror of
https://github.com/OpenEmu/OpenEmuKit.git
synced 2025-11-01 11:08:14 +00:00
140 lines
5.8 KiB
Swift
140 lines
5.8 KiB
Swift
// Copyright (c) 2022, OpenEmu Team
|
|
//
|
|
// Redistribution and use in source and binary forms, with or without
|
|
// modification, are permitted provided that the following conditions are met:
|
|
// * Redistributions of source code must retain the above copyright
|
|
// notice, this list of conditions and the following disclaimer.
|
|
// * Redistributions in binary form must reproduce the above copyright
|
|
// notice, this list of conditions and the following disclaimer in the
|
|
// documentation and/or other materials provided with the distribution.
|
|
// * Neither the name of the OpenEmu Team nor the
|
|
// names of its contributors may be used to endorse or promote products
|
|
// derived from this software without specific prior written permission.
|
|
//
|
|
// THIS SOFTWARE IS PROVIDED BY OpenEmu Team ''AS IS'' AND ANY
|
|
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
// DISCLAIMED. IN NO EVENT SHALL OpenEmu Team BE LIABLE FOR ANY
|
|
// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
|
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
|
|
import Foundation
|
|
import os.log
|
|
|
|
extension NSXPCConnection {
|
|
struct BrokerError: Error, LocalizedError {
|
|
var errorDescription: String? {
|
|
NSLocalizedString("Failed to launch helper app", comment: "")
|
|
}
|
|
let failureReason: String?
|
|
}
|
|
|
|
static let helperIdentifierArgumentPrefix = "--org.openemu.broker.id="
|
|
static let helperServiceNameArgumentPrefix = "--org.openemu.broker.name="
|
|
private static var xpcTaskKey = 0
|
|
|
|
static func makeConnection(serviceName name: String, executableURL url: URL) throws -> NSXPCConnection {
|
|
let identifier = UUID().uuidString
|
|
|
|
/// 1. Launch Helper App
|
|
/// This results in the helper app establishing a connection to the broker and registering its
|
|
/// `identifier`
|
|
let task = Process()
|
|
task.executableURL = url
|
|
task.arguments = ["\(Self.helperIdentifierArgumentPrefix)\(identifier)", "\(Self.helperServiceNameArgumentPrefix)\(name)"]
|
|
task.terminationHandler = { task in
|
|
os_log(.error, log: .helper,
|
|
"Helper terminated unexpectedly. { id = %{public}@, reason = %ld, exit = %d }",
|
|
identifier,
|
|
task.terminationReason.rawValue,
|
|
task.terminationStatus)
|
|
}
|
|
task.standardError = FileHandle.standardError
|
|
task.standardOutput = FileHandle.standardOutput
|
|
do {
|
|
try task.run()
|
|
} catch {
|
|
throw BrokerError(failureReason: error.localizedDescription)
|
|
}
|
|
|
|
var success = false
|
|
defer {
|
|
// Terminate helper when broker connection fails
|
|
if !success {
|
|
os_log(.error, log: .helper,
|
|
"Terminating helper; failed to complete handshake. { id = %{public}@ }",
|
|
identifier)
|
|
task.terminationHandler = nil
|
|
task.terminate()
|
|
}
|
|
}
|
|
|
|
/// 2. Launch a connection to the broker
|
|
let cn = NSXPCConnection(serviceName: name)
|
|
cn.invalidationHandler = {
|
|
os_log(.error, log: .helper, "Broker connection was unexpectedely invalidated.")
|
|
}
|
|
|
|
cn.remoteObjectInterface = .init(with: OEXPCMatchMaking.self)
|
|
cn.resume()
|
|
|
|
let mm = cn.remoteObjectProxyWithErrorHandler { error in
|
|
os_log(.error, log: .helper, "Error waiting for reply from OEXPCMatchMaking. { error = %{public}@ }", error.localizedDescription)
|
|
} as? OEXPCMatchMaking
|
|
|
|
guard let mm = mm else {
|
|
os_log(.error, log: .helper, "Unexpected nil for OEXPCMatchMaking proxy.")
|
|
throw BrokerError(failureReason: NSLocalizedString("OEXPCMatchMaking proxy was nil", comment: ""))
|
|
}
|
|
|
|
let sem = DispatchSemaphore(value: 0)
|
|
var endpoint: NSXPCListenerEndpoint?
|
|
mm.retrieveListenerEndpoint(forIdentifier: identifier) { ep in
|
|
endpoint = ep
|
|
sem.signal()
|
|
}
|
|
|
|
#if XPC_WAIT_FOREVER
|
|
sem.wait()
|
|
#else
|
|
if sem.wait(timeout: .now() + .seconds(2)) == .timedOut {
|
|
// mediation of connection between host and helper via broker timed out
|
|
os_log(.error, log: .helper, "Timeout waiting for listener endpoint from broker.")
|
|
|
|
throw BrokerError(failureReason: NSLocalizedString("Timeout waiting for connection from helper app", comment: ""))
|
|
}
|
|
|
|
#endif
|
|
|
|
cn.invalidationHandler = nil
|
|
cn.invalidate()
|
|
|
|
guard let endpoint = endpoint else {
|
|
throw BrokerError(failureReason: NSLocalizedString("Broker endpoint is nil", comment: ""))
|
|
}
|
|
|
|
guard task.isRunning else {
|
|
throw BrokerError(failureReason: NSLocalizedString("Helper unexpectedly terminated", comment: ""))
|
|
}
|
|
|
|
let newCn = NSXPCConnection(listenerEndpoint: endpoint)
|
|
|
|
task.terminationHandler = { [weak newCn] task in
|
|
os_log(.debug, log: .helper,
|
|
"Helper terminated. { id = %{public}@, exit = %d }.",
|
|
identifier,
|
|
task.terminationStatus)
|
|
newCn?.invalidate()
|
|
}
|
|
|
|
objc_setAssociatedObject(newCn, &Self.xpcTaskKey, task, .OBJC_ASSOCIATION_RETAIN)
|
|
|
|
success = true
|
|
return newCn
|
|
}
|
|
}
|