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")
}
}
|