File: AuthenticationViewController.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 (130 lines) | stat: -rw-r--r-- 4,228 bytes parent folder | download
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
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
//  SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
//  SPDX-License-Identifier: GPL-2.0-or-later

import AppKit
import FileProviderUI
import NextcloudFileProviderKit
import os

///
/// Our root view controller for the modal sheet Finder is presenting to the user for authentication.
///
class AuthenticationViewController: NSViewController {
    private var authenticationError: Error?
    private var logger: FileProviderLogger!

    var log: (any FileProviderLogging)? {
        didSet {
            guard let log else {
                return
            }

            logger = FileProviderLogger(category: "AuthenticationViewController", log: log)
        }
    }

    var serviceResolver: ServiceResolver?

    @IBOutlet var activityDescription: NSTextField!
    @IBOutlet var cancellationButton: NSButton!
    @IBOutlet var progressIndicator: NSProgressIndicator!

    override func viewDidLoad() {
        super.viewDidLoad()

        guard let domainIdentifier = extensionContext.domainIdentifier else {
            fatalError("Domain identifier is not provided by the extension context!")
            return
        }

        activityDescription.stringValue = String(localized: "Authenticating…")
        cancellationButton.title = String(localized: "Cancel")
    }

    override func viewDidAppear() {
        super.viewDidAppear()
        progressIndicator.startAnimation(self) // This does not start automatically on macOS.
        dispatchAuthenticationTask()
    }

    ///
    /// Resolve the current extension context.
    ///
    override var extensionContext: FPUIActionExtensionContext {
        guard let parent = self.parent as? DocumentActionViewController else {
            fatalError("Parent view controller is not of expected type DocumentActionViewController!")
        }

        return parent.extensionContext
    }

    ///
    /// Action for the cancellation button in the user interface.
    ///
    @IBAction func cancel(_ sender: NSButton) {
        let code: FPUIExtensionErrorCode

        if let authenticationError = self.authenticationError {
            code = FPUIExtensionErrorCode.failed
        } else {
            code = FPUIExtensionErrorCode.userCancelled
        }

        let error = NSError(domain: FPUIErrorDomain, code: Int(code.rawValue))
        // extensionContext.cancelRequest(withError: error)
        extensionContext.completeRequest()
    }

    func dispatchAuthenticationTask() {
        Task {
            do {
                try await self.authenticate()
            } catch {
                updateViewsWithError(error)
            }
        }
    }

    func authenticate() async throws {
        guard let identifier = extensionContext.domainIdentifier else {
            fatalError("Extension context does not provide file provider domain identifier!")
        }

        let domain = NSFileProviderDomain(identifier: identifier, displayName: "")

        guard let manager = NSFileProviderManager(for: domain) else {
            fatalError("Failed to create file provider manager for domain with identifier \(identifier)!")
        }

        let url = try await manager.getUserVisibleURL(for: .rootContainer)

        guard let log else {
            fatalError("Log is not available yet!")
        }

        guard let serviceResolver else {
            fatalError("Service resolver is not available yet!")
        }

        let connection = try await serviceResolver.getService(at: url)

        if let error = await connection.authenticate() {
            logger.error("An error was returned from the authentication call: \(error.localizedDescription)")
            updateViewsWithError(error)
            return
        }

        logger.info("Apparently, the authentication was successful.")
        extensionContext.completeRequest()
    }

    ///
    /// Update the activity description, hide the activity indicator and update the button title.
    ///
    func updateViewsWithError(_ error: Error) {
        authenticationError = error
        progressIndicator.isHidden = true
        activityDescription.stringValue = error.localizedDescription
        cancellationButton.title = String(localized: "Close")
    }
}