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
|
// 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)
}
}
@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)
let connection = try await serviceConnection(url: url, interruptionHandler: {
self.logger.error("Service connection interrupted")
})
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")
}
}
|