File: ServiceResolver.swift

package info (click to toggle)
nextcloud-desktop 4.0.6-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 40,740 kB
  • sloc: cpp: 119,301; objc: 752; python: 606; ansic: 389; sh: 377; makefile: 44; javascript: 32; xml: 6
file content (90 lines) | stat: -rw-r--r-- 3,233 bytes parent folder | download | duplicates (3)
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
    }
}