1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90
|
// SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
// SPDX-License-Identifier: GPL-2.0-or-later
import Foundation
import NextcloudFileProviderKit
///
/// Facility to establish and handle an XPC connection to the file provider extension.
///
final class ServiceResolver {
enum ServiceResolverError: Error {
case failedConnection
case remoteProxyObjectInvalid
case serviceNotFound
}
let log: any FileProviderLogging
let logger: FileProviderLogger
init(log: any FileProviderLogging) {
self.log = log
self.logger = FileProviderLogger(category: "ServiceResolver", log: log)
}
///
/// Logs the interruption of a connection.
///
private func interruptionHandler() {
logger.error("Interruption handler called. Possibly, the remote file provider extension process exited or crashed.")
}
///
/// Logs the invalidation of a connection.
///
private func invalidationHandler() {
logger.error("Invalidation handler called. Possibly, the remote file provider extension process exited or crashed.")
}
func getService(at url: URL) async throws -> FPUIExtensionService {
logger.info("Getting service for item at location.", [.url: url])
var services: [NSFileProviderServiceName : NSFileProviderService] = [:]
do {
if url.startAccessingSecurityScopedResource() {
logger.debug("Started accessing security-scoped resource.", [.url: url])
services = try await FileManager().fileProviderServicesForItem(at: url)
url.stopAccessingSecurityScopedResource()
logger.debug("Stopped accessing security-scoped resource.", [.url: url])
} else {
logger.error("Failed to access security-scoped resource!", [.url: url])
}
} catch {
logger.error("Failed to get file provider services for item!", [.url: url])
throw error
}
guard let service = services[fpUiExtensionServiceName] else {
logger.error("Failed to find service by name in array of returned services!", [.name: fpUiExtensionServiceName])
throw ServiceResolverError.serviceNotFound
}
let connection: NSXPCConnection?
do {
connection = try await service.fileProviderConnection()
} catch {
logger.error("Failed to establish XPC connection!")
throw ServiceResolverError.failedConnection
}
guard let connection else {
throw ServiceResolverError.failedConnection
}
connection.remoteObjectInterface = NSXPCInterface(with: FPUIExtensionService.self)
connection.interruptionHandler = interruptionHandler
connection.invalidationHandler = invalidationHandler
connection.resume()
guard let proxy = connection.remoteObjectProxy as? FPUIExtensionService else {
logger.error("The remote object proxy does not conform to the expected protocol!")
throw ServiceResolverError.remoteProxyObjectInvalid
}
logger.info("Providing service.")
return proxy
}
}
|