mirror of
https://github.com/nextcloud/desktop.git
synced 2025-10-26 11:17:43 +00:00
Merge pull request #8792 from nextcloud/feature/8546-rich-logs
feat: Replaced Unified Logging System with Custom Solution.
This commit is contained in:
commit
1595798ace
@ -1,13 +1,13 @@
|
||||
{
|
||||
"originHash" : "7fd0674d455b084268d98aec4518c2ba8f46839c630e4e9f23da1b1241b16612",
|
||||
"originHash" : "42f510a9baeebb4b09104db0837f6dd21eba28f9233a7e61bb67fb3885667f9c",
|
||||
"pins" : [
|
||||
{
|
||||
"identity" : "swift-argument-parser",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/apple/swift-argument-parser",
|
||||
"state" : {
|
||||
"revision" : "309a47b2b1d9b5e991f36961c983ecec72275be3",
|
||||
"version" : "1.6.1"
|
||||
"revision" : "011f0c765fb46d9cac61bca19be0527e99c98c8b",
|
||||
"version" : "1.5.1"
|
||||
}
|
||||
}
|
||||
],
|
||||
|
||||
@ -1,40 +0,0 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
|
||||
import OSLog
|
||||
|
||||
extension Logger {
|
||||
private static var subsystem = Bundle.main.bundleIdentifier!
|
||||
|
||||
static let desktopClientConnection = Logger(
|
||||
subsystem: subsystem, category: "desktopclientconnection")
|
||||
static let fileProviderDomainDefaults = Logger(subsystem: subsystem, category: "fileProviderDomainDefaults")
|
||||
static let fpUiExtensionService = Logger(subsystem: subsystem, category: "fpUiExtensionService")
|
||||
static let fileProviderExtension = Logger(
|
||||
subsystem: subsystem, category: "fileproviderextension")
|
||||
static let keychain = Logger(subsystem: subsystem, category: "keychain")
|
||||
static let shares = Logger(subsystem: subsystem, category: "shares")
|
||||
static let logger = Logger(subsystem: subsystem, category: "logger")
|
||||
|
||||
@available(macOSApplicationExtension 12.0, *)
|
||||
static func logEntries(interval: TimeInterval = -3600) -> (Array<String>?, Error?) {
|
||||
do {
|
||||
let logStore = try OSLogStore(scope: .currentProcessIdentifier)
|
||||
let timeDate = Date().addingTimeInterval(interval)
|
||||
let logPosition = logStore.position(date: timeDate)
|
||||
let entries = try logStore.getEntries(at: logPosition)
|
||||
|
||||
return (entries
|
||||
.compactMap { $0 as? OSLogEntryLog }
|
||||
.filter { $0.subsystem == Logger.subsystem }
|
||||
.map { $0.composedMessage }, nil)
|
||||
|
||||
} catch let error {
|
||||
Logger.logger.error("Could not acquire os log store: \(error)");
|
||||
return (nil, error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -3,6 +3,7 @@
|
||||
|
||||
import FileProvider
|
||||
import Foundation
|
||||
import NextcloudFileProviderKit
|
||||
import os
|
||||
|
||||
///
|
||||
@ -24,8 +25,11 @@ struct FileProviderDomainDefaults {
|
||||
///
|
||||
let identifier: NSFileProviderDomainIdentifier
|
||||
|
||||
init(identifier: NSFileProviderDomainIdentifier) {
|
||||
let logger: FileProviderLogger
|
||||
|
||||
init(identifier: NSFileProviderDomainIdentifier, log: any FileProviderLogging) {
|
||||
self.identifier = identifier
|
||||
self.logger = FileProviderLogger(category: "FileProviderDomainDefaults", log: log)
|
||||
}
|
||||
|
||||
///
|
||||
@ -58,10 +62,10 @@ struct FileProviderDomainDefaults {
|
||||
let identifier = self.identifier.rawValue
|
||||
|
||||
if let value = internalConfig[ConfigKey.serverUrl.rawValue] as? String {
|
||||
Logger.fileProviderDomainDefaults.debug("Returning existing value \"\(value)\" for \"serverUrl\" for file provider domain \"\(identifier)\".")
|
||||
logger.debug("Returning existing value \"\(value)\" for \"serverUrl\" for file provider domain \"\(identifier)\".")
|
||||
return value
|
||||
} else {
|
||||
Logger.fileProviderDomainDefaults.debug("No existing value for \"serverUrl\" for file provider domain \"\(identifier)\" found.")
|
||||
logger.debug("No existing value for \"serverUrl\" for file provider domain \"\(identifier)\" found.")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
@ -70,10 +74,10 @@ struct FileProviderDomainDefaults {
|
||||
let identifier = self.identifier.rawValue
|
||||
|
||||
if newValue == nil {
|
||||
Logger.fileProviderDomainDefaults.debug("Removing key \"serverUrl\" for file provider domain \"\(identifier)\" because the new value is nil.")
|
||||
logger.debug("Removing key \"serverUrl\" for file provider domain \"\(identifier)\" because the new value is nil.")
|
||||
internalConfig.removeValue(forKey: ConfigKey.serverUrl.rawValue)
|
||||
} else if newValue == "" {
|
||||
Logger.fileProviderDomainDefaults.error("Ignoring new value for \"serverUrl\" because it is an empty string for file provider domain \"\(identifier)\"!")
|
||||
logger.error("Ignoring new value for \"serverUrl\" because it is an empty string for file provider domain \"\(identifier)\"!")
|
||||
} else {
|
||||
internalConfig[ConfigKey.serverUrl.rawValue] = newValue
|
||||
}
|
||||
@ -88,7 +92,7 @@ struct FileProviderDomainDefaults {
|
||||
let identifier = self.identifier.rawValue
|
||||
|
||||
if let value = internalConfig[ConfigKey.trashDeletionEnabled.rawValue] as? Bool {
|
||||
Logger.fileProviderDomainDefaults.debug("Returning existing value \"\(value)\" for \"trashDeletionEnabled\" for file provider domain \"\(identifier)\".")
|
||||
logger.debug("Returning existing value \"\(value)\" for \"trashDeletionEnabled\" for file provider domain \"\(identifier)\".")
|
||||
return value
|
||||
} else {
|
||||
return false
|
||||
@ -98,7 +102,7 @@ struct FileProviderDomainDefaults {
|
||||
set {
|
||||
let identifier = self.identifier.rawValue
|
||||
|
||||
Logger.fileProviderDomainDefaults.error("Setting value \"\(newValue)\" for \"trashDeletionEnabled\" for file provider domain \"\(identifier)\".")
|
||||
logger.error("Setting value \"\(newValue)\" for \"trashDeletionEnabled\" for file provider domain \"\(identifier)\".")
|
||||
internalConfig[ConfigKey.trashDeletionEnabled.rawValue] = newValue
|
||||
}
|
||||
}
|
||||
@ -111,10 +115,10 @@ struct FileProviderDomainDefaults {
|
||||
let identifier = self.identifier.rawValue
|
||||
|
||||
if let value = internalConfig[ConfigKey.user.rawValue] as? String {
|
||||
Logger.fileProviderDomainDefaults.debug("Returning existing value \"\(value)\" for \"user\" for file provider domain \"\(identifier)\".")
|
||||
logger.debug("Returning existing value \"\(value)\" for \"user\" for file provider domain \"\(identifier)\".")
|
||||
return value
|
||||
} else {
|
||||
Logger.fileProviderDomainDefaults.debug("No existing value for \"user\" for file provider domain \"\(identifier)\" found.")
|
||||
logger.debug("No existing value for \"user\" for file provider domain \"\(identifier)\" found.")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
@ -123,10 +127,10 @@ struct FileProviderDomainDefaults {
|
||||
let identifier = self.identifier.rawValue
|
||||
|
||||
if newValue == nil {
|
||||
Logger.fileProviderDomainDefaults.debug("Removing key \"user\" for file provider domain \"\(identifier)\" because the new value is nil.")
|
||||
logger.debug("Removing key \"user\" for file provider domain \"\(identifier)\" because the new value is nil.")
|
||||
internalConfig.removeValue(forKey: ConfigKey.user.rawValue)
|
||||
} else if newValue == "" {
|
||||
Logger.fileProviderDomainDefaults.error("Ignoring new value for \"user\" because it is an empty string for file provider domain \"\(identifier)\"!")
|
||||
logger.error("Ignoring new value for \"user\" because it is an empty string for file provider domain \"\(identifier)\"!")
|
||||
} else {
|
||||
internalConfig[ConfigKey.user.rawValue] = newValue
|
||||
}
|
||||
@ -141,10 +145,10 @@ struct FileProviderDomainDefaults {
|
||||
let identifier = self.identifier.rawValue
|
||||
|
||||
if let value = internalConfig[ConfigKey.userId.rawValue] as? String {
|
||||
Logger.fileProviderDomainDefaults.debug("Returning existing value \"\(value)\" for \"userId\" for file provider domain \"\(identifier)\".")
|
||||
logger.debug("Returning existing value \"\(value)\" for \"userId\" for file provider domain \"\(identifier)\".")
|
||||
return value
|
||||
} else {
|
||||
Logger.fileProviderDomainDefaults.debug("No existing value for \"userId\" for file provider domain \"\(identifier)\" found.")
|
||||
logger.debug("No existing value for \"userId\" for file provider domain \"\(identifier)\" found.")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
@ -153,10 +157,10 @@ struct FileProviderDomainDefaults {
|
||||
let identifier = self.identifier.rawValue
|
||||
|
||||
if newValue == nil {
|
||||
Logger.fileProviderDomainDefaults.debug("Removing key \"userId\" for file provider domain \"\(identifier)\" because the new value is nil.")
|
||||
logger.debug("Removing key \"userId\" for file provider domain \"\(identifier)\" because the new value is nil.")
|
||||
internalConfig.removeValue(forKey: ConfigKey.userId.rawValue)
|
||||
} else if newValue == "" {
|
||||
Logger.fileProviderDomainDefaults.error("Ignoring new value for \"userId\" because it is an empty string for file provider domain \"\(identifier)\"!")
|
||||
logger.error("Ignoring new value for \"userId\" because it is an empty string for file provider domain \"\(identifier)\"!")
|
||||
} else {
|
||||
internalConfig[ConfigKey.userId.rawValue] = newValue
|
||||
}
|
||||
|
||||
@ -33,7 +33,7 @@ extension FileProviderExtension: NSFileProviderServicing, ChangeNotificationInte
|
||||
for itemIdentifier: NSFileProviderItemIdentifier,
|
||||
completionHandler: @escaping ([NSFileProviderServiceSource]?, Error?) -> Void
|
||||
) -> Progress {
|
||||
Logger.desktopClientConnection.debug("Serving supported service sources")
|
||||
logger.debug("Serving supported service sources")
|
||||
let clientCommService = ClientCommunicationService(fpExtension: self)
|
||||
let fpuiExtService = FPUIExtensionServiceSource(fpExtension: self)
|
||||
let services: [NSFileProviderServiceSource] = [clientCommService, fpuiExtService]
|
||||
@ -55,9 +55,7 @@ extension FileProviderExtension: NSFileProviderServicing, ChangeNotificationInte
|
||||
|
||||
private func signalEnumeratorAfterAccountSetup() {
|
||||
guard let fpManager = NSFileProviderManager(for: domain) else {
|
||||
Logger.fileProviderExtension.error(
|
||||
"Could not get file provider manager for domain \(self.domain.displayName, privacy: .public), cannot notify after account setup"
|
||||
)
|
||||
logger.error("Could not get file provider manager for domain \(self.domain.displayName), cannot notify after account setup")
|
||||
return
|
||||
}
|
||||
|
||||
@ -65,32 +63,24 @@ extension FileProviderExtension: NSFileProviderServicing, ChangeNotificationInte
|
||||
|
||||
fpManager.signalErrorResolved(NSFileProviderError(.notAuthenticated)) { error in
|
||||
if error != nil {
|
||||
Logger.fileProviderExtension.error(
|
||||
"Error resolving not authenticated, received error: \(error!.localizedDescription)"
|
||||
)
|
||||
self.logger.error("Error resolving not authenticated, received error: \(error!.localizedDescription)")
|
||||
}
|
||||
}
|
||||
|
||||
Logger.fileProviderExtension.debug(
|
||||
"Signalling enumerators for user \(self.ncAccount!.username) at server \(self.ncAccount!.serverUrl, privacy: .public)"
|
||||
)
|
||||
logger.debug("Signalling enumerators for user \(self.ncAccount!.username) at server \(self.ncAccount!.serverUrl)")
|
||||
|
||||
notifyChange()
|
||||
}
|
||||
|
||||
func notifyChange() {
|
||||
guard let fpManager = NSFileProviderManager(for: domain) else {
|
||||
Logger.fileProviderExtension.error(
|
||||
"Could not get file provider manager for domain \(self.domain.displayName, privacy: .public), cannot notify changes"
|
||||
)
|
||||
logger.error("Could not get file provider manager for domain \(self.domain.displayName), cannot notify changes")
|
||||
return
|
||||
}
|
||||
|
||||
fpManager.signalEnumerator(for: .workingSet) { error in
|
||||
if error != nil {
|
||||
Logger.fileProviderExtension.error(
|
||||
"Error signalling enumerator for working set, received error: \(error!.localizedDescription, privacy: .public)"
|
||||
)
|
||||
self.logger.error("Error signalling enumerator for working set, received error: \(error!.localizedDescription)")
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -109,34 +99,34 @@ extension FileProviderExtension: NSFileProviderServicing, ChangeNotificationInte
|
||||
) {
|
||||
let account = Account(user: user, id: userId, serverUrl: serverUrl, password: password)
|
||||
|
||||
Logger.fileProviderExtension.info("Setting up domain account for user: \(user, privacy: .public), userId: \(userId, privacy: .public), serverUrl: \(serverUrl, privacy: .public), password: \(password.isEmpty ? "<empty>" : "<not-empty>", privacy: .public), ncKitAccount: \(account.ncKitAccount, privacy: .public)")
|
||||
logger.info("Setting up domain account for user: \(user), userId: \(userId), serverUrl: \(serverUrl), password: \(password.isEmpty ? "<empty>" : "<not-empty>"), ncKitAccount: \(account.ncKitAccount)")
|
||||
|
||||
guard account != ncAccount else {
|
||||
Logger.fileProviderExtension.warning("Cancelling domain account setup because of receiving the same account information repeatedly!")
|
||||
logger.info("Cancelling domain account setup because of receiving the same account information repeatedly!")
|
||||
completionHandler?(NSError(.invalidCredentials))
|
||||
return
|
||||
}
|
||||
|
||||
guard password.isEmpty == false else {
|
||||
Logger.fileProviderExtension.warning("Cancelling domain account setup because \"password\" is an empty string!")
|
||||
logger.info("Cancelling domain account setup because \"password\" is an empty string!")
|
||||
completionHandler?(NSError(.missingAccountInformation))
|
||||
return
|
||||
}
|
||||
|
||||
guard serverUrl.isEmpty == false else {
|
||||
Logger.fileProviderExtension.warning("Cancelling domain account setup because \"serverUrl\" is an empty string!")
|
||||
logger.info("Cancelling domain account setup because \"serverUrl\" is an empty string!")
|
||||
completionHandler?(NSError(.missingAccountInformation))
|
||||
return
|
||||
}
|
||||
|
||||
guard user.isEmpty == false else {
|
||||
Logger.fileProviderExtension.warning("Cancelling domain account setup because \"user\" is an empty string!")
|
||||
logger.info("Cancelling domain account setup because \"user\" is an empty string!")
|
||||
completionHandler?(NSError(.missingAccountInformation))
|
||||
return
|
||||
}
|
||||
|
||||
guard userId.isEmpty == false else {
|
||||
Logger.fileProviderExtension.warning("Cancelling domain account setup because \"userId\" is an empty string!")
|
||||
logger.info("Cancelling domain account setup because \"userId\" is an empty string!")
|
||||
completionHandler?(NSError(.missingAccountInformation))
|
||||
return
|
||||
}
|
||||
@ -145,7 +135,7 @@ extension FileProviderExtension: NSFileProviderServicing, ChangeNotificationInte
|
||||
config.serverUrl = serverUrl
|
||||
config.user = user
|
||||
config.userId = userId
|
||||
Keychain.savePassword(password, for: user, on: serverUrl)
|
||||
keychain.savePassword(password, for: user, on: serverUrl)
|
||||
NextcloudKit.clearAccountErrorState(for: account.ncKitAccount)
|
||||
|
||||
Task {
|
||||
@ -156,7 +146,6 @@ extension FileProviderExtension: NSFileProviderServicing, ChangeNotificationInte
|
||||
userId: userId,
|
||||
password: password,
|
||||
userAgent: userAgent,
|
||||
nextcloudVersion: 25,
|
||||
groupIdentifier: ""
|
||||
)
|
||||
|
||||
@ -172,28 +161,28 @@ extension FileProviderExtension: NSFileProviderServicing, ChangeNotificationInte
|
||||
break
|
||||
}
|
||||
|
||||
Logger.fileProviderExtension.info("\(user, privacy: .public) authentication try timed out. Trying again soon.")
|
||||
logger.info("\(user) authentication try timed out. Trying again soon.")
|
||||
try? await Task.sleep(nanoseconds: authTimeout)
|
||||
}
|
||||
|
||||
switch (authAttemptState) {
|
||||
case .authenticationError:
|
||||
Logger.fileProviderExtension.error("Authentication of \"\(user, privacy: .public)\" failed due to bad credentials, cancelling domain account setup!")
|
||||
logger.error("Authentication of \"\(user)\" failed due to bad credentials, cancelling domain account setup!")
|
||||
completionHandler?(NSError(.invalidCredentials))
|
||||
return
|
||||
case .connectionError:
|
||||
// Despite multiple connection attempts we are still getting connection issues.
|
||||
// Connection error should be provided
|
||||
Logger.fileProviderExtension.error("Authentication of \"\(user, privacy: .public)\" try failed, no connection.")
|
||||
logger.error("Authentication of \"\(user)\" try failed, no connection.")
|
||||
completionHandler?(NSError(.connection))
|
||||
return
|
||||
case .success:
|
||||
Logger.fileProviderExtension.info("Successfully authenticated! Nextcloud account set up in file provider extension. User: \(user, privacy: .public) at server: \(serverUrl, privacy: .public)")
|
||||
logger.info("Successfully authenticated! Nextcloud account set up in file provider extension. User: \(user) at server: \(serverUrl)")
|
||||
}
|
||||
|
||||
Task { @MainActor in
|
||||
ncAccount = account
|
||||
dbManager = FilesDatabaseManager(account: account, fileProviderDomainIdentifier: domain.identifier)
|
||||
dbManager = FilesDatabaseManager(account: account, fileProviderDomainIdentifier: domain.identifier, log: log)
|
||||
|
||||
if let changeObserver {
|
||||
changeObserver.invalidate()
|
||||
@ -205,10 +194,11 @@ extension FileProviderExtension: NSFileProviderServicing, ChangeNotificationInte
|
||||
remoteInterface: ncKit,
|
||||
changeNotificationInterface: self,
|
||||
domain: domain,
|
||||
dbManager: dbManager
|
||||
dbManager: dbManager,
|
||||
log: log
|
||||
)
|
||||
} else {
|
||||
Logger.fileProviderExtension.error("Invalid db manager, cannot start RCO")
|
||||
logger.error("Invalid db manager, cannot start RCO")
|
||||
}
|
||||
|
||||
ncKit.setup(groupIdentifier: Bundle.main.bundleIdentifier!, delegate: changeObserver)
|
||||
@ -219,9 +209,7 @@ extension FileProviderExtension: NSFileProviderServicing, ChangeNotificationInte
|
||||
}
|
||||
|
||||
@objc func removeAccountConfig() {
|
||||
Logger.fileProviderExtension.info(
|
||||
"Received instruction to remove account data for user \(self.ncAccount!.username, privacy: .public) at server \(self.ncAccount!.serverUrl, privacy: .public)"
|
||||
)
|
||||
logger.info("Received instruction to remove account data for user \(self.ncAccount!.username) at server \(self.ncAccount!.serverUrl)")
|
||||
ncAccount = nil
|
||||
dbManager = nil
|
||||
}
|
||||
@ -246,7 +234,7 @@ extension FileProviderExtension: NSFileProviderServicing, ChangeNotificationInte
|
||||
actionsLock.unlock()
|
||||
|
||||
guard let argument else { return }
|
||||
Logger.fileProviderExtension.debug("Reporting sync \(argument)")
|
||||
logger.debug("Reporting sync \(argument)")
|
||||
let message = command + ":" + argument + "\n"
|
||||
socketClient?.sendMessage(message)
|
||||
}
|
||||
|
||||
@ -27,7 +27,7 @@ extension FileProviderExtension: NSFileProviderCustomAction {
|
||||
completionHandler: completionHandler
|
||||
)
|
||||
default:
|
||||
Logger.fileProviderExtension.error("Unsupported action: \(actionIdentifier.rawValue)")
|
||||
logger.error("Unsupported action: \(actionIdentifier.rawValue)")
|
||||
completionHandler(NSError(domain: NSCocoaErrorDomain, code: NSFeatureUnsupportedError))
|
||||
return Progress()
|
||||
}
|
||||
@ -39,16 +39,12 @@ extension FileProviderExtension: NSFileProviderCustomAction {
|
||||
completionHandler: @escaping ((any Error)?) -> Void
|
||||
) -> Progress {
|
||||
guard let ncAccount else {
|
||||
Logger.fileProviderExtension.error(
|
||||
"Not setting keep offline for items, account not set up yet."
|
||||
)
|
||||
logger.error("Not setting keep offline for items, account not set up yet.")
|
||||
completionHandler(NSFileProviderError(.notAuthenticated))
|
||||
return Progress()
|
||||
}
|
||||
guard let dbManager else {
|
||||
Logger.fileProviderExtension.error(
|
||||
"Not setting keep offline for items as database is unreachable."
|
||||
)
|
||||
logger.error("Not setting keep offline for items as database is unreachable.")
|
||||
completionHandler(NSFileProviderError(.cannotSynchronize))
|
||||
return Progress()
|
||||
}
|
||||
@ -57,7 +53,7 @@ extension FileProviderExtension: NSFileProviderCustomAction {
|
||||
|
||||
// If there are no items, complete successfully immediately.
|
||||
if itemIdentifiers.isEmpty {
|
||||
Logger.fileProviderExtension.info("No items to process for keepDownloaded action.")
|
||||
logger.info("No items to process for keepDownloaded action.")
|
||||
completionHandler(nil)
|
||||
return progress
|
||||
}
|
||||
@ -78,7 +74,8 @@ extension FileProviderExtension: NSFileProviderCustomAction {
|
||||
identifier: identifier,
|
||||
account: ncAccount,
|
||||
remoteInterface: localNcKit,
|
||||
dbManager: dbManager
|
||||
dbManager: dbManager,
|
||||
log: self.log
|
||||
) else {
|
||||
throw NSError.fileProviderErrorForNonExistentItem(
|
||||
withIdentifier: identifier
|
||||
@ -92,20 +89,10 @@ extension FileProviderExtension: NSFileProviderCustomAction {
|
||||
progress.completedUnitCount = 1
|
||||
}
|
||||
}
|
||||
Logger.fileProviderExtension.info(
|
||||
"""
|
||||
All items successfully processed for
|
||||
keepDownloaded=\(keepDownloaded, privacy: .public)
|
||||
"""
|
||||
)
|
||||
logger.info("All items successfully processed for keepDownloaded=\(keepDownloaded)")
|
||||
completionHandler(nil)
|
||||
} catch let error {
|
||||
Logger.fileProviderExtension.error(
|
||||
"""
|
||||
Error during keepDownloaded=\(keepDownloaded, privacy: .public)
|
||||
action: \(error.localizedDescription, privacy: .public)
|
||||
"""
|
||||
)
|
||||
logger.error("Error during keepDownloaded=\(keepDownloaded) action: \(error.localizedDescription)")
|
||||
completionHandler(error)
|
||||
}
|
||||
}
|
||||
|
||||
@ -36,6 +36,7 @@ extension FileProviderExtension: NSFileProviderThumbnailing {
|
||||
usingRemoteInterface: self.ncKit,
|
||||
andDatabase: dbManager,
|
||||
perThumbnailCompletionHandler: perThumbnailCompletionHandler,
|
||||
log: log,
|
||||
completionHandler: completionHandler
|
||||
)
|
||||
}
|
||||
|
||||
@ -12,6 +12,10 @@ import OSLog
|
||||
@objc class FileProviderExtension: NSObject, NSFileProviderReplicatedExtension {
|
||||
let domain: NSFileProviderDomain
|
||||
|
||||
let keychain: Keychain
|
||||
let log: any FileProviderLogging
|
||||
let logger: FileProviderLogger
|
||||
|
||||
///
|
||||
/// NextcloudKit instance used by this file provider extension object.
|
||||
///
|
||||
@ -25,13 +29,13 @@ import OSLog
|
||||
lazy var ncKitBackground = NKBackground(nkCommonInstance: ncKit.nkCommonInstance)
|
||||
lazy var socketClient: LocalSocketClient? = {
|
||||
guard let containerUrl = pathForAppGroupContainer() else {
|
||||
Logger.fileProviderExtension.critical("Won't start socket client, no container url")
|
||||
logger.fault("Won't start socket client, no container URL available!")
|
||||
return nil;
|
||||
}
|
||||
|
||||
let socketPath = containerUrl.appendingPathComponent(
|
||||
".fileprovidersocket", conformingTo: .archive)
|
||||
let lineProcessor = FileProviderSocketLineProcessor(delegate: self)
|
||||
let lineProcessor = FileProviderSocketLineProcessor(delegate: self, log: log)
|
||||
return LocalSocketClient(socketPath: socketPath.path, lineProcessor: lineProcessor)
|
||||
}()
|
||||
|
||||
@ -50,31 +54,32 @@ import OSLog
|
||||
// Since it's not desirable to cancel a long recursive enumeration half-way through, we do the
|
||||
// fast enumeration by default. We prompt the user on the client side to run a proper, full
|
||||
// enumeration if they want for safety.
|
||||
lazy var config = FileProviderDomainDefaults(identifier: domain.identifier)
|
||||
lazy var config = FileProviderDomainDefaults(identifier: domain.identifier, log: log)
|
||||
|
||||
required init(domain: NSFileProviderDomain) {
|
||||
// The containing application must create a domain using
|
||||
// `NSFileProviderManager.add(_:, completionHandler:)`. The system will then launch the
|
||||
// application extension process, call `FileProviderExtension.init(domain:)` to instantiate
|
||||
// the extension for that domain, and call methods on the instance.
|
||||
Logger.fileProviderExtension.debug("Initializing with domain identifier: \(domain.identifier.rawValue)")
|
||||
self.domain = domain
|
||||
|
||||
// Set up logging.
|
||||
self.log = FileProviderLog(fileProviderDomainIdentifier: domain.identifier)
|
||||
self.logger = FileProviderLogger(category: "FileProviderExtension", log: log)
|
||||
logger.debug("Initializing with domain identifier: \(domain.identifier.rawValue)")
|
||||
|
||||
// Set up NextcloudKit.
|
||||
self.ncKit = NextcloudKit.shared
|
||||
|
||||
if let logDirectory = FileManager.default.fileProviderDomainSupportDirectory(for: domain.identifier) {
|
||||
Logger.fileProviderExtension.info("NextcloudKit log file directory: \(logDirectory.path)")
|
||||
#if DEBUG
|
||||
NKLogFileManager.configure(logLevel: .verbose)
|
||||
#else
|
||||
NKLogFileManager.configure(logLevel: .normal)
|
||||
#endif
|
||||
|
||||
#if DEBUG
|
||||
let nextcloudKitLogLevel = 2
|
||||
#else
|
||||
let nextcloudKitLogLevel = 1
|
||||
#endif
|
||||
logger.info("Current NextcloudKit log file URL: \(NKLogFileManager.shared.currentLogFileURL().absoluteString)")
|
||||
|
||||
Logger.fileProviderExtension.info("NextcloudKit log level: \(nextcloudKitLogLevel)")
|
||||
ncKit.setupLog(pathLog: logDirectory.path, levelLog: nextcloudKitLogLevel, copyLogToDocumentDirectory: true)
|
||||
}
|
||||
self.keychain = Keychain(log: log)
|
||||
|
||||
super.init()
|
||||
socketClient?.start()
|
||||
@ -82,7 +87,7 @@ import OSLog
|
||||
|
||||
func invalidate() {
|
||||
// TODO: cleanup any resources
|
||||
Logger.fileProviderExtension.debug("Extension for domain \(self.domain.displayName, privacy: .public) is being torn down")
|
||||
logger.debug("Extension for domain \(self.domain.displayName) is being torn down")
|
||||
}
|
||||
|
||||
func insertSyncAction(_ actionId: UUID) {
|
||||
@ -119,22 +124,12 @@ import OSLog
|
||||
completionHandler: @escaping (NSFileProviderItem?, Error?) -> Void
|
||||
) -> Progress {
|
||||
guard let ncAccount else {
|
||||
Logger.fileProviderExtension.error(
|
||||
"""
|
||||
Not fetching item for identifier: \(identifier.rawValue, privacy: .public)
|
||||
as account not set up yet.
|
||||
"""
|
||||
)
|
||||
logger.error("Not fetching item because account not set up yet.", [.item: identifier])
|
||||
completionHandler(nil, NSFileProviderError(.notAuthenticated))
|
||||
return Progress()
|
||||
}
|
||||
guard let dbManager else {
|
||||
Logger.fileProviderExtension.error(
|
||||
"""
|
||||
Not fetching item for identifier: \(identifier.rawValue, privacy: .public)
|
||||
as database is unreachable
|
||||
"""
|
||||
)
|
||||
logger.error("Not fetching item because database is unavailable.", [.item: identifier])
|
||||
completionHandler(nil, NSFileProviderError(.cannotSynchronize))
|
||||
return Progress()
|
||||
}
|
||||
@ -146,7 +141,8 @@ import OSLog
|
||||
identifier: identifier,
|
||||
account: ncAccount,
|
||||
remoteInterface: ncKit,
|
||||
dbManager: dbManager
|
||||
dbManager: dbManager,
|
||||
log: log
|
||||
) {
|
||||
progress.completedUnitCount = 1
|
||||
completionHandler(item, nil)
|
||||
@ -168,15 +164,11 @@ import OSLog
|
||||
let actionId = UUID()
|
||||
insertSyncAction(actionId)
|
||||
|
||||
Logger.fileProviderExtension.debug(
|
||||
"Received request to fetch contents of item with identifier: \(itemIdentifier.rawValue, privacy: .public)"
|
||||
)
|
||||
logger.info("Received request to fetch contents of item.", [.item: itemIdentifier])
|
||||
|
||||
guard requestedVersion == nil else {
|
||||
// TODO: Add proper support for file versioning
|
||||
Logger.fileProviderExtension.error(
|
||||
"Can't return contents for a specific version as this is not supported."
|
||||
)
|
||||
logger.error("Can't return contents for a specific version as this is not supported.", [.item: itemIdentifier])
|
||||
insertErrorAction(actionId)
|
||||
completionHandler(
|
||||
nil,
|
||||
@ -187,23 +179,14 @@ import OSLog
|
||||
}
|
||||
|
||||
guard let ncAccount else {
|
||||
Logger.fileProviderExtension.error(
|
||||
"""
|
||||
Not fetching contents for item: \(itemIdentifier.rawValue, privacy: .public)
|
||||
as account not set up yet.
|
||||
"""
|
||||
)
|
||||
logger.error("Not fetching contents for item because account not set up yet.", [.item: itemIdentifier])
|
||||
insertErrorAction(actionId)
|
||||
completionHandler(nil, nil, NSFileProviderError(.notAuthenticated))
|
||||
return Progress()
|
||||
}
|
||||
|
||||
guard let dbManager else {
|
||||
Logger.fileProviderExtension.error(
|
||||
"""
|
||||
Not fetching contents for item: \(itemIdentifier.rawValue, privacy: .public)
|
||||
as database is unreachable
|
||||
"""
|
||||
)
|
||||
logger.error("Not fetching contents for item because database is unavailable.", [.item: itemIdentifier])
|
||||
completionHandler(nil, nil, NSFileProviderError(.cannotSynchronize))
|
||||
return Progress()
|
||||
}
|
||||
@ -215,14 +198,11 @@ import OSLog
|
||||
identifier: itemIdentifier,
|
||||
account: ncAccount,
|
||||
remoteInterface: ncKit,
|
||||
dbManager: dbManager
|
||||
dbManager: dbManager,
|
||||
log: log
|
||||
) else {
|
||||
Logger.fileProviderExtension.error(
|
||||
"""
|
||||
Not fetching contents for item: \(itemIdentifier.rawValue, privacy: .public)
|
||||
as item not found.
|
||||
"""
|
||||
)
|
||||
logger.error("Not fetching contents for item because item was not found.", [.item: itemIdentifier])
|
||||
|
||||
completionHandler(
|
||||
nil,
|
||||
nil,
|
||||
@ -255,17 +235,17 @@ import OSLog
|
||||
insertSyncAction(actionId)
|
||||
|
||||
let tempId = itemTemplate.itemIdentifier.rawValue
|
||||
Logger.fileProviderExtension.debug(
|
||||
logger.debug(
|
||||
"""
|
||||
Received create item request for item with identifier: \(tempId, privacy: .public)
|
||||
and filename: \(itemTemplate.filename, privacy: .public)
|
||||
Received create item request for item with identifier: \(tempId)
|
||||
and filename: \(itemTemplate.filename)
|
||||
"""
|
||||
)
|
||||
|
||||
guard let ncAccount else {
|
||||
Logger.fileProviderExtension.error(
|
||||
logger.error(
|
||||
"""
|
||||
Not creating item: \(itemTemplate.itemIdentifier.rawValue, privacy: .public)
|
||||
Not creating item: \(itemTemplate.itemIdentifier.rawValue)
|
||||
as account not set up yet
|
||||
"""
|
||||
)
|
||||
@ -275,25 +255,14 @@ import OSLog
|
||||
}
|
||||
|
||||
guard let ignoredFiles else {
|
||||
Logger.fileProviderExtension.error(
|
||||
"""
|
||||
Not creating item for identifier:
|
||||
\(itemTemplate.itemIdentifier.rawValue, privacy: .public)
|
||||
as ignore list not set up yet.
|
||||
"""
|
||||
)
|
||||
logger.error("Not creating item for identifier: \(itemTemplate.itemIdentifier.rawValue) as ignore list not set up yet.")
|
||||
insertErrorAction(actionId)
|
||||
completionHandler(itemTemplate, [], false, NSFileProviderError(.notAuthenticated))
|
||||
return Progress()
|
||||
}
|
||||
|
||||
guard let dbManager else {
|
||||
Logger.fileProviderExtension.error(
|
||||
"""
|
||||
Not creating item: \(itemTemplate.itemIdentifier.rawValue, privacy: .public)
|
||||
as database is unreachable
|
||||
"""
|
||||
)
|
||||
logger.error("Not creating item because database is unavailable.", [.item: itemTemplate.itemIdentifier])
|
||||
insertErrorAction(actionId)
|
||||
completionHandler(itemTemplate, [], false, NSFileProviderError(.cannotSynchronize))
|
||||
return Progress()
|
||||
@ -311,7 +280,8 @@ import OSLog
|
||||
remoteInterface: ncKit,
|
||||
ignoredFiles: ignoredFiles,
|
||||
progress: progress,
|
||||
dbManager: dbManager
|
||||
dbManager: dbManager,
|
||||
log: log
|
||||
)
|
||||
|
||||
if error != nil {
|
||||
@ -349,26 +319,17 @@ import OSLog
|
||||
|
||||
let identifier = item.itemIdentifier
|
||||
let ocId = identifier.rawValue
|
||||
Logger.fileProviderExtension.debug(
|
||||
"""
|
||||
Received modify item request for item with identifier: \(ocId, privacy: .public)
|
||||
and filename: \(item.filename, privacy: .public)
|
||||
"""
|
||||
)
|
||||
logger.debug("Received modify item request for item with identifier: \(ocId) and filename: \(item.filename)")
|
||||
|
||||
guard let ncAccount else {
|
||||
Logger.fileProviderExtension.error(
|
||||
"Not modifying item: \(ocId, privacy: .public) as account not set up yet."
|
||||
)
|
||||
logger.error("Not modifying item: \(ocId) as account not set up yet.")
|
||||
insertErrorAction(actionId)
|
||||
completionHandler(item, [], false, NSFileProviderError(.notAuthenticated))
|
||||
return Progress()
|
||||
}
|
||||
|
||||
guard let ignoredFiles else {
|
||||
Logger.fileProviderExtension.error(
|
||||
"Not modifying item: \(ocId, privacy: .public) as ignore list not set up yet."
|
||||
)
|
||||
logger.error("Not modifying item: \(ocId) as ignore list not set up yet.")
|
||||
insertErrorAction(actionId)
|
||||
completionHandler(item, [], false, NSFileProviderError(.notAuthenticated))
|
||||
return Progress()
|
||||
@ -376,13 +337,7 @@ import OSLog
|
||||
|
||||
|
||||
guard let dbManager else {
|
||||
Logger.fileProviderExtension.error(
|
||||
"""
|
||||
Not modifying item: \(ocId, privacy: .public)
|
||||
with filename: \(item.filename, privacy: .public)
|
||||
as database is unreachable
|
||||
"""
|
||||
)
|
||||
logger.error("Not modifying item because database is unavailable.")
|
||||
insertErrorAction(actionId)
|
||||
completionHandler(item, [], false, NSFileProviderError(.cannotSynchronize))
|
||||
return Progress()
|
||||
@ -394,11 +349,10 @@ import OSLog
|
||||
identifier: identifier,
|
||||
account: ncAccount,
|
||||
remoteInterface: ncKit,
|
||||
dbManager: dbManager
|
||||
dbManager: dbManager,
|
||||
log: log
|
||||
) else {
|
||||
Logger.fileProviderExtension.error(
|
||||
"Not modifying item: \(ocId, privacy: .public) as item not found."
|
||||
)
|
||||
logger.error("Not modifying item: \(ocId) as item not found.")
|
||||
insertErrorAction(actionId)
|
||||
completionHandler(
|
||||
item,
|
||||
@ -443,32 +397,24 @@ import OSLog
|
||||
let actionId = UUID()
|
||||
insertSyncAction(actionId)
|
||||
|
||||
Logger.fileProviderExtension.debug(
|
||||
"Received delete request for item: \(identifier.rawValue, privacy: .public)"
|
||||
)
|
||||
logger.debug("Received delete request for item: \(identifier.rawValue)")
|
||||
|
||||
guard let ncAccount else {
|
||||
Logger.fileProviderExtension.error(
|
||||
"Not deleting item \(identifier.rawValue, privacy: .public), account not set up yet"
|
||||
)
|
||||
logger.error("Not deleting item \(identifier.rawValue), account not set up yet")
|
||||
insertErrorAction(actionId)
|
||||
completionHandler(NSFileProviderError(.notAuthenticated))
|
||||
return Progress()
|
||||
}
|
||||
|
||||
guard let ignoredFiles else {
|
||||
Logger.fileProviderExtension.error(
|
||||
"Not deleting \(identifier.rawValue, privacy: .public), ignore list not received"
|
||||
)
|
||||
logger.error("Not deleting \(identifier.rawValue), ignore list not received")
|
||||
insertErrorAction(actionId)
|
||||
completionHandler(NSFileProviderError(.notAuthenticated))
|
||||
return Progress()
|
||||
}
|
||||
|
||||
guard let dbManager else {
|
||||
Logger.fileProviderExtension.error(
|
||||
"Not deleting item \(identifier.rawValue, privacy: .public), db manager unavailable"
|
||||
)
|
||||
logger.error("Not deleting item \(identifier.rawValue), db manager unavailable")
|
||||
insertErrorAction(actionId)
|
||||
completionHandler(NSFileProviderError(.cannotSynchronize))
|
||||
return Progress()
|
||||
@ -480,11 +426,10 @@ import OSLog
|
||||
identifier: identifier,
|
||||
account: ncAccount,
|
||||
remoteInterface: ncKit,
|
||||
dbManager: dbManager
|
||||
dbManager: dbManager,
|
||||
log: log
|
||||
) else {
|
||||
Logger.fileProviderExtension.error(
|
||||
"Not deleting item \(identifier.rawValue, privacy: .public), item not found"
|
||||
)
|
||||
logger.error("Not deleting item \(identifier.rawValue), item not found")
|
||||
insertErrorAction(actionId)
|
||||
completionHandler(
|
||||
NSError.fileProviderErrorForNonExistentItem(withIdentifier: identifier)
|
||||
@ -493,12 +438,7 @@ import OSLog
|
||||
}
|
||||
|
||||
guard config.trashDeletionEnabled || item.parentItemIdentifier != .trashContainer else {
|
||||
Logger.fileProviderExtension.warning(
|
||||
"""
|
||||
System requested deletion of item in trash, but deleting trash items is disabled.
|
||||
item: \(item.filename, privacy: .public)
|
||||
"""
|
||||
)
|
||||
logger.info("System requested deletion of item in trash, but deleting trash items is disabled. item: \(item.filename)")
|
||||
removeSyncAction(actionId)
|
||||
completionHandler(NSError.fileProviderErrorForRejectedDeletion(of: item))
|
||||
return
|
||||
@ -522,24 +462,12 @@ import OSLog
|
||||
for containerItemIdentifier: NSFileProviderItemIdentifier, request _: NSFileProviderRequest
|
||||
) throws -> NSFileProviderEnumerator {
|
||||
guard let ncAccount else {
|
||||
Logger.fileProviderExtension.error(
|
||||
"""
|
||||
Not providing enumerator for container
|
||||
with identifier \(containerItemIdentifier.rawValue, privacy: .public) yet
|
||||
as account not set up
|
||||
"""
|
||||
)
|
||||
logger.error("Not providing enumerator for container with identifier \(containerItemIdentifier.rawValue) yet as account not set up")
|
||||
throw NSFileProviderError(.notAuthenticated)
|
||||
}
|
||||
|
||||
guard let dbManager else {
|
||||
Logger.fileProviderExtension.error(
|
||||
"""
|
||||
Not providing enumerator for container
|
||||
with identifier \(containerItemIdentifier.rawValue, privacy: .public) yet
|
||||
as db manager is unavailable
|
||||
"""
|
||||
)
|
||||
logger.error("Not providing enumerator for container with identifier \(containerItemIdentifier.rawValue) yet as db manager is unavailable")
|
||||
throw NSFileProviderError(.cannotSynchronize)
|
||||
}
|
||||
|
||||
@ -548,41 +476,32 @@ import OSLog
|
||||
account: ncAccount,
|
||||
remoteInterface: ncKit,
|
||||
dbManager: dbManager,
|
||||
domain: domain
|
||||
domain: domain,
|
||||
log: log
|
||||
)
|
||||
}
|
||||
|
||||
func materializedItemsDidChange(completionHandler: @escaping () -> Void) {
|
||||
guard let ncAccount else {
|
||||
Logger.fileProviderExtension.error(
|
||||
"Not purging stale local file metadatas, account not set up")
|
||||
logger.error("Not purging stale local file metadatas, account not set up")
|
||||
completionHandler()
|
||||
return
|
||||
}
|
||||
|
||||
guard let dbManager else {
|
||||
Logger.fileProviderExtension.error(
|
||||
"""
|
||||
Not purging stale local file metadatas.
|
||||
db manager unabilable for domain: \(self.domain.displayName, privacy: .public)
|
||||
"""
|
||||
)
|
||||
logger.error("Not purging stale local file metadatas. db manager unabilable for domain: \(self.domain.displayName)")
|
||||
completionHandler()
|
||||
return
|
||||
}
|
||||
|
||||
guard let fpManager = NSFileProviderManager(for: domain) else {
|
||||
Logger.fileProviderExtension.error(
|
||||
"Could not get file provider manager for domain: \(self.domain.displayName, privacy: .public)"
|
||||
)
|
||||
logger.error("Could not get file provider manager for domain: \(self.domain.displayName)")
|
||||
completionHandler()
|
||||
return
|
||||
}
|
||||
|
||||
let materialisedEnumerator = fpManager.enumeratorForMaterializedItems()
|
||||
let materialisedObserver = MaterialisedEnumerationObserver(
|
||||
ncKitAccount: ncAccount.ncKitAccount, dbManager: dbManager
|
||||
) { _, _ in
|
||||
let materialisedObserver = MaterialisedEnumerationObserver(ncKitAccount: ncAccount.ncKitAccount, dbManager: dbManager, log: log) { _, _ in
|
||||
completionHandler()
|
||||
}
|
||||
let startingPage = NSFileProviderPage(NSFileProviderPage.initialPageSortedByName as Data)
|
||||
@ -594,9 +513,7 @@ import OSLog
|
||||
|
||||
func signalEnumerator(completionHandler: @escaping (_ error: Error?) -> Void) {
|
||||
guard let fpManager = NSFileProviderManager(for: domain) else {
|
||||
Logger.fileProviderExtension.error(
|
||||
"Could not get file provider manager for domain, could not signal enumerator. This might lead to future conflicts."
|
||||
)
|
||||
logger.error("Could not get file provider manager for domain, could not signal enumerator. This might lead to future conflicts.")
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@ -10,35 +10,35 @@ import OSLog
|
||||
|
||||
class FileProviderSocketLineProcessor: NSObject, LineProcessor {
|
||||
var delegate: FileProviderExtension
|
||||
let logger: FileProviderLogger
|
||||
|
||||
required init(delegate: FileProviderExtension) {
|
||||
required init(delegate: FileProviderExtension, log: any FileProviderLogging) {
|
||||
self.delegate = delegate
|
||||
self.logger = FileProviderLogger(category: "FileProviderSocketLineProcessor", log: log)
|
||||
}
|
||||
|
||||
func process(_ line: String) {
|
||||
if line.contains("~") { // We use this as the separator specifically in ACCOUNT_DETAILS
|
||||
Logger.desktopClientConnection.debug(
|
||||
"Processing file provider line with potentially sensitive user data")
|
||||
logger.debug("Processing file provider line with potentially sensitive user data")
|
||||
} else {
|
||||
Logger.desktopClientConnection.debug(
|
||||
"Processing file provider line: \(line, privacy: .public)")
|
||||
logger.debug("Processing file provider line: \(line)")
|
||||
}
|
||||
|
||||
let splitLine = line.split(separator: ":", maxSplits: 1)
|
||||
guard let commandSubsequence = splitLine.first else {
|
||||
Logger.desktopClientConnection.error("Input line did not have a first element")
|
||||
logger.error("Input line did not have a first element")
|
||||
return
|
||||
}
|
||||
let command = String(commandSubsequence)
|
||||
|
||||
Logger.desktopClientConnection.debug("Received command: \(command, privacy: .public)")
|
||||
logger.debug("Received command: \(command)")
|
||||
if command == "SEND_FILE_PROVIDER_DOMAIN_IDENTIFIER" {
|
||||
delegate.sendFileProviderDomainIdentifier()
|
||||
} else if command == "ACCOUNT_NOT_AUTHENTICATED" {
|
||||
delegate.removeAccountConfig()
|
||||
} else if command == "ACCOUNT_DETAILS" {
|
||||
guard let accountDetailsSubsequence = splitLine.last else {
|
||||
Logger.desktopClientConnection.error("Account details did not have a first element")
|
||||
logger.error("Account details did not have a first element")
|
||||
return
|
||||
}
|
||||
let splitAccountDetails = accountDetailsSubsequence.split(separator: "~", maxSplits: 4)
|
||||
@ -58,13 +58,11 @@ class FileProviderSocketLineProcessor: NSObject, LineProcessor {
|
||||
)
|
||||
} else if command == "IGNORE_LIST" {
|
||||
guard let ignoreListSubsequence = splitLine.last else {
|
||||
Logger.desktopClientConnection.error("Ignore list missing contents!")
|
||||
logger.error("Ignore list missing contents!")
|
||||
return
|
||||
}
|
||||
let ignoreList = ignoreListSubsequence.components(separatedBy: "_~IL$~_")
|
||||
Logger.desktopClientConnection.debug(
|
||||
"Applying \(ignoreList.count, privacy: .public) ignore file patterns"
|
||||
)
|
||||
logger.debug("Applying \(ignoreList.count) ignore file patterns")
|
||||
delegate.ignoredFiles = IgnoredFilesMatcher(ignoreList: ignoreList)
|
||||
}
|
||||
}
|
||||
|
||||
@ -2,19 +2,26 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
import Foundation
|
||||
import NextcloudFileProviderKit
|
||||
import os
|
||||
|
||||
///
|
||||
/// macOS keychain abstraction to fetch account passwords.
|
||||
///
|
||||
struct Keychain {
|
||||
let logger: FileProviderLogger
|
||||
|
||||
init(log: any FileProviderLogging) {
|
||||
self.logger = FileProviderLogger(category: "Keychain", log: log)
|
||||
}
|
||||
|
||||
///
|
||||
/// Lookup a generic password for the given account on the given server.
|
||||
///
|
||||
/// - Returns: `nil` in case of any error or the password not being found.
|
||||
///
|
||||
static func getPassword(for account: String, on server: String) -> String? {
|
||||
Logger.keychain.debug("Looking for password of \"\(account)\" on \"\(server)\" in keychain...")
|
||||
func getPassword(for account: String, on server: String) -> String? {
|
||||
logger.debug("Looking for password of \"\(account)\" on \"\(server)\" in keychain...")
|
||||
|
||||
let query: [String: Any] = [
|
||||
kSecClass as String: kSecClassGenericPassword,
|
||||
@ -29,32 +36,32 @@ struct Keychain {
|
||||
let status = SecItemCopyMatching(query as CFDictionary, &item)
|
||||
|
||||
guard status != errSecItemNotFound else {
|
||||
Logger.keychain.error("Item not found!")
|
||||
logger.error("Item not found!")
|
||||
return nil
|
||||
}
|
||||
|
||||
guard status == errSecSuccess else {
|
||||
Logger.keychain.error("Keychain status: \(status)")
|
||||
logger.error("Keychain status: \(status)")
|
||||
return nil
|
||||
}
|
||||
|
||||
guard let existingItem = item as? [String : Any], let passwordData = existingItem[kSecValueData as String] as? Data, let password = String(data: passwordData, encoding: String.Encoding.utf8) else {
|
||||
Logger.keychain.error("Unexpected password data!")
|
||||
logger.error("Unexpected password data!")
|
||||
return nil
|
||||
}
|
||||
|
||||
Logger.keychain.debug("Found \(password.isEmpty ? "empty" : "non-empty") password for \"\(account)\" on \"\(server)\" in keychain.")
|
||||
logger.debug("Found \(password.isEmpty ? "empty" : "non-empty") password for \"\(account)\" on \"\(server)\" in keychain.")
|
||||
|
||||
return password
|
||||
}
|
||||
|
||||
static func savePassword(_ password: String, for account: String, on server: String) {
|
||||
func savePassword(_ password: String, for account: String, on server: String) {
|
||||
guard password.isEmpty == false else {
|
||||
Logger.keychain.error("Not saving password password for \"\(account)\" on \"\(server)\" because it is empty!")
|
||||
logger.error("Not saving password password for \"\(account)\" on \"\(server)\" because it is empty!")
|
||||
return
|
||||
}
|
||||
|
||||
Logger.keychain.debug("Saving password for \"\(account)\" on \"\(server)\"...")
|
||||
logger.debug("Saving password for \"\(account)\" on \"\(server)\"...")
|
||||
|
||||
let query: [String: Any] = [
|
||||
kSecClass as String: kSecClassGenericPassword,
|
||||
@ -74,9 +81,9 @@ struct Keychain {
|
||||
let updateStatus = SecItemUpdate(query as CFDictionary, updateAttributes as CFDictionary)
|
||||
|
||||
if updateStatus == errSecSuccess {
|
||||
Logger.keychain.debug("Succeeded to update password for \"\(account)\" on \"\(server)\" in keychain.")
|
||||
logger.debug("Succeeded to update password for \"\(account)\" on \"\(server)\" in keychain.")
|
||||
} else {
|
||||
Logger.keychain.error("Failed to update password for \"\(account)\" on \"\(server)\" in keychain. Status: \(updateStatus)")
|
||||
logger.error("Failed to update password for \"\(account)\" on \"\(server)\" in keychain. Status: \(updateStatus)")
|
||||
}
|
||||
} else if status == errSecItemNotFound {
|
||||
// Item doesn't exist, add a new one
|
||||
@ -90,12 +97,12 @@ struct Keychain {
|
||||
let addStatus = SecItemAdd(addQuery as CFDictionary, nil)
|
||||
|
||||
if addStatus == errSecSuccess {
|
||||
Logger.keychain.debug("Succeeded to add password for \"\(account)\" on \"\(server)\" in keychain.")
|
||||
logger.debug("Succeeded to add password for \"\(account)\" on \"\(server)\" in keychain.")
|
||||
} else {
|
||||
Logger.keychain.error("Failed to add password for \"\(account)\" on \"\(server)\" in keychain. Status: \(addStatus)")
|
||||
logger.error("Failed to add password for \"\(account)\" on \"\(server)\" in keychain. Status: \(addStatus)")
|
||||
}
|
||||
} else {
|
||||
Logger.keychain.error("Failed to check for existing password for \"\(account)\" on \"\(server)\" in keychain. Status: \(status)")
|
||||
logger.error("Failed to check for existing password for \"\(account)\" on \"\(server)\" in keychain. Status: \(status)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -21,7 +21,6 @@
|
||||
password:(NSString *)password
|
||||
userAgent:(NSString *)userAgent;
|
||||
- (void)removeAccountConfig;
|
||||
- (void)createDebugLogStringWithCompletionHandler:(void(^)(NSString *debugLogString, NSError *error))completionHandler;
|
||||
- (void)getTrashDeletionEnabledStateWithCompletionHandler:(void(^)(BOOL enabled, BOOL set))completionHandler;
|
||||
- (void)setTrashDeletionEnabled:(BOOL)enabled;
|
||||
- (void)setIgnoreList:(NSArray<NSString *> *)ignoreList;
|
||||
|
||||
@ -10,11 +10,13 @@ import OSLog
|
||||
|
||||
class ClientCommunicationService: NSObject, NSFileProviderServiceSource, NSXPCListenerDelegate, ClientCommunicationProtocol {
|
||||
let listener = NSXPCListener.anonymous()
|
||||
let logger: FileProviderLogger
|
||||
let serviceName = NSFileProviderServiceName("com.nextcloud.desktopclient.ClientCommunicationService")
|
||||
let fpExtension: FileProviderExtension
|
||||
|
||||
init(fpExtension: FileProviderExtension) {
|
||||
Logger.desktopClientConnection.debug("Instantiating client communication service")
|
||||
self.logger = FileProviderLogger(category: "ClientCommunicationService", log: fpExtension.log)
|
||||
logger.debug("Instantiating client communication service")
|
||||
self.fpExtension = fpExtension
|
||||
super.init()
|
||||
}
|
||||
@ -36,7 +38,7 @@ class ClientCommunicationService: NSObject, NSFileProviderServiceSource, NSXPCLi
|
||||
|
||||
func getFileProviderDomainIdentifier(completionHandler: @escaping (String?, Error?) -> Void) {
|
||||
let identifier = self.fpExtension.domain.identifier.rawValue
|
||||
Logger.desktopClientConnection.info("Returning file provider domain identifier \(identifier, privacy: .public)")
|
||||
logger.info("Returning file provider domain identifier \(identifier)")
|
||||
completionHandler(identifier, nil)
|
||||
}
|
||||
|
||||
@ -47,7 +49,7 @@ class ClientCommunicationService: NSObject, NSFileProviderServiceSource, NSXPCLi
|
||||
password: String,
|
||||
userAgent: String
|
||||
) {
|
||||
Logger.desktopClientConnection.info("Received configure account information over client communication service")
|
||||
logger.info("Received configure account information over client communication service")
|
||||
self.fpExtension.setupDomainAccount(
|
||||
user: user,
|
||||
userId: userId,
|
||||
@ -61,23 +63,6 @@ class ClientCommunicationService: NSObject, NSFileProviderServiceSource, NSXPCLi
|
||||
self.fpExtension.removeAccountConfig()
|
||||
}
|
||||
|
||||
func createDebugLogString(completionHandler: ((String?, Error?) -> Void)!) {
|
||||
if #available(macOSApplicationExtension 12.0, *) {
|
||||
let (logs, error) = Logger.logEntries()
|
||||
guard error == nil else {
|
||||
Logger.logger.error("Cannot create debug archive, received error: \(error, privacy: .public)")
|
||||
completionHandler(nil, error)
|
||||
return
|
||||
}
|
||||
guard let logs = logs else {
|
||||
Logger.logger.error("Canot create debug archive with nil logs.")
|
||||
completionHandler(nil, nil)
|
||||
return
|
||||
}
|
||||
completionHandler(logs.joined(separator: "\n"), nil)
|
||||
}
|
||||
}
|
||||
|
||||
func getTrashDeletionEnabledState(completionHandler: @escaping (Bool, Bool) -> Void) {
|
||||
let enabled = fpExtension.config.trashDeletionEnabled
|
||||
let set = fpExtension.config.trashDeletionSet
|
||||
@ -86,13 +71,11 @@ class ClientCommunicationService: NSObject, NSFileProviderServiceSource, NSXPCLi
|
||||
|
||||
func setTrashDeletionEnabled(_ enabled: Bool) {
|
||||
fpExtension.config.trashDeletionEnabled = enabled
|
||||
Logger.fileProviderExtension.info(
|
||||
"Trash deletion setting changed to: \(enabled, privacy: .public)"
|
||||
)
|
||||
logger.info("Trash deletion setting changed to: \(enabled)")
|
||||
}
|
||||
|
||||
func setIgnoreList(_ ignoreList: [String]) {
|
||||
self.fpExtension.ignoredFiles = IgnoredFilesMatcher(ignoreList: ignoreList)
|
||||
Logger.fileProviderExtension.info("Ignore list updated.")
|
||||
logger.info("Ignore list updated.")
|
||||
}
|
||||
}
|
||||
|
||||
@ -13,12 +13,16 @@ import NextcloudFileProviderKit
|
||||
import OSLog
|
||||
|
||||
class FPUIExtensionServiceSource: NSObject, NSFileProviderServiceSource, NSXPCListenerDelegate, FPUIExtensionService {
|
||||
let keychain: Keychain
|
||||
let listener = NSXPCListener.anonymous()
|
||||
let logger: FileProviderLogger
|
||||
let serviceName = fpUiExtensionServiceName
|
||||
let fpExtension: FileProviderExtension
|
||||
|
||||
init(fpExtension: FileProviderExtension) {
|
||||
Logger.fpUiExtensionService.debug("Instantiating FPUIExtensionService service")
|
||||
keychain = Keychain(log: fpExtension.log)
|
||||
logger = FileProviderLogger(category: "FPUIExtensionServiceSource", log: fpExtension.log)
|
||||
logger.debug("Instantiating FPUIExtensionService service")
|
||||
self.fpExtension = fpExtension
|
||||
super.init()
|
||||
}
|
||||
@ -42,10 +46,10 @@ class FPUIExtensionServiceSource: NSObject, NSFileProviderServiceSource, NSXPCLi
|
||||
//MARK: - FPUIExtensionService protocol methods
|
||||
|
||||
func authenticate() async -> NSError? {
|
||||
Logger.fpUiExtensionService.info("Authenticating...")
|
||||
logger.info("Authenticating...")
|
||||
|
||||
guard let user = fpExtension.config.user, let userId = fpExtension.config.userId, let serverUrl = fpExtension.config.serverUrl, let password = Keychain.getPassword(for: user, on: serverUrl) else {
|
||||
Logger.fpUiExtensionService.error("Missing account information, cannot authenticate!")
|
||||
guard let user = fpExtension.config.user, let userId = fpExtension.config.userId, let serverUrl = fpExtension.config.serverUrl, let password = keychain.getPassword(for: user, on: serverUrl) else {
|
||||
logger.error("Missing account information, cannot authenticate!")
|
||||
return NSError(.missingAccountInformation)
|
||||
}
|
||||
|
||||
@ -61,7 +65,7 @@ class FPUIExtensionServiceSource: NSObject, NSFileProviderServiceSource, NSXPCLi
|
||||
return nil
|
||||
}
|
||||
|
||||
let nkSession = fpExtension.ncKit.getSession(account: account)
|
||||
let nkSession = fpExtension.ncKit.nkCommonInstance.nksessions.session(forAccount: account)
|
||||
return nkSession?.userAgent as NSString?
|
||||
}
|
||||
|
||||
@ -71,23 +75,23 @@ class FPUIExtensionServiceSource: NSObject, NSFileProviderServiceSource, NSXPCLi
|
||||
|
||||
func itemServerPath(identifier: NSFileProviderItemIdentifier) async -> NSString? {
|
||||
let rawIdentifier = identifier.rawValue
|
||||
Logger.shares.info("Fetching shares for item \(rawIdentifier, privacy: .public)")
|
||||
logger.info("Fetching shares for item \(rawIdentifier)")
|
||||
|
||||
guard let baseUrl = fpExtension.ncAccount?.davFilesUrl else {
|
||||
Logger.shares.error("Could not fetch shares as ncAccount on parent extension is nil")
|
||||
logger.error("Could not fetch shares as ncAccount on parent extension is nil")
|
||||
return nil
|
||||
}
|
||||
|
||||
guard let account = fpExtension.ncAccount?.ncKitAccount else {
|
||||
Logger.shares.error("Could not fetch ncKitAccount on parent extension")
|
||||
logger.error("Could not fetch ncKitAccount on parent extension")
|
||||
return nil
|
||||
}
|
||||
guard let dbManager = fpExtension.dbManager else {
|
||||
Logger.shares.error("Could not get db manager for \(account, privacy: .public)")
|
||||
logger.error("Could not get db manager for \(account)")
|
||||
return nil
|
||||
}
|
||||
guard let item = dbManager.itemMetadataFromFileProviderItemIdentifier(identifier) else {
|
||||
Logger.shares.error("No item \(rawIdentifier, privacy: .public) in db, no shares.")
|
||||
logger.error("No item \(rawIdentifier) in db, no shares.")
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@ -3,6 +3,7 @@
|
||||
|
||||
import AppKit
|
||||
import FileProviderUI
|
||||
import NextcloudFileProviderKit
|
||||
import os
|
||||
|
||||
///
|
||||
@ -10,6 +11,17 @@ import os
|
||||
///
|
||||
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!
|
||||
@ -18,6 +30,11 @@ class AuthenticationViewController: NSViewController {
|
||||
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")
|
||||
}
|
||||
@ -80,16 +97,16 @@ class AuthenticationViewController: NSViewController {
|
||||
let url = try await manager.getUserVisibleURL(for: .rootContainer)
|
||||
|
||||
let connection = try await serviceConnection(url: url, interruptionHandler: {
|
||||
Logger.authenticationViewController.error("Service connection interrupted")
|
||||
self.logger.error("Service connection interrupted")
|
||||
})
|
||||
|
||||
if let error = await connection.authenticate() {
|
||||
Logger.authenticationViewController.error("An error was returned from the authentication call: \(error.localizedDescription, privacy: .public)")
|
||||
logger.error("An error was returned from the authentication call: \(error.localizedDescription)")
|
||||
updateViewsWithError(error)
|
||||
return
|
||||
}
|
||||
|
||||
Logger.authenticationViewController.info("Apparently, the authentication was successful.")
|
||||
logger.info("Apparently, the authentication was successful.")
|
||||
extensionContext.completeRequest()
|
||||
}
|
||||
|
||||
|
||||
@ -1,12 +1,8 @@
|
||||
//
|
||||
// DocumentActionViewController.swift
|
||||
// FileProviderUIExt
|
||||
//
|
||||
// SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
//
|
||||
|
||||
import FileProviderUI
|
||||
import NextcloudFileProviderKit
|
||||
import OSLog
|
||||
|
||||
class DocumentActionViewController: FPUIActionExtensionViewController {
|
||||
@ -20,7 +16,32 @@ class DocumentActionViewController: FPUIActionExtensionViewController {
|
||||
)
|
||||
}
|
||||
|
||||
///
|
||||
/// To be passed down in the hierarchy to all subordinate code.
|
||||
///
|
||||
var log: (any FileProviderLogging)!
|
||||
|
||||
///
|
||||
/// To be used by this view controller only.
|
||||
///
|
||||
/// Child view controllers must set up their own for clarity.
|
||||
///
|
||||
var logger: FileProviderLogger!
|
||||
|
||||
// MARK: - Lifecycle
|
||||
|
||||
func setUpLogger() {
|
||||
if log == nil {
|
||||
log = FileProviderLog(fileProviderDomainIdentifier: domain.identifier)
|
||||
}
|
||||
|
||||
if logger == nil, let log {
|
||||
logger = FileProviderLogger(category: "DocumentActionViewController", log: log)
|
||||
}
|
||||
}
|
||||
|
||||
func prepare(childViewController: NSViewController) {
|
||||
setUpLogger()
|
||||
addChild(childViewController)
|
||||
view.addSubview(childViewController.view)
|
||||
|
||||
@ -32,31 +53,32 @@ class DocumentActionViewController: FPUIActionExtensionViewController {
|
||||
])
|
||||
}
|
||||
|
||||
override func prepare(
|
||||
forAction actionIdentifier: String, itemIdentifiers: [NSFileProviderItemIdentifier]
|
||||
) {
|
||||
Logger.actionViewController.info("Preparing action: \(actionIdentifier, privacy: .public)")
|
||||
override func prepare(forAction actionIdentifier: String, itemIdentifiers: [NSFileProviderItemIdentifier]) {
|
||||
setUpLogger()
|
||||
logger?.info("Preparing action: \(actionIdentifier)")
|
||||
|
||||
switch (actionIdentifier) {
|
||||
case "com.nextcloud.desktopclient.FileProviderUIExt.ShareAction":
|
||||
prepare(childViewController: ShareViewController(itemIdentifiers))
|
||||
case "com.nextcloud.desktopclient.FileProviderUIExt.LockFileAction":
|
||||
prepare(childViewController: LockViewController(itemIdentifiers, locking: true))
|
||||
case "com.nextcloud.desktopclient.FileProviderUIExt.UnlockFileAction":
|
||||
prepare(childViewController: LockViewController(itemIdentifiers, locking: false))
|
||||
case "com.nextcloud.desktopclient.FileProviderUIExt.EvictAction":
|
||||
evict(itemsWithIdentifiers: itemIdentifiers, inDomain: domain);
|
||||
extensionContext.completeRequest();
|
||||
default:
|
||||
return
|
||||
case "com.nextcloud.desktopclient.FileProviderUIExt.ShareAction":
|
||||
prepare(childViewController: ShareViewController(itemIdentifiers, log: log))
|
||||
case "com.nextcloud.desktopclient.FileProviderUIExt.LockFileAction":
|
||||
prepare(childViewController: LockViewController(itemIdentifiers, locking: true, log: log))
|
||||
case "com.nextcloud.desktopclient.FileProviderUIExt.UnlockFileAction":
|
||||
prepare(childViewController: LockViewController(itemIdentifiers, locking: false, log: log))
|
||||
case "com.nextcloud.desktopclient.FileProviderUIExt.EvictAction":
|
||||
evict(itemsWithIdentifiers: itemIdentifiers, inDomain: domain);
|
||||
extensionContext.completeRequest();
|
||||
default:
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
override func prepare(forError error: Error) {
|
||||
Logger.actionViewController.info("Preparing for error: \(error.localizedDescription, privacy: .public)")
|
||||
setUpLogger()
|
||||
logger?.info("Preparing for error.", [.error: error])
|
||||
|
||||
let storyboard = NSStoryboard(name: "Authentication", bundle: Bundle(for: type(of: self)))
|
||||
let viewController = storyboard.instantiateInitialController() as! NSViewController
|
||||
let viewController = storyboard.instantiateInitialController() as! AuthenticationViewController
|
||||
viewController.log = log
|
||||
|
||||
prepare(childViewController: viewController)
|
||||
}
|
||||
@ -64,4 +86,40 @@ class DocumentActionViewController: FPUIActionExtensionViewController {
|
||||
override public func loadView() {
|
||||
self.view = NSView()
|
||||
}
|
||||
|
||||
// MARK: - Eviction
|
||||
|
||||
///
|
||||
/// Use a file provider domain manager to evict all items identified by the given array.
|
||||
///
|
||||
func evict(itemsWithIdentifiers identifiers: [NSFileProviderItemIdentifier], inDomain domain: NSFileProviderDomain) async {
|
||||
logger?.debug("Starting eviction process…")
|
||||
|
||||
guard let manager = NSFileProviderManager(for: domain) else {
|
||||
logger?.error("Could not get file provider domain manager.", [.domain: domain.identifier])
|
||||
return;
|
||||
}
|
||||
do {
|
||||
for itemIdentifier in identifiers {
|
||||
logger?.error("Evicting item: \(itemIdentifier.rawValue)")
|
||||
try await manager.evictItem(identifier: itemIdentifier)
|
||||
}
|
||||
} catch let error {
|
||||
logger?.error("Error evicting item: \(error.localizedDescription)")
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
/// Synchronous wrapper of ``evict(itemsWithIdentifiers:inDomain:)-67w8c``.
|
||||
///
|
||||
func evict(itemsWithIdentifiers identifiers: [NSFileProviderItemIdentifier], inDomain domain: NSFileProviderDomain) {
|
||||
let semaphore = DispatchSemaphore(value: 0)
|
||||
|
||||
Task {
|
||||
await evict(itemsWithIdentifiers: identifiers, inDomain: domain)
|
||||
semaphore.signal()
|
||||
}
|
||||
|
||||
semaphore.wait()
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,48 +0,0 @@
|
||||
//
|
||||
// Eviction.swift
|
||||
// FileProviderUIExt
|
||||
//
|
||||
// SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
//
|
||||
|
||||
import FileProvider
|
||||
import Foundation
|
||||
import OSLog
|
||||
|
||||
func evict(
|
||||
itemsWithIdentifiers identifiers: [NSFileProviderItemIdentifier],
|
||||
inDomain domain: NSFileProviderDomain
|
||||
) async {
|
||||
Logger.eviction.debug("Starting eviction process…")
|
||||
guard let manager = NSFileProviderManager(for: domain) else {
|
||||
Logger.eviction.error(
|
||||
"Could not get manager for domain: \(domain.identifier.rawValue, privacy: .public)"
|
||||
)
|
||||
return;
|
||||
}
|
||||
do {
|
||||
for itemIdentifier in identifiers {
|
||||
Logger.eviction.error(
|
||||
"Evicting item: \(itemIdentifier.rawValue, privacy: .public)"
|
||||
)
|
||||
try await manager.evictItem(identifier: itemIdentifier)
|
||||
}
|
||||
} catch let error {
|
||||
Logger.eviction.error(
|
||||
"Error evicting item: \(error.localizedDescription, privacy: .public)"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func evict(
|
||||
itemsWithIdentifiers identifiers: [NSFileProviderItemIdentifier],
|
||||
inDomain domain: NSFileProviderDomain
|
||||
) {
|
||||
let semaphore = DispatchSemaphore(value: 0)
|
||||
Task {
|
||||
await evict(itemsWithIdentifiers: identifiers, inDomain: domain)
|
||||
semaphore.signal()
|
||||
}
|
||||
semaphore.wait()
|
||||
}
|
||||
@ -1,26 +0,0 @@
|
||||
//
|
||||
// Logger+Extensions.swift
|
||||
// FileProviderUIExt
|
||||
//
|
||||
// SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
//
|
||||
|
||||
import OSLog
|
||||
|
||||
extension Logger {
|
||||
private static var subsystem = Bundle.main.bundleIdentifier!
|
||||
|
||||
static let actionViewController = Logger(subsystem: subsystem, category: "actionViewController")
|
||||
static let authenticationViewController = Logger(subsystem: subsystem, category: "authenticationViewController")
|
||||
static let eviction = Logger(subsystem: subsystem, category: "eviction")
|
||||
static let lockViewController = Logger(subsystem: subsystem, category: "lockViewController")
|
||||
static let metadataProvider = Logger(subsystem: subsystem, category: "metadataProvider")
|
||||
static let shareCapabilities = Logger(subsystem: subsystem, category: "shareCapabilities")
|
||||
static let shareController = Logger(subsystem: subsystem, category: "shareController")
|
||||
static let shareeDataSource = Logger(subsystem: subsystem, category: "shareeDataSource")
|
||||
static let sharesDataSource = Logger(subsystem: subsystem, category: "sharesDataSource")
|
||||
static let shareOptionsView = Logger(subsystem: subsystem, category: "shareOptionsView")
|
||||
static let shareViewController = Logger(subsystem: subsystem, category: "shareViewController")
|
||||
}
|
||||
|
||||
@ -17,6 +17,8 @@ import QuickLookThumbnailing
|
||||
class LockViewController: NSViewController {
|
||||
let itemIdentifiers: [NSFileProviderItemIdentifier]
|
||||
let locking: Bool
|
||||
let log: any FileProviderLogging
|
||||
let logger: FileProviderLogger
|
||||
|
||||
@IBOutlet weak var fileNameIcon: NSImageView!
|
||||
@IBOutlet weak var fileNameLabel: NSTextField!
|
||||
@ -33,9 +35,11 @@ class LockViewController: NSViewController {
|
||||
return parent as? DocumentActionViewController
|
||||
}
|
||||
|
||||
init(_ itemIdentifiers: [NSFileProviderItemIdentifier], locking: Bool) {
|
||||
init(_ itemIdentifiers: [NSFileProviderItemIdentifier], locking: Bool, log: any FileProviderLogging) {
|
||||
self.itemIdentifiers = itemIdentifiers
|
||||
self.locking = locking
|
||||
self.log = log
|
||||
self.logger = FileProviderLogger(category: "LockViewController", log: log)
|
||||
super.init(nibName: nil, bundle: nil)
|
||||
}
|
||||
|
||||
@ -45,17 +49,12 @@ class LockViewController: NSViewController {
|
||||
|
||||
override func viewDidLoad() {
|
||||
guard let firstItem = itemIdentifiers.first else {
|
||||
Logger.shareViewController.error("called without items")
|
||||
logger.error("called without items")
|
||||
closeAction(self)
|
||||
return
|
||||
}
|
||||
|
||||
Logger.lockViewController.info(
|
||||
"""
|
||||
Locking \(self.locking ? "enabled" : "disabled", privacy: .public) for items:
|
||||
\(firstItem.rawValue, privacy: .public)
|
||||
"""
|
||||
)
|
||||
logger.info("Locking \(self.locking ? "enabled" : "disabled") for items: \(firstItem.rawValue)")
|
||||
|
||||
Task {
|
||||
await processItemIdentifier(firstItem)
|
||||
@ -75,20 +74,21 @@ class LockViewController: NSViewController {
|
||||
}
|
||||
|
||||
private func presentError(_ error: String) {
|
||||
Logger.lockViewController.error("Error: \(error, privacy: .public)")
|
||||
logger.error("Error: \(error)")
|
||||
descriptionLabel.stringValue = "Error: \(error)"
|
||||
stopIndicatingLoading()
|
||||
}
|
||||
|
||||
private func fetchCapabilities(account: Account, kit: NextcloudKit) async -> Capabilities? {
|
||||
return await withCheckedContinuation { continuation in
|
||||
kit.getCapabilities(account: account.ncKitAccount) { account, data, error in
|
||||
kit.getCapabilities(account: account.ncKitAccount) { account, _, data, error in
|
||||
guard error == .success, let capabilitiesJson = data?.data else {
|
||||
self.presentError("Error getting server caps: \(error.errorDescription)")
|
||||
continuation.resume(returning: nil)
|
||||
return
|
||||
}
|
||||
Logger.lockViewController.info("Successfully retrieved server share capabilities")
|
||||
|
||||
self.logger.info("Successfully retrieved server share capabilities")
|
||||
continuation.resume(returning: Capabilities(data: capabilitiesJson))
|
||||
}
|
||||
}
|
||||
@ -102,7 +102,7 @@ class LockViewController: NSViewController {
|
||||
do {
|
||||
let itemUrl = try await manager.getUserVisibleURL(for: itemIdentifier)
|
||||
guard itemUrl.startAccessingSecurityScopedResource() else {
|
||||
Logger.lockViewController.error("Could not access scoped resource for item url!")
|
||||
logger.error("Could not access scoped resource for item url!")
|
||||
return
|
||||
}
|
||||
await updateFileDetailsDisplay(itemUrl: itemUrl)
|
||||
@ -110,7 +110,7 @@ class LockViewController: NSViewController {
|
||||
await lockOrUnlockFile(localItemUrl: itemUrl)
|
||||
} catch let error {
|
||||
let errorString = "Error processing item: \(error)"
|
||||
Logger.lockViewController.error("\(errorString, privacy: .public)")
|
||||
logger.error("\(errorString)")
|
||||
fileNameLabel.stringValue = String(localized: "Could not lock unknown item…")
|
||||
descriptionLabel.stringValue = error.localizedDescription
|
||||
}
|
||||
@ -129,9 +129,7 @@ class LockViewController: NSViewController {
|
||||
let fileThumbnail = await withCheckedContinuation { continuation in
|
||||
generator.generateRepresentations(for: request) { thumbnail, type, error in
|
||||
if thumbnail == nil || error != nil {
|
||||
Logger.lockViewController.error(
|
||||
"Could not get thumbnail: \(error, privacy: .public)"
|
||||
)
|
||||
self.logger.error("Could not get thumbnail.", [.error: error])
|
||||
}
|
||||
continuation.resume(returning: thumbnail)
|
||||
}
|
||||
@ -161,7 +159,7 @@ class LockViewController: NSViewController {
|
||||
|
||||
do {
|
||||
let connection = try await serviceConnection(url: localItemUrl, interruptionHandler: {
|
||||
Logger.lockViewController.error("Service connection interrupted")
|
||||
self.logger.error("Service connection interrupted")
|
||||
})
|
||||
guard let serverPath = await connection.itemServerPath(identifier: itemIdentifier),
|
||||
let credentials = await connection.credentials() as? Dictionary<String, String>,
|
||||
@ -180,7 +178,6 @@ class LockViewController: NSViewController {
|
||||
userId: account.id,
|
||||
password: account.password,
|
||||
userAgent: "Nextcloud-macOS/FileProviderUIExt",
|
||||
nextcloudVersion: 25,
|
||||
groupIdentifier: ""
|
||||
)
|
||||
guard let capabilities = await fetchCapabilities(account: account, kit: kit),
|
||||
@ -213,12 +210,7 @@ class LockViewController: NSViewController {
|
||||
|
||||
let serverUrlFileName = itemMetadata.serverUrl + "/" + itemMetadata.fileName
|
||||
|
||||
Logger.lockViewController.info(
|
||||
"""
|
||||
Locking file: \(serverUrlFileName, privacy: .public)
|
||||
\(self.locking ? "locking" : "unlocking", privacy: .public)
|
||||
"""
|
||||
)
|
||||
logger.info("Locking file: \(serverUrlFileName) \(self.locking ? "locking" : "unlocking")")
|
||||
|
||||
let error = await withCheckedContinuation { continuation in
|
||||
kit.lockUnlockFile(
|
||||
|
||||
@ -1,32 +1,27 @@
|
||||
//
|
||||
// MetadataProvider.swift
|
||||
// FileProviderUIExt
|
||||
//
|
||||
// SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import NextcloudFileProviderKit
|
||||
import NextcloudKit
|
||||
import OSLog
|
||||
|
||||
func fetchItemMetadata(
|
||||
itemRelativePath: String, account: Account, kit: NextcloudKit
|
||||
) async -> NKFile? {
|
||||
func fetchItemMetadata(itemRelativePath: String, account: Account, kit: NextcloudKit) async -> NKFile? {
|
||||
func slashlessPath(_ string: String) -> String {
|
||||
var strCopy = string
|
||||
|
||||
if strCopy.hasPrefix("/") {
|
||||
strCopy.removeFirst()
|
||||
}
|
||||
|
||||
if strCopy.hasSuffix("/") {
|
||||
strCopy.removeLast()
|
||||
}
|
||||
|
||||
return strCopy
|
||||
}
|
||||
|
||||
guard let nksession = kit.getSession(account: account.ncKitAccount) else {
|
||||
Logger.metadataProvider.error("Could not get nksession for \(account.ncKitAccount)")
|
||||
guard let nksession = kit.nkCommonInstance.nksessions.session(forAccount: account.ncKitAccount) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -34,21 +29,15 @@ func fetchItemMetadata(
|
||||
let davSuffix = slashlessPath(nksession.dav)
|
||||
let userId = nksession.userId
|
||||
let itemRelPath = slashlessPath(itemRelativePath)
|
||||
|
||||
let itemFullServerPath = "\(urlBase)/\(davSuffix)/files/\(userId)/\(itemRelPath)"
|
||||
|
||||
return await withCheckedContinuation { continuation in
|
||||
kit.readFileOrFolder(
|
||||
serverUrlFileName: itemFullServerPath, depth: "0", account: account.ncKitAccount
|
||||
) {
|
||||
account, files, data, error in
|
||||
kit.readFileOrFolder(serverUrlFileName: itemFullServerPath, depth: "0", account: account.ncKitAccount) { account, files, data, error in
|
||||
guard error == .success else {
|
||||
Logger.metadataProvider.error(
|
||||
"Error getting item metadata: \(error.errorDescription)"
|
||||
)
|
||||
continuation.resume(returning: nil)
|
||||
return
|
||||
}
|
||||
Logger.metadataProvider.info("Successfully retrieved item metadata")
|
||||
|
||||
continuation.resume(returning: files?.first)
|
||||
}
|
||||
}
|
||||
|
||||
@ -16,6 +16,8 @@ class ShareController: ObservableObject {
|
||||
@Published private(set) var share: NKShare
|
||||
private let kit: NextcloudKit
|
||||
private let account: Account
|
||||
let log: any FileProviderLogging
|
||||
let logger: FileProviderLogger
|
||||
|
||||
static func create(
|
||||
account: Account,
|
||||
@ -33,33 +35,24 @@ class ShareController: ObservableObject {
|
||||
attributes: String? = nil,
|
||||
options: NKRequestOptions = NKRequestOptions()
|
||||
) async -> NKError? {
|
||||
Logger.shareController.info("Creating share: \(itemServerRelativePath)")
|
||||
return await withCheckedContinuation { continuation in
|
||||
if shareType == .publicLink {
|
||||
kit.createShareLink(
|
||||
kit.createShare(
|
||||
path: itemServerRelativePath,
|
||||
hideDownload: hideDownload,
|
||||
shareType: ShareType.publicLink.rawValue,
|
||||
shareWith: nil,
|
||||
publicUpload: publicUpload,
|
||||
hideDownload: hideDownload,
|
||||
password: password,
|
||||
permissions: permissions,
|
||||
account: account.ncKitAccount,
|
||||
options: options
|
||||
) { account, share, data, error in
|
||||
defer { continuation.resume(returning: error) }
|
||||
guard error == .success else {
|
||||
Logger.shareController.error(
|
||||
"""
|
||||
Error creating link share: \(error.errorDescription, privacy: .public)
|
||||
"""
|
||||
)
|
||||
return
|
||||
}
|
||||
continuation.resume(returning: error)
|
||||
}
|
||||
} else {
|
||||
guard let shareWith = shareWith else {
|
||||
let errorString = "No recipient for share!"
|
||||
Logger.shareController.error("\(errorString, privacy: .public)")
|
||||
let error = NKError(statusCode: 0, fallbackDescription: errorString)
|
||||
let error = NKError(statusCode: 0, fallbackDescription: "No recipient for share!")
|
||||
continuation.resume(returning: error)
|
||||
return
|
||||
}
|
||||
@ -73,24 +66,18 @@ class ShareController: ObservableObject {
|
||||
attributes: attributes,
|
||||
account: account.ncKitAccount
|
||||
) { account, share, data, error in
|
||||
defer { continuation.resume(returning: error) }
|
||||
guard error == .success else {
|
||||
Logger.shareController.error(
|
||||
"""
|
||||
Error creating share: \(error.errorDescription, privacy: .public)
|
||||
"""
|
||||
)
|
||||
return
|
||||
}
|
||||
continuation.resume(returning: error)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
init(share: NKShare, account: Account, kit: NextcloudKit) {
|
||||
init(share: NKShare, account: Account, kit: NextcloudKit, log: any FileProviderLogging) {
|
||||
self.account = account
|
||||
self.share = share
|
||||
self.kit = kit
|
||||
self.log = log
|
||||
self.logger = FileProviderLogger(category: "ShareController", log: log)
|
||||
}
|
||||
|
||||
func save(
|
||||
@ -104,7 +91,8 @@ class ShareController: ObservableObject {
|
||||
attributes: String? = nil,
|
||||
options: NKRequestOptions = NKRequestOptions()
|
||||
) async -> NKError? {
|
||||
Logger.shareController.info("Saving share: \(self.share.url, privacy: .public)")
|
||||
logger.info("Saving share.", [.url: self.share.url])
|
||||
|
||||
return await withCheckedContinuation { continuation in
|
||||
kit.updateShare(
|
||||
idShare: share.idShare,
|
||||
@ -119,43 +107,36 @@ class ShareController: ObservableObject {
|
||||
account: account.ncKitAccount,
|
||||
options: options
|
||||
) { account, share, data, error in
|
||||
Logger.shareController.info(
|
||||
"""
|
||||
Received update response: \(share?.url ?? "", privacy: .public)
|
||||
"""
|
||||
)
|
||||
defer { continuation.resume(returning: error) }
|
||||
self.logger.info("Received update response: \(share?.url ?? "")")
|
||||
|
||||
defer {
|
||||
continuation.resume(returning: error)
|
||||
}
|
||||
|
||||
guard error == .success, let share = share else {
|
||||
Logger.shareController.error(
|
||||
"""
|
||||
Error updating save: \(error.errorDescription, privacy: .public)
|
||||
"""
|
||||
)
|
||||
self.logger.error("Error updating save.", [.error: error])
|
||||
return
|
||||
}
|
||||
|
||||
self.share = share
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func delete() async -> NKError? {
|
||||
Logger.shareController.info("Deleting share: \(self.share.url, privacy: .public)")
|
||||
logger.info("Deleting share: \(self.share.url)")
|
||||
|
||||
return await withCheckedContinuation { continuation in
|
||||
kit.deleteShare(
|
||||
idShare: share.idShare, account: account.ncKitAccount
|
||||
) { account, _, error in
|
||||
Logger.shareController.info(
|
||||
"""
|
||||
Received delete response: \(self.share.url, privacy: .public)
|
||||
"""
|
||||
)
|
||||
defer { continuation.resume(returning: error) }
|
||||
kit.deleteShare(idShare: share.idShare, account: account.ncKitAccount) { account, _, error in
|
||||
self.logger.info("Received delete response: \(self.share.url)")
|
||||
|
||||
defer {
|
||||
continuation.resume(returning: error)
|
||||
}
|
||||
|
||||
guard error == .success else {
|
||||
Logger.shareController.error(
|
||||
"""
|
||||
Error deleting save: \(error.errorDescription, privacy: .public)
|
||||
"""
|
||||
)
|
||||
self.logger.error("Error deleting save: \(error.errorDescription)")
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
@ -14,6 +14,8 @@ import OSLog
|
||||
import SuggestionsTextFieldKit
|
||||
|
||||
class ShareOptionsView: NSView {
|
||||
var logger: FileProviderLogger?
|
||||
|
||||
@IBOutlet private weak var optionsTitleTextField: NSTextField!
|
||||
@IBOutlet private weak var shareRecipientTextField: NSTextField! // Hide if public link share
|
||||
@IBOutlet private weak var labelTextField: NSTextField!
|
||||
@ -41,20 +43,28 @@ class ShareOptionsView: NSView {
|
||||
let kit = NextcloudKit.shared
|
||||
var account: Account? {
|
||||
didSet {
|
||||
Logger.shareOptionsView.info("Setting up account.")
|
||||
logger?.info("Setting up account.")
|
||||
|
||||
guard let account else {
|
||||
Logger.shareOptionsView.error("Could not configure suggestions data source.")
|
||||
logger?.error("Could not configure suggestions data source.")
|
||||
return
|
||||
}
|
||||
|
||||
suggestionsTextFieldDelegate.suggestionsDataSource = ShareeSuggestionsDataSource(
|
||||
account: account, kit: kit
|
||||
)
|
||||
suggestionsTextFieldDelegate.confirmationHandler = { suggestion in
|
||||
guard let sharee = suggestion?.data as? NKSharee else { return }
|
||||
self.shareRecipientTextField.stringValue = sharee.shareWith
|
||||
Logger.shareOptionsView.debug("Chose sharee \(sharee.shareWith, privacy: .public)")
|
||||
guard let controller else {
|
||||
return
|
||||
}
|
||||
|
||||
suggestionsTextFieldDelegate.suggestionsDataSource = ShareeSuggestionsDataSource(account: account, kit: kit, log: controller.log)
|
||||
|
||||
suggestionsTextFieldDelegate.confirmationHandler = { suggestion in
|
||||
guard let sharee = suggestion?.data as? NKSharee else {
|
||||
return
|
||||
}
|
||||
|
||||
self.shareRecipientTextField.stringValue = sharee.shareWith
|
||||
self.logger?.debug("Chose sharee \(sharee.shareWith)")
|
||||
}
|
||||
|
||||
suggestionsTextFieldDelegate.targetTextField = shareRecipientTextField
|
||||
}
|
||||
}
|
||||
@ -74,7 +84,7 @@ class ShareOptionsView: NSView {
|
||||
}
|
||||
var createMode = false {
|
||||
didSet {
|
||||
Logger.shareOptionsView.info("Create mode set: \(self.createMode, privacy: .public)")
|
||||
logger?.info("Create mode set: \(self.createMode)")
|
||||
shareTypePicker.isHidden = !createMode
|
||||
shareRecipientTextField.isHidden = !createMode
|
||||
labelTextField.isHidden = createMode // Cannot set label on create API call
|
||||
@ -97,7 +107,7 @@ class ShareOptionsView: NSView {
|
||||
private var suggestionsTextFieldDelegate = SuggestionsTextFieldDelegate()
|
||||
|
||||
private func update() {
|
||||
guard let share = controller?.share else {
|
||||
guard let controller else {
|
||||
reset()
|
||||
setAllFields(enabled: false)
|
||||
saveButton.isEnabled = false
|
||||
@ -105,6 +115,9 @@ class ShareOptionsView: NSView {
|
||||
return
|
||||
}
|
||||
|
||||
logger = FileProviderLogger(category: "ShareOptionsView", log: controller.log)
|
||||
let share = controller.share
|
||||
|
||||
// Programmatically update localizable texts.
|
||||
publicLinkShareMenuItem.title = String(localized: "Public link share")
|
||||
userShareMenuItem.title = String(localized: "User share")
|
||||
@ -278,18 +291,13 @@ class ShareOptionsView: NSView {
|
||||
let uploadAndEdit = uploadEditPermissionCheckbox.state == .on
|
||||
|
||||
guard !createMode else {
|
||||
Logger.shareOptionsView.info("Creating new share!")
|
||||
logger?.info("Creating new share!")
|
||||
|
||||
guard let dataSource,
|
||||
let account,
|
||||
let itemServerRelativePath = dataSource.itemServerRelativePath
|
||||
else {
|
||||
Logger.shareOptionsView.error("Cannot create new share due to missing data.")
|
||||
Logger.shareOptionsView.error("dataSource: \(self.dataSource, privacy: .public)")
|
||||
Logger.shareOptionsView.error("account: \(self.account != nil, privacy: .public)")
|
||||
Logger.shareOptionsView.error(
|
||||
"path: \(self.dataSource?.itemServerRelativePath ?? "", privacy: .public)"
|
||||
)
|
||||
logger?.error("Cannot create new share due to missing data. dataSource: \(String(describing: self.dataSource)) account: \(self.account != nil) path: \(self.dataSource?.itemServerRelativePath ?? "")")
|
||||
return
|
||||
}
|
||||
|
||||
@ -327,10 +335,10 @@ class ShareOptionsView: NSView {
|
||||
return
|
||||
}
|
||||
|
||||
Logger.shareOptionsView.info("Editing existing share!")
|
||||
logger?.info("Editing existing share!")
|
||||
|
||||
guard let controller = controller else {
|
||||
Logger.shareOptionsView.error("No valid share controller, cannot edit share.")
|
||||
logger?.error("No valid share controller, cannot edit share.")
|
||||
return
|
||||
}
|
||||
let share = controller.share
|
||||
@ -378,3 +386,4 @@ class ShareOptionsView: NSView {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -19,6 +19,7 @@ class ShareTableViewDataSource: NSObject, NSTableViewDataSource, NSTableViewDele
|
||||
private let reattemptInterval: TimeInterval = 3.0
|
||||
|
||||
let kit = NextcloudKit.shared
|
||||
let logger: FileProviderLogger
|
||||
|
||||
var uiDelegate: ShareViewDataSourceUIDelegate?
|
||||
var sharesTableView: NSTableView? {
|
||||
@ -48,12 +49,15 @@ class ShareTableViewDataSource: NSObject, NSTableViewDataSource, NSTableViewDele
|
||||
userId: account.username,
|
||||
password: account.password,
|
||||
userAgent: userAgent,
|
||||
nextcloudVersion: 25,
|
||||
groupIdentifier: ""
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
init(log: any FileProviderLogging) {
|
||||
self.logger = FileProviderLogger(category: "ShareTableViewDataSource", log: log)
|
||||
}
|
||||
|
||||
func loadItem(url: URL) {
|
||||
itemServerRelativePath = nil
|
||||
itemURL = url
|
||||
@ -93,7 +97,7 @@ class ShareTableViewDataSource: NSObject, NSTableViewDataSource, NSTableViewDele
|
||||
|
||||
do {
|
||||
let connection = try await serviceConnection(url: itemURL, interruptionHandler: {
|
||||
Logger.sharesDataSource.error("Service connection interrupted")
|
||||
self.logger.error("Service connection interrupted")
|
||||
})
|
||||
if let acquiredUserAgent = await connection.userAgent() {
|
||||
userAgent = acquiredUserAgent as String
|
||||
@ -148,7 +152,7 @@ class ShareTableViewDataSource: NSObject, NSTableViewDataSource, NSTableViewDele
|
||||
defer { Task { @MainActor in uiDelegate?.fetchFinished() } }
|
||||
|
||||
let rawIdentifier = itemIdentifier.rawValue
|
||||
Logger.sharesDataSource.info("Fetching shares for item \(rawIdentifier, privacy: .public)")
|
||||
logger.info("Fetching shares for item \(rawIdentifier)")
|
||||
|
||||
guard let account else {
|
||||
self.presentError(String(localized: "NextcloudKit instance or account is unavailable, cannot fetch shares!"))
|
||||
@ -162,7 +166,7 @@ class ShareTableViewDataSource: NSObject, NSTableViewDataSource, NSTableViewDele
|
||||
parameters: parameter, account: account.ncKitAccount
|
||||
) { account, shares, data, error in
|
||||
let shareCount = shares?.count ?? 0
|
||||
Logger.sharesDataSource.info("Received \(shareCount, privacy: .public) shares")
|
||||
self.logger.info("Received \(shareCount) shares")
|
||||
defer { continuation.resume(returning: shares ?? []) }
|
||||
guard error == .success else {
|
||||
self.presentError(String(localized: "Error fetching shares: \(error.errorDescription)"))
|
||||
@ -190,20 +194,21 @@ class ShareTableViewDataSource: NSObject, NSTableViewDataSource, NSTableViewDele
|
||||
}
|
||||
|
||||
return await withCheckedContinuation { continuation in
|
||||
kit.getCapabilities(account: account.ncKitAccount) { account, data, error in
|
||||
kit.getCapabilities(account: account.ncKitAccount) { account, _, data, error in
|
||||
guard error == .success, let capabilitiesJson = data?.data else {
|
||||
self.presentError(String(localized: "Error getting server caps: \(error.errorDescription)"))
|
||||
continuation.resume(returning: nil)
|
||||
return
|
||||
}
|
||||
Logger.sharesDataSource.info("Successfully retrieved server share capabilities")
|
||||
|
||||
self.logger.info("Successfully retrieved server share capabilities")
|
||||
continuation.resume(returning: Capabilities(data: capabilitiesJson))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func presentError(_ errorString: String) {
|
||||
Logger.sharesDataSource.error("\(errorString, privacy: .public)")
|
||||
logger.error("\(errorString)")
|
||||
Task { @MainActor in self.uiDelegate?.showError(errorString) }
|
||||
}
|
||||
|
||||
@ -222,7 +227,7 @@ class ShareTableViewDataSource: NSObject, NSTableViewDataSource, NSTableViewDele
|
||||
guard let view = tableView.makeView(
|
||||
withIdentifier: shareItemViewIdentifier, owner: self
|
||||
) as? ShareTableItemView else {
|
||||
Logger.sharesDataSource.error("Acquired item view from table is not a share item view!")
|
||||
logger.error("Acquired item view from table is not a share item view!")
|
||||
return nil
|
||||
}
|
||||
view.share = share
|
||||
|
||||
@ -1,20 +1,18 @@
|
||||
//
|
||||
// ShareViewController.swift
|
||||
// FileProviderUIExt
|
||||
//
|
||||
// SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
//
|
||||
|
||||
import AppKit
|
||||
import FileProvider
|
||||
import NextcloudFileProviderKit
|
||||
import NextcloudKit
|
||||
import OSLog
|
||||
import QuickLookThumbnailing
|
||||
|
||||
class ShareViewController: NSViewController, ShareViewDataSourceUIDelegate {
|
||||
let shareDataSource = ShareTableViewDataSource()
|
||||
let shareDataSource: ShareTableViewDataSource
|
||||
let itemIdentifiers: [NSFileProviderItemIdentifier]
|
||||
let log: any FileProviderLogging
|
||||
let logger: FileProviderLogger
|
||||
|
||||
@IBOutlet weak var fileNameIcon: NSImageView!
|
||||
@IBOutlet weak var fileNameLabel: NSTextField!
|
||||
@ -39,22 +37,20 @@ class ShareViewController: NSViewController, ShareViewDataSourceUIDelegate {
|
||||
return parent as? DocumentActionViewController
|
||||
}
|
||||
|
||||
init(_ itemIdentifiers: [NSFileProviderItemIdentifier]) {
|
||||
init(_ itemIdentifiers: [NSFileProviderItemIdentifier], log: any FileProviderLogging) {
|
||||
self.itemIdentifiers = itemIdentifiers
|
||||
self.log = log
|
||||
self.logger = FileProviderLogger(category: "ShareViewController", log: log)
|
||||
self.shareDataSource = ShareTableViewDataSource(log: log)
|
||||
super.init(nibName: nil, bundle: nil)
|
||||
|
||||
guard let firstItem = itemIdentifiers.first else {
|
||||
Logger.shareViewController.error("called without items")
|
||||
logger.error("called without items")
|
||||
closeAction(self)
|
||||
return
|
||||
}
|
||||
|
||||
Logger.shareViewController.info(
|
||||
"""
|
||||
Instantiated with itemIdentifiers:
|
||||
\(itemIdentifiers.map { $0.rawValue }, privacy: .public)
|
||||
"""
|
||||
)
|
||||
logger.info("Instantiated with itemIdentifiers: \(itemIdentifiers.map { $0.rawValue })")
|
||||
|
||||
Task {
|
||||
await processItemIdentifier(firstItem)
|
||||
@ -83,9 +79,10 @@ class ShareViewController: NSViewController, ShareViewDataSourceUIDelegate {
|
||||
do {
|
||||
let itemUrl = try await manager.getUserVisibleURL(for: itemIdentifier)
|
||||
guard itemUrl.startAccessingSecurityScopedResource() else {
|
||||
Logger.shareViewController.error("Could not access scoped resource for item url!")
|
||||
logger.error("Could not access scoped resource for item url!")
|
||||
return
|
||||
}
|
||||
|
||||
await updateDisplay(itemUrl: itemUrl)
|
||||
shareDataSource.uiDelegate = self
|
||||
shareDataSource.sharesTableView = tableView
|
||||
@ -94,7 +91,7 @@ class ShareViewController: NSViewController, ShareViewDataSourceUIDelegate {
|
||||
itemUrl.stopAccessingSecurityScopedResource()
|
||||
} catch let error {
|
||||
let errorString = "Error processing item: \(error)"
|
||||
Logger.shareViewController.error("\(errorString, privacy: .public)")
|
||||
logger.error("\(errorString)")
|
||||
fileNameLabel.stringValue = String(localized: "Unknown item")
|
||||
descriptionLabel.stringValue = errorString
|
||||
}
|
||||
@ -114,12 +111,9 @@ class ShareViewController: NSViewController, ShareViewDataSourceUIDelegate {
|
||||
let fileThumbnail = await withCheckedContinuation { continuation in
|
||||
generator.generateRepresentations(for: request) { thumbnail, type, error in
|
||||
if thumbnail == nil || error != nil {
|
||||
Logger.shareViewController.error(
|
||||
"""
|
||||
Could not get thumbnail: \(error, privacy: .public)
|
||||
"""
|
||||
)
|
||||
self.logger.error("Could not get thumbnail.", [.error: error])
|
||||
}
|
||||
|
||||
continuation.resume(returning: thumbnail)
|
||||
}
|
||||
}
|
||||
@ -183,9 +177,8 @@ class ShareViewController: NSViewController, ShareViewDataSourceUIDelegate {
|
||||
func showOptions(share: NKShare) {
|
||||
guard let account = shareDataSource.account, share.canEdit || share.canDelete else { return }
|
||||
optionsView.account = account
|
||||
optionsView.controller = ShareController(
|
||||
share: share, account: account, kit: shareDataSource.kit
|
||||
)
|
||||
optionsView.controller = ShareController(share: share, account: account, kit: shareDataSource.kit, log: log)
|
||||
|
||||
if !splitView.arrangedSubviews.contains(optionsView) {
|
||||
splitView.addArrangedSubview(optionsView)
|
||||
optionsView.isHidden = false
|
||||
|
||||
@ -14,26 +14,33 @@ import SuggestionsTextFieldKit
|
||||
|
||||
class ShareeSuggestionsDataSource: SuggestionsDataSource {
|
||||
let kit: NextcloudKit
|
||||
let logger: FileProviderLogger
|
||||
let account: Account
|
||||
var suggestions: [Suggestion] = []
|
||||
var inputString: String = "" {
|
||||
didSet { Task { await updateSuggestions() } }
|
||||
didSet {
|
||||
Task {
|
||||
await updateSuggestions()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
init(account: Account, kit: NextcloudKit) {
|
||||
init(account: Account, kit: NextcloudKit, log: any FileProviderLogging) {
|
||||
self.account = account
|
||||
self.kit = kit
|
||||
self.logger = FileProviderLogger(category: "ShareeSuggestionsDataSource", log: log)
|
||||
}
|
||||
|
||||
private func updateSuggestions() async {
|
||||
let sharees = await fetchSharees(search: inputString)
|
||||
Logger.shareeDataSource.info("Fetched \(sharees.count, privacy: .public) sharees.")
|
||||
logger.info("Fetched \(sharees.count) sharees.")
|
||||
suggestions = suggestionsFromSharees(sharees)
|
||||
NotificationCenter.default.post(name: SuggestionsChangedNotificationName, object: self)
|
||||
}
|
||||
|
||||
private func fetchSharees(search: String) async -> [NKSharee] {
|
||||
Logger.shareeDataSource.debug("Searching sharees with: \(search, privacy: .public)")
|
||||
logger.debug("Searching sharees with: \(search)")
|
||||
|
||||
return await withCheckedContinuation { continuation in
|
||||
kit.searchSharees(
|
||||
search: inputString,
|
||||
@ -41,11 +48,12 @@ class ShareeSuggestionsDataSource: SuggestionsDataSource {
|
||||
perPage: 20,
|
||||
account: account.ncKitAccount,
|
||||
completion: { account, sharees, data, error in
|
||||
defer { continuation.resume(returning: sharees ?? []) }
|
||||
defer {
|
||||
continuation.resume(returning: sharees ?? [])
|
||||
}
|
||||
|
||||
guard error == .success else {
|
||||
Logger.shareeDataSource.error(
|
||||
"Error fetching sharees: \(error.errorDescription, privacy: .public)"
|
||||
)
|
||||
self.logger.error("Error fetching sharees: \(error.errorDescription)")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
@ -8,19 +8,14 @@
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
530429982DD44235004BB598 /* FileProviderExtension+CustomActions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 530429972DD44226004BB598 /* FileProviderExtension+CustomActions.swift */; };
|
||||
5307A6E62965C6FA001E0C6A /* NextcloudKit in Frameworks */ = {isa = PBXBuildFile; productRef = 5307A6E52965C6FA001E0C6A /* NextcloudKit */; };
|
||||
5307A6E82965DAD8001E0C6A /* NextcloudKit in Frameworks */ = {isa = PBXBuildFile; productRef = 5307A6E72965DAD8001E0C6A /* NextcloudKit */; };
|
||||
531522822B8E01C6002E31BE /* ShareTableItemView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 531522812B8E01C6002E31BE /* ShareTableItemView.xib */; };
|
||||
531EDE572D897B4F00FD91F9 /* Eviction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 531EDE562D897B4F00FD91F9 /* Eviction.swift */; };
|
||||
5350E4E92B0C534A00F276CB /* ClientCommunicationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5350E4E82B0C534A00F276CB /* ClientCommunicationService.swift */; };
|
||||
5352B36C29DC44B50011CE03 /* FileProviderExtension+Thumbnailing.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5352B36B29DC44B50011CE03 /* FileProviderExtension+Thumbnailing.swift */; };
|
||||
5358F2B92BAA0F5300E3C729 /* NextcloudCapabilitiesKit in Frameworks */ = {isa = PBXBuildFile; productRef = 5358F2B82BAA0F5300E3C729 /* NextcloudCapabilitiesKit */; };
|
||||
535AE30E29C0A2CC0042A9BA /* Logger+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 535AE30D29C0A2CC0042A9BA /* Logger+Extensions.swift */; };
|
||||
53651E442BBC0CA300ECAC29 /* SuggestionsTextFieldKit in Frameworks */ = {isa = PBXBuildFile; productRef = 53651E432BBC0CA300ECAC29 /* SuggestionsTextFieldKit */; };
|
||||
53651E462BBC0D9500ECAC29 /* ShareeSuggestionsDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53651E452BBC0D9500ECAC29 /* ShareeSuggestionsDataSource.swift */; };
|
||||
536EFBF7295CF58100F4CB13 /* FileProviderSocketLineProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 536EFBF6295CF58100F4CB13 /* FileProviderSocketLineProcessor.swift */; };
|
||||
5374FD442B95EE1400C78D54 /* ShareController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5374FD432B95EE1400C78D54 /* ShareController.swift */; };
|
||||
5376307D2B85E2ED0026BFAB /* Logger+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5376307C2B85E2ED0026BFAB /* Logger+Extensions.swift */; };
|
||||
537630912B85F4980026BFAB /* ShareViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 537630902B85F4980026BFAB /* ShareViewController.xib */; };
|
||||
537630932B85F4B00026BFAB /* ShareViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 537630922B85F4B00026BFAB /* ShareViewController.swift */; };
|
||||
537630952B860D560026BFAB /* FPUIExtensionServiceSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 537630942B860D560026BFAB /* FPUIExtensionServiceSource.swift */; };
|
||||
@ -50,7 +45,6 @@
|
||||
53D666612B70C9A70042C03D /* FileProviderDomainDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53D666602B70C9A70042C03D /* FileProviderDomainDefaults.swift */; };
|
||||
53ED473029C9CE0B00795DB1 /* FileProviderExtension+ClientInterface.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53ED472F29C9CE0B00795DB1 /* FileProviderExtension+ClientInterface.swift */; };
|
||||
53FE14502B8E0658006C4193 /* ShareTableViewDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53FE144F2B8E0658006C4193 /* ShareTableViewDataSource.swift */; };
|
||||
53FE14542B8E1219006C4193 /* NextcloudKit in Frameworks */ = {isa = PBXBuildFile; productRef = 53FE14532B8E1219006C4193 /* NextcloudKit */; };
|
||||
53FE14592B8E3F6C006C4193 /* ShareTableItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53FE14582B8E3F6C006C4193 /* ShareTableItemView.swift */; };
|
||||
53FE145B2B8F1305006C4193 /* NKShare+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53FE145A2B8F1305006C4193 /* NKShare+Extensions.swift */; };
|
||||
53FE14652B8F6700006C4193 /* ShareViewDataSourceUIDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53FE14642B8F6700006C4193 /* ShareViewDataSourceUIDelegate.swift */; };
|
||||
@ -162,16 +156,13 @@
|
||||
/* Begin PBXFileReference section */
|
||||
530429972DD44226004BB598 /* FileProviderExtension+CustomActions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "FileProviderExtension+CustomActions.swift"; sourceTree = "<group>"; };
|
||||
531522812B8E01C6002E31BE /* ShareTableItemView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ShareTableItemView.xib; sourceTree = "<group>"; };
|
||||
531EDE562D897B4F00FD91F9 /* Eviction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Eviction.swift; sourceTree = "<group>"; };
|
||||
5350E4E72B0C514400F276CB /* ClientCommunicationProtocol.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ClientCommunicationProtocol.h; sourceTree = "<group>"; };
|
||||
5350E4E82B0C534A00F276CB /* ClientCommunicationService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClientCommunicationService.swift; sourceTree = "<group>"; };
|
||||
5350E4EA2B0C9CE100F276CB /* FileProviderExt-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "FileProviderExt-Bridging-Header.h"; sourceTree = "<group>"; };
|
||||
5352B36B29DC44B50011CE03 /* FileProviderExtension+Thumbnailing.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "FileProviderExtension+Thumbnailing.swift"; sourceTree = "<group>"; };
|
||||
535AE30D29C0A2CC0042A9BA /* Logger+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Logger+Extensions.swift"; sourceTree = "<group>"; };
|
||||
53651E452BBC0D9500ECAC29 /* ShareeSuggestionsDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareeSuggestionsDataSource.swift; sourceTree = "<group>"; };
|
||||
536EFBF6295CF58100F4CB13 /* FileProviderSocketLineProcessor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileProviderSocketLineProcessor.swift; sourceTree = "<group>"; };
|
||||
5374FD432B95EE1400C78D54 /* ShareController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareController.swift; sourceTree = "<group>"; };
|
||||
5376307C2B85E2ED0026BFAB /* Logger+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Logger+Extensions.swift"; sourceTree = "<group>"; };
|
||||
537630902B85F4980026BFAB /* ShareViewController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = ShareViewController.xib; sourceTree = "<group>"; };
|
||||
537630922B85F4B00026BFAB /* ShareViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareViewController.swift; sourceTree = "<group>"; };
|
||||
537630942B860D560026BFAB /* FPUIExtensionServiceSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FPUIExtensionServiceSource.swift; sourceTree = "<group>"; };
|
||||
@ -231,7 +222,6 @@
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
5307A6E82965DAD8001E0C6A /* NextcloudKit in Frameworks */,
|
||||
538E396A27F4765000FA63D5 /* UniformTypeIdentifiers.framework in Frameworks */,
|
||||
53903D302956173F00D0B308 /* NCDesktopClientSocketKit.framework in Frameworks */,
|
||||
53C331B22BCD28C30093D38B /* NextcloudFileProviderKit in Frameworks */,
|
||||
@ -252,7 +242,6 @@
|
||||
5358F2B92BAA0F5300E3C729 /* NextcloudCapabilitiesKit in Frameworks */,
|
||||
53C331B62BCD3AFF0093D38B /* NextcloudFileProviderKit in Frameworks */,
|
||||
53651E442BBC0CA300ECAC29 /* SuggestionsTextFieldKit in Frameworks */,
|
||||
53FE14542B8E1219006C4193 /* NextcloudKit in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@ -260,7 +249,6 @@
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
5307A6E62965C6FA001E0C6A /* NextcloudKit in Frameworks */,
|
||||
53903D212956164F00D0B308 /* NCDesktopClientSocketKit.framework in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
@ -276,14 +264,6 @@
|
||||
/* End PBXFrameworksBuildPhase section */
|
||||
|
||||
/* Begin PBXGroup section */
|
||||
531EDE542D88569400FD91F9 /* Evicting */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
531EDE562D897B4F00FD91F9 /* Eviction.swift */,
|
||||
);
|
||||
path = Evicting;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
5350E4C72B0C368B00F276CB /* Services */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@ -299,7 +279,6 @@
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
AA9987852E72B6DB00B2C428 /* NextcloudKit+clearAccountErrorState.swift */,
|
||||
535AE30D29C0A2CC0042A9BA /* Logger+Extensions.swift */,
|
||||
AA7F17E62E7038340000E928 /* NSError+FileProviderErrorCode.swift */,
|
||||
);
|
||||
path = Extensions;
|
||||
@ -308,7 +287,6 @@
|
||||
5376307B2B85E2E00026BFAB /* Extensions */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
5376307C2B85E2ED0026BFAB /* Logger+Extensions.swift */,
|
||||
53FE145A2B8F1305006C4193 /* NKShare+Extensions.swift */,
|
||||
);
|
||||
path = Extensions;
|
||||
@ -382,7 +360,6 @@
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
AA7F17DF2E70171A0000E928 /* Authentication */,
|
||||
531EDE542D88569400FD91F9 /* Evicting */,
|
||||
5376307B2B85E2E00026BFAB /* Extensions */,
|
||||
537BD6782C58D0FC00446ED0 /* Locking */,
|
||||
537BD6772C58D0C400446ED0 /* Sharing */,
|
||||
@ -506,7 +483,6 @@
|
||||
);
|
||||
name = FileProviderExt;
|
||||
packageProductDependencies = (
|
||||
5307A6E72965DAD8001E0C6A /* NextcloudKit */,
|
||||
53C331B12BCD28C30093D38B /* NextcloudFileProviderKit */,
|
||||
);
|
||||
productName = FileProviderExt;
|
||||
@ -542,11 +518,9 @@
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
53FE14522B8E1213006C4193 /* PBXTargetDependency */,
|
||||
);
|
||||
name = FileProviderUIExt;
|
||||
packageProductDependencies = (
|
||||
53FE14532B8E1219006C4193 /* NextcloudKit */,
|
||||
5358F2B82BAA0F5300E3C729 /* NextcloudCapabilitiesKit */,
|
||||
53651E432BBC0CA300ECAC29 /* SuggestionsTextFieldKit */,
|
||||
53C331B52BCD3AFF0093D38B /* NextcloudFileProviderKit */,
|
||||
@ -574,7 +548,6 @@
|
||||
);
|
||||
name = desktopclient;
|
||||
packageProductDependencies = (
|
||||
5307A6E52965C6FA001E0C6A /* NextcloudKit */,
|
||||
);
|
||||
productName = desktopclient;
|
||||
productReference = C2B573B11B1CD91E00303B36 /* desktopclient.app */;
|
||||
@ -753,7 +726,6 @@
|
||||
);
|
||||
mainGroup = C2B573941B1CD88000303B36;
|
||||
packageReferences = (
|
||||
5307A6E42965C6FA001E0C6A /* XCRemoteSwiftPackageReference "NextcloudKit" */,
|
||||
5358F2B72BAA045E00E3C729 /* XCRemoteSwiftPackageReference "NextcloudCapabilitiesKit" */,
|
||||
53651E422BBC0C7F00ECAC29 /* XCRemoteSwiftPackageReference "SuggestionsTextFieldKit" */,
|
||||
53C331B02BCD28C30093D38B /* XCRemoteSwiftPackageReference "NextcloudFileProviderKit" */,
|
||||
@ -851,7 +823,6 @@
|
||||
536EFBF7295CF58100F4CB13 /* FileProviderSocketLineProcessor.swift in Sources */,
|
||||
AA02B2AB2E7048C800C72B34 /* Keychain.swift in Sources */,
|
||||
537630972B860D920026BFAB /* FPUIExtensionService.swift in Sources */,
|
||||
535AE30E29C0A2CC0042A9BA /* Logger+Extensions.swift in Sources */,
|
||||
537630952B860D560026BFAB /* FPUIExtensionServiceSource.swift in Sources */,
|
||||
530429982DD44235004BB598 /* FileProviderExtension+CustomActions.swift in Sources */,
|
||||
5350E4E92B0C534A00F276CB /* ClientCommunicationService.swift in Sources */,
|
||||
@ -872,7 +843,6 @@
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
537BD6822C58F72E00446ED0 /* MetadataProvider.swift in Sources */,
|
||||
531EDE572D897B4F00FD91F9 /* Eviction.swift in Sources */,
|
||||
537630932B85F4B00026BFAB /* ShareViewController.swift in Sources */,
|
||||
53FE14672B8F78B6006C4193 /* ShareOptionsView.swift in Sources */,
|
||||
53651E462BBC0D9500ECAC29 /* ShareeSuggestionsDataSource.swift in Sources */,
|
||||
@ -882,7 +852,6 @@
|
||||
5374FD442B95EE1400C78D54 /* ShareController.swift in Sources */,
|
||||
53FE145B2B8F1305006C4193 /* NKShare+Extensions.swift in Sources */,
|
||||
53FE14592B8E3F6C006C4193 /* ShareTableItemView.swift in Sources */,
|
||||
5376307D2B85E2ED0026BFAB /* Logger+Extensions.swift in Sources */,
|
||||
AA7F17E32E70173E0000E928 /* AuthenticationViewController.swift in Sources */,
|
||||
53FE14502B8E0658006C4193 /* ShareTableViewDataSource.swift in Sources */,
|
||||
537BD6802C58F01B00446ED0 /* FileProviderCommunication.swift in Sources */,
|
||||
@ -930,10 +899,6 @@
|
||||
target = 53903D0B2956164F00D0B308 /* NCDesktopClientSocketKit */;
|
||||
targetProxy = 53903D322956173F00D0B308 /* PBXContainerItemProxy */;
|
||||
};
|
||||
53FE14522B8E1213006C4193 /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
productRef = 53FE14512B8E1213006C4193 /* NextcloudKit */;
|
||||
};
|
||||
C2B573E01B1CD9CE00303B36 /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
target = C2B573D61B1CD9CE00303B36 /* FinderSyncExt */;
|
||||
@ -1649,14 +1614,6 @@
|
||||
/* End XCConfigurationList section */
|
||||
|
||||
/* Begin XCRemoteSwiftPackageReference section */
|
||||
5307A6E42965C6FA001E0C6A /* XCRemoteSwiftPackageReference "NextcloudKit" */ = {
|
||||
isa = XCRemoteSwiftPackageReference;
|
||||
repositoryURL = "https://github.com/nextcloud/NextcloudKit.git";
|
||||
requirement = {
|
||||
kind = exactVersion;
|
||||
version = 6.0.9;
|
||||
};
|
||||
};
|
||||
5358F2B72BAA045E00E3C729 /* XCRemoteSwiftPackageReference "NextcloudCapabilitiesKit" */ = {
|
||||
isa = XCRemoteSwiftPackageReference;
|
||||
repositoryURL = "https://github.com/claucambra/NextcloudCapabilitiesKit.git";
|
||||
@ -1684,16 +1641,6 @@
|
||||
/* End XCRemoteSwiftPackageReference section */
|
||||
|
||||
/* Begin XCSwiftPackageProductDependency section */
|
||||
5307A6E52965C6FA001E0C6A /* NextcloudKit */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = 5307A6E42965C6FA001E0C6A /* XCRemoteSwiftPackageReference "NextcloudKit" */;
|
||||
productName = NextcloudKit;
|
||||
};
|
||||
5307A6E72965DAD8001E0C6A /* NextcloudKit */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = 5307A6E42965C6FA001E0C6A /* XCRemoteSwiftPackageReference "NextcloudKit" */;
|
||||
productName = NextcloudKit;
|
||||
};
|
||||
5358F2B82BAA0F5300E3C729 /* NextcloudCapabilitiesKit */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = 5358F2B72BAA045E00E3C729 /* XCRemoteSwiftPackageReference "NextcloudCapabilitiesKit" */;
|
||||
@ -1714,16 +1661,6 @@
|
||||
package = 53C331B02BCD28C30093D38B /* XCRemoteSwiftPackageReference "NextcloudFileProviderKit" */;
|
||||
productName = NextcloudFileProviderKit;
|
||||
};
|
||||
53FE14512B8E1213006C4193 /* NextcloudKit */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = 5307A6E42965C6FA001E0C6A /* XCRemoteSwiftPackageReference "NextcloudKit" */;
|
||||
productName = NextcloudKit;
|
||||
};
|
||||
53FE14532B8E1219006C4193 /* NextcloudKit */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = 5307A6E42965C6FA001E0C6A /* XCRemoteSwiftPackageReference "NextcloudKit" */;
|
||||
productName = NextcloudKit;
|
||||
};
|
||||
/* End XCSwiftPackageProductDependency section */
|
||||
};
|
||||
rootObject = C2B573951B1CD88000303B36 /* Project object */;
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
{
|
||||
"originHash" : "b1fcfe4980e7dccc02e64ff6b1167e865e1b0fd2839c1f05e315987946e210b5",
|
||||
"originHash" : "af7b30e0399513f38616bd75821c834208fc3c22e3f5ec7d1765b1dbb03a46cd",
|
||||
"pins" : [
|
||||
{
|
||||
"identity" : "alamofire",
|
||||
@ -25,16 +25,16 @@
|
||||
"location" : "https://github.com/nextcloud/NextcloudFileProviderKit.git",
|
||||
"state" : {
|
||||
"branch" : "main",
|
||||
"revision" : "628d78e08a72a9927775d50c71f6af0e8c0f8df3"
|
||||
"revision" : "8add813aceb22a11309496e3249b36bd80c5db3d"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "nextcloudkit",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/nextcloud/NextcloudKit.git",
|
||||
"location" : "https://github.com/nextcloud/NextcloudKit",
|
||||
"state" : {
|
||||
"revision" : "8ac6704234f529d09b0651e4d94fdc14f8820333",
|
||||
"version" : "6.0.9"
|
||||
"revision" : "3353eacfb44fb981ae2e14236f0bcd7e11ea2707",
|
||||
"version" : "7.1.4"
|
||||
}
|
||||
},
|
||||
{
|
||||
@ -51,8 +51,8 @@
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/realm/realm-swift.git",
|
||||
"state" : {
|
||||
"revision" : "bae6c4be7df169fdb047d0ad63f902c1e2665e83",
|
||||
"version" : "20.0.1"
|
||||
"revision" : "6260534683132eb981338c7c39fd5e69205876e6",
|
||||
"version" : "20.0.3"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@ -25,6 +25,7 @@
|
||||
#endif
|
||||
|
||||
#ifdef BUILD_FILE_PROVIDER_MODULE
|
||||
#include "macOS/fileproviderutils.h"
|
||||
#include "macOS/fileprovider.h"
|
||||
#include "macOS/fileprovidersettingscontroller.h"
|
||||
#endif
|
||||
@ -39,6 +40,7 @@
|
||||
#include <QMessageBox>
|
||||
#include <QNetworkProxy>
|
||||
#include <QDir>
|
||||
#include <QDirIterator>
|
||||
#include <QScopedValueRollback>
|
||||
#include <QMessageBox>
|
||||
|
||||
@ -139,23 +141,27 @@ bool createDebugArchive(const QString &filename)
|
||||
}
|
||||
|
||||
#ifdef BUILD_FILE_PROVIDER_MODULE
|
||||
const auto fileProvider = OCC::Mac::FileProvider::instance();
|
||||
if (fileProvider && fileProvider->fileProviderAvailable()) {
|
||||
const auto tempDir = QTemporaryDir();
|
||||
const auto xpc = fileProvider->xpc();
|
||||
const auto vfsAccounts = OCC::Mac::FileProviderSettingsController::instance()->vfsEnabledAccounts();
|
||||
for (const auto &accountUserIdAtHost : vfsAccounts) {
|
||||
const auto accountState = OCC::AccountManager::instance()->accountFromUserId(accountUserIdAtHost);
|
||||
if (!accountState) {
|
||||
qWarning() << "Could not find account for" << accountUserIdAtHost;
|
||||
continue;
|
||||
}
|
||||
const auto account = accountState->account();
|
||||
const auto vfsLogFilename = QStringLiteral("macOS_vfs_%1.log").arg(account->davUser());
|
||||
const auto vfsLogPath = tempDir.filePath(vfsLogFilename);
|
||||
xpc->createDebugArchiveForFileProviderDomain(accountUserIdAtHost, vfsLogPath);
|
||||
zip.addLocalFile(vfsLogPath, vfsLogFilename);
|
||||
qDebug() << "Trying to add file provider domain log files...";
|
||||
const auto fileProviderExtensionLogDirectory = OCC::Mac::FileProviderUtils::fileProviderExtensionLogDirectory();
|
||||
|
||||
if (fileProviderExtensionLogDirectory.exists()) {
|
||||
// Recursively add all files from the container log directory
|
||||
QDirIterator it(fileProviderExtensionLogDirectory.path(), QDir::Files | QDir::NoDotAndDotDot, QDirIterator::Subdirectories);
|
||||
|
||||
while (it.hasNext()) {
|
||||
const auto logFilePath = it.next();
|
||||
const auto logFileInfo = QFileInfo(logFilePath);
|
||||
|
||||
// Calculate relative path from the base container log directory
|
||||
const auto relativePath = fileProviderExtensionLogDirectory.relativeFilePath(logFilePath);
|
||||
const auto zipPath = QStringLiteral("File Provider Domains/%1").arg(relativePath);
|
||||
|
||||
zip.addLocalFile(logFilePath, zipPath);
|
||||
}
|
||||
|
||||
qDebug() << "Added file provider domain log files from" << fileProviderExtensionLogDirectory.path();
|
||||
} else {
|
||||
qWarning() << "file provider domain container log directory not found at" << fileProviderExtensionLogDirectory.path();
|
||||
}
|
||||
#endif
|
||||
|
||||
@ -182,7 +188,7 @@ GeneralSettings::GeneralSettings(QWidget *parent)
|
||||
_ui->setupUi(this);
|
||||
|
||||
updatePollIntervalVisibility();
|
||||
|
||||
|
||||
connect(_ui->serverNotificationsCheckBox, &QAbstractButton::toggled,
|
||||
this, &GeneralSettings::slotToggleOptionalServerNotifications);
|
||||
_ui->serverNotificationsCheckBox->setToolTip(tr("Server notifications that require attention."));
|
||||
@ -211,7 +217,7 @@ GeneralSettings::GeneralSettings(QWidget *parent)
|
||||
_ui->autostartCheckBox->setChecked(hasSystemAutoStart);
|
||||
_ui->autostartCheckBox->setDisabled(hasSystemAutoStart);
|
||||
_ui->autostartCheckBox->setToolTip(tr("You cannot disable autostart because system-wide autostart is enabled."));
|
||||
} else {
|
||||
} else {
|
||||
connect(_ui->autostartCheckBox, &QAbstractButton::toggled, this, &GeneralSettings::slotToggleLaunchOnStartup);
|
||||
_ui->autostartCheckBox->setChecked(ConfigFile().launchOnSystemStartup());
|
||||
}
|
||||
@ -239,7 +245,7 @@ GeneralSettings::GeneralSettings(QWidget *parent)
|
||||
connect(_ui->newExternalStorage, &QAbstractButton::toggled, this, &GeneralSettings::saveMiscSettings);
|
||||
connect(_ui->moveFilesToTrashCheckBox, &QAbstractButton::toggled, this, &GeneralSettings::saveMiscSettings);
|
||||
connect(_ui->remotePollIntervalSpinBox, &QSpinBox::valueChanged, this, &GeneralSettings::slotRemotePollIntervalChanged);
|
||||
|
||||
|
||||
// Hide on non-Windows, or WindowsVersion < 10.
|
||||
// The condition should match the default value of ConfigFile::showInExplorerNavigationPane.
|
||||
#ifdef Q_OS_WIN
|
||||
@ -318,9 +324,9 @@ void GeneralSettings::loadMiscSettings()
|
||||
_ui->newExternalStorage->setChecked(cfgFile.confirmExternalStorage());
|
||||
_ui->monoIconsCheckBox->setChecked(cfgFile.monoIcons());
|
||||
|
||||
const auto interval = cfgFile.remotePollInterval();
|
||||
const auto interval = cfgFile.remotePollInterval();
|
||||
_ui->remotePollIntervalSpinBox->setValue(static_cast<int>(interval.count() / 1000));
|
||||
updatePollIntervalVisibility();
|
||||
updatePollIntervalVisibility();
|
||||
}
|
||||
|
||||
#if defined(BUILD_UPDATER)
|
||||
@ -680,7 +686,7 @@ void GeneralSettings::customizeStyle()
|
||||
#endif
|
||||
}
|
||||
|
||||
void GeneralSettings::slotRemotePollIntervalChanged(int seconds)
|
||||
void GeneralSettings::slotRemotePollIntervalChanged(int seconds)
|
||||
{
|
||||
if (_currentlyLoading) {
|
||||
return;
|
||||
@ -691,7 +697,7 @@ void GeneralSettings::slotRemotePollIntervalChanged(int seconds)
|
||||
cfgFile.setRemotePollInterval(interval);
|
||||
}
|
||||
|
||||
void GeneralSettings::updatePollIntervalVisibility()
|
||||
void GeneralSettings::updatePollIntervalVisibility()
|
||||
{
|
||||
const auto accounts = AccountManager::instance()->accounts();
|
||||
const auto pushAvailable = std::any_of(accounts.cbegin(), accounts.cend(), [](const AccountStatePtr &accountState) -> bool {
|
||||
|
||||
@ -33,25 +33,25 @@ QString uuidDomainIdentifierForAccount(const OCC::Account * const account)
|
||||
{
|
||||
Q_ASSERT(account);
|
||||
const auto accountId = account->userIdAtHostWithPort();
|
||||
|
||||
|
||||
if (accountId.isEmpty()) {
|
||||
qCWarning(OCC::lcMacFileProviderDomainManager) << "Cannot generate UUID for account with empty userIdAtHostWithPort";
|
||||
return {};
|
||||
}
|
||||
|
||||
|
||||
// Try to get existing UUID mapping first
|
||||
OCC::ConfigFile cfg;
|
||||
const QString existingUuid = cfg.fileProviderDomainUuidFromAccountId(accountId);
|
||||
|
||||
if (!existingUuid.isEmpty()) {
|
||||
qCDebug(OCC::lcMacFileProviderDomainManager) << "Using existing UUID for account:"
|
||||
qCDebug(OCC::lcMacFileProviderDomainManager) << "Using existing UUID for account:"
|
||||
<< accountId
|
||||
<< "UUID:"
|
||||
<< existingUuid;
|
||||
|
||||
return existingUuid;
|
||||
}
|
||||
|
||||
|
||||
// Generate new UUID for this account
|
||||
const QString newUuid = QUuid::createUuid().toString(QUuid::WithoutBraces);
|
||||
|
||||
@ -85,7 +85,7 @@ inline QString accountIdFromDomainId(const QString &domainId)
|
||||
if (domainId.isEmpty()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
|
||||
// Check if this is a UUID-based domain identifier
|
||||
if (QUuid::fromString(domainId).isNull() == false) {
|
||||
// This is a UUID, look up the account ID from the mapping
|
||||
@ -107,7 +107,7 @@ inline QString accountIdFromDomainId(const QString &domainId)
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
|
||||
// This is a legacy account-based domain identifier
|
||||
qCDebug(OCC::lcMacFileProviderDomainManager) << "Using legacy account-based domain ID:"
|
||||
<< domainId;
|
||||
@ -120,13 +120,13 @@ QString accountIdFromDomainId(NSString * const domainId)
|
||||
if (!domainId) {
|
||||
return {};
|
||||
}
|
||||
|
||||
|
||||
auto qDomainId = QString::fromNSString(domainId);
|
||||
|
||||
if (qDomainId.isEmpty()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
|
||||
// Check if this is a UUID-based domain identifier
|
||||
if (QUuid::fromString(qDomainId).isNull() == false) {
|
||||
// This is a UUID, look up the account ID from the mapping
|
||||
@ -143,7 +143,7 @@ QString accountIdFromDomainId(NSString * const domainId)
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
|
||||
// This is a legacy account-based domain identifier - handle the old logic
|
||||
qCDebug(OCC::lcMacFileProviderDomainManager) << "Processing legacy account-based domain ID from NSString:" << qDomainId;
|
||||
|
||||
@ -231,21 +231,24 @@ public:
|
||||
<< "and display name:"
|
||||
<< domain.displayName
|
||||
<< "removing and recreating";
|
||||
|
||||
[NSFileProviderManager removeDomain:domain completionHandler:^(NSError * const error) {
|
||||
if (error) {
|
||||
qCWarning(lcMacFileProviderDomainManager) << "Error removing file provider domain with illegal domain identifier: "
|
||||
<< error.code
|
||||
<< error.localizedDescription;
|
||||
|
||||
} else {
|
||||
qCInfo(lcMacFileProviderDomainManager) << "Successfully removed file provider domain with illegal domain identifier: "
|
||||
<< domain.identifier;
|
||||
}
|
||||
|
||||
removeFileProviderDomainData(domain.identifier);
|
||||
[domain release];
|
||||
}];
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
_registeredDomains.insert(accountId, domain);
|
||||
|
||||
NSFileProviderManager * const fpManager = [NSFileProviderManager managerForDomain:domain];
|
||||
@ -276,6 +279,8 @@ public:
|
||||
<< error.code
|
||||
<< error.localizedDescription;
|
||||
}
|
||||
|
||||
removeFileProviderDomainData(domain.identifier);
|
||||
}];
|
||||
}
|
||||
}
|
||||
@ -341,6 +346,38 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
void removeFileProviderDomainData(NSString * const domainIdentifier)
|
||||
{
|
||||
const auto qDomainIdentifier = QString::fromNSString(domainIdentifier);
|
||||
|
||||
// Remove logs.
|
||||
|
||||
auto logDirectory = OCC::Mac::FileProviderUtils::fileProviderDomainLogDirectory(qDomainIdentifier);
|
||||
|
||||
if (logDirectory.exists()) {
|
||||
qCInfo(lcMacFileProviderDomainManager) << "Removing log directory at" << logDirectory.path();
|
||||
logDirectory.removeRecursively();
|
||||
} else {
|
||||
qCInfo(lcMacFileProviderDomainManager) << "Due to lack of existence, not removing log directory at" << logDirectory.path();
|
||||
}
|
||||
|
||||
// Remove support data.
|
||||
|
||||
auto supportDirectory = OCC::Mac::FileProviderUtils::fileProviderDomainSupportDirectory(qDomainIdentifier);
|
||||
|
||||
if (supportDirectory.exists()) {
|
||||
qCInfo(lcMacFileProviderDomainManager) << "Removing support directory at" << supportDirectory.path();
|
||||
supportDirectory.removeRecursively();
|
||||
} else {
|
||||
qCInfo(lcMacFileProviderDomainManager) << "Due to lack of existence, not removing support directory at" << supportDirectory.path();
|
||||
}
|
||||
|
||||
// Remove configuration leftovers.
|
||||
|
||||
OCC::ConfigFile cfg;
|
||||
cfg.removeFileProviderDomainMappingByDomainIdentifier(qDomainIdentifier);
|
||||
}
|
||||
|
||||
void removeFileProviderDomain(const AccountState * const accountState)
|
||||
{
|
||||
if (@available(macOS 11.0, *)) {
|
||||
@ -367,6 +404,7 @@ public:
|
||||
<< error.localizedDescription;
|
||||
}
|
||||
|
||||
removeFileProviderDomainData(fileProviderDomain.identifier);
|
||||
NSFileProviderDomain * const domain = _registeredDomains.take(accountId);
|
||||
[domain release];
|
||||
|
||||
@ -392,16 +430,15 @@ public:
|
||||
|
||||
const auto registeredDomainPtrs = _registeredDomains.values();
|
||||
const auto accountIds = _registeredDomains.keys();
|
||||
|
||||
for (NSFileProviderDomain * const domain : registeredDomainPtrs) {
|
||||
removeFileProviderDomainData(domain.identifier);
|
||||
|
||||
if (domain != nil) {
|
||||
[domain release];
|
||||
}
|
||||
}
|
||||
// Clean up UUID mappings for all accounts
|
||||
OCC::ConfigFile cfg;
|
||||
for (const QString &accountId : accountIds) {
|
||||
cfg.removeFileProviderDomainUuidMapping(accountId);
|
||||
}
|
||||
|
||||
_registeredDomains.clear();
|
||||
}];
|
||||
}
|
||||
@ -432,15 +469,13 @@ public:
|
||||
return;
|
||||
}
|
||||
|
||||
removeFileProviderDomainData(domain.identifier);
|
||||
|
||||
const QString accountId = accountIdFromDomainId(domain.identifier);
|
||||
NSFileProviderDomain * const registeredDomainPtr = _registeredDomains.take(accountId);
|
||||
|
||||
if (registeredDomainPtr != nil) {
|
||||
[domain release];
|
||||
// Clean up UUID mapping when wiping domain
|
||||
if (!accountId.isEmpty()) {
|
||||
OCC::ConfigFile cfg;
|
||||
cfg.removeFileProviderDomainUuidMapping(accountId);
|
||||
}
|
||||
}
|
||||
}];
|
||||
}
|
||||
@ -510,6 +545,7 @@ public:
|
||||
Q_ASSERT(fileProviderDomain != nil);
|
||||
|
||||
NSFileProviderManager * const fpManager = [NSFileProviderManager managerForDomain:fileProviderDomain];
|
||||
|
||||
[fpManager reconnectWithCompletionHandler:^(NSError * const error) {
|
||||
if (error) {
|
||||
qCWarning(lcMacFileProviderDomainManager) << "Error reconnecting file provider domain: "
|
||||
@ -587,6 +623,7 @@ void FileProviderDomainManager::start()
|
||||
// shutdowns.
|
||||
connect(AccountManager::instance(), &AccountManager::accountSyncConnectionRemoved,
|
||||
this, &FileProviderDomainManager::removeFileProviderDomainForAccount);
|
||||
|
||||
connect(AccountManager::instance(), &AccountManager::accountRemoved,
|
||||
this, [this](const AccountState * const accountState) {
|
||||
const auto trReason = tr("%1 application has been closed. Reopen to reconnect.").arg(APPLICATION_NAME);
|
||||
|
||||
@ -49,7 +49,6 @@ public slots:
|
||||
void createEvictionWindowForAccount(const QString &userIdAtHost);
|
||||
void refreshMaterialisedItemsForAccount(const QString &userIdAtHost);
|
||||
void signalFileProviderDomain(const QString &userIdAtHost);
|
||||
void createDebugArchive(const QString &userIdAtHost);
|
||||
|
||||
signals:
|
||||
void vfsEnabledAccountsChanged();
|
||||
|
||||
@ -509,24 +509,6 @@ void FileProviderSettingsController::signalFileProviderDomain(const QString &use
|
||||
d->signalFileProviderDomain(userIdAtHost);
|
||||
}
|
||||
|
||||
void FileProviderSettingsController::createDebugArchive(const QString &userIdAtHost)
|
||||
{
|
||||
const auto filename = QFileDialog::getSaveFileName(nullptr,
|
||||
tr("Create Debug Archive"),
|
||||
QStandardPaths::writableLocation(QStandardPaths::StandardLocation::DocumentsLocation),
|
||||
tr("Text files") + " (*.txt)");
|
||||
if (filename.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto xpc = FileProvider::instance()->xpc();
|
||||
if (!xpc) {
|
||||
qCWarning(lcFileProviderSettingsController) << "Could not create debug archive, FileProviderXPC is not available.";
|
||||
return;
|
||||
}
|
||||
xpc->createDebugArchiveForFileProviderDomain(userIdAtHost, filename);
|
||||
}
|
||||
|
||||
FileProviderDomainSyncStatus *FileProviderSettingsController::domainSyncStatusForAccount(const QString &userIdAtHost) const
|
||||
{
|
||||
return d->domainSyncStatusForAccount(userIdAtHost);
|
||||
|
||||
@ -9,8 +9,17 @@
|
||||
|
||||
class QString;
|
||||
|
||||
// Forward declarations for Objective-C types using conditional compilation
|
||||
#ifdef __OBJC__
|
||||
@class NSFileProviderDomain;
|
||||
@class NSFileProviderManager;
|
||||
@class NSString;
|
||||
#else
|
||||
// In C++ context, use opaque pointers
|
||||
struct NSFileProviderDomain;
|
||||
struct NSFileProviderManager;
|
||||
struct NSString;
|
||||
#endif
|
||||
|
||||
/**
|
||||
* This file contains the FileProviderUtils namespace, which contains
|
||||
@ -94,6 +103,21 @@ QString domainIdentifierForAccountIdentifier(const NSString *accountId);
|
||||
*/
|
||||
bool illegalDomainIdentifier(const QString &domainId);
|
||||
|
||||
/**
|
||||
* @brief Find the logs directory of the file provider extension for all the domains.
|
||||
*/
|
||||
QDir fileProviderExtensionLogDirectory();
|
||||
|
||||
/**
|
||||
* @brief Find the logs directory of the file provider domain with the given identifier.
|
||||
*/
|
||||
QDir fileProviderDomainLogDirectory(const QString domainIdentifier);
|
||||
|
||||
/**
|
||||
* @brief Find the application support directory of the file provider domain with the given identifier.
|
||||
*/
|
||||
QDir fileProviderDomainSupportDirectory(const QString domainIdentifier);
|
||||
|
||||
/**
|
||||
* @brief Synchronously retrieves an NSFileProviderManager for the given domain identifier.
|
||||
*
|
||||
|
||||
@ -7,6 +7,8 @@
|
||||
#include "fileproviderutils.h"
|
||||
#include "account.h"
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QDir>
|
||||
#include <QLoggingCategory>
|
||||
#include <QRegularExpression>
|
||||
#include <QString>
|
||||
@ -134,6 +136,49 @@ QString domainIdentifierForAccount(const OCC::AccountPtr account)
|
||||
return domainIdentifierForAccount(account.get());
|
||||
}
|
||||
|
||||
QDir fileProviderExtensionContainer()
|
||||
{
|
||||
const auto baseBundleId = QCoreApplication::organizationDomain();
|
||||
const auto extensionBundleId = baseBundleId + QStringLiteral(".FileProviderExt");
|
||||
|
||||
auto dir = QDir::home();
|
||||
dir.cd("Library");
|
||||
dir.cd("Containers");
|
||||
dir.cd(extensionBundleId);
|
||||
|
||||
return dir;
|
||||
}
|
||||
|
||||
QDir fileProviderExtensionLogDirectory()
|
||||
{
|
||||
auto dir = fileProviderExtensionContainer();
|
||||
dir.cd("Data");
|
||||
dir.cd("Library");
|
||||
dir.cd("Logs");
|
||||
|
||||
return dir;
|
||||
}
|
||||
|
||||
QDir fileProviderDomainLogDirectory(const QString domainIdentifier)
|
||||
{
|
||||
auto dir = fileProviderExtensionLogDirectory();
|
||||
dir.cd(domainIdentifier);
|
||||
|
||||
return dir;
|
||||
}
|
||||
|
||||
QDir fileProviderDomainSupportDirectory(const QString domainIdentifier)
|
||||
{
|
||||
auto dir = fileProviderExtensionContainer();
|
||||
dir.cd("Data");
|
||||
dir.cd("Library");
|
||||
dir.cd("Application Support");
|
||||
dir.cd("File Provider Domains");
|
||||
dir.cd(domainIdentifier);
|
||||
|
||||
return dir;
|
||||
}
|
||||
|
||||
NSFileProviderManager *managerForDomainIdentifier(const QString &domainIdentifier)
|
||||
{
|
||||
NSFileProviderDomain * const domain = domainForIdentifier(domainIdentifier);
|
||||
|
||||
@ -35,7 +35,6 @@ public slots:
|
||||
void authenticateFileProviderDomains();
|
||||
void authenticateFileProviderDomain(const QString &fileProviderDomainIdentifier) const;
|
||||
void unauthenticateFileProviderDomain(const QString &fileProviderDomainIdentifier) const;
|
||||
void createDebugArchiveForFileProviderDomain(const QString &fileProviderDomainIdentifier, const QString &filename);
|
||||
|
||||
void setIgnoreList() const;
|
||||
void setTrashDeletionEnabledForFileProviderDomain(const QString &fileProviderDomainIdentifier, bool enabled) const;
|
||||
|
||||
@ -124,48 +124,6 @@ void FileProviderXPC::slotAccountStateChanged(const AccountState::State state) c
|
||||
break;
|
||||
}
|
||||
}
|
||||
void FileProviderXPC::createDebugArchiveForFileProviderDomain(const QString &fileProviderDomainIdentifier, const QString &filename)
|
||||
{
|
||||
qCInfo(lcFileProviderXPC) << "Creating debug archive for extension" << fileProviderDomainIdentifier << "at" << filename;
|
||||
|
||||
if (!fileProviderDomainReachable(fileProviderDomainIdentifier)) {
|
||||
qCWarning(lcFileProviderXPC) << "Extension is not reachable. Cannot create debug archive";
|
||||
return;
|
||||
}
|
||||
|
||||
// You need to fetch the contents from the extension and then create the archive from the client side.
|
||||
// The extension is not allowed to ask for permission to write into the file system as it is not a user facing process.
|
||||
const auto clientCommService = (NSObject<ClientCommunicationProtocol> *)_clientCommServices.value(fileProviderDomainIdentifier);
|
||||
const auto group = dispatch_group_create();
|
||||
__block NSString *rcvdDebugLogString;
|
||||
dispatch_group_enter(group);
|
||||
[clientCommService createDebugLogStringWithCompletionHandler:^(NSString *const debugLogString, NSError *const error) {
|
||||
if (error != nil) {
|
||||
qCWarning(lcFileProviderXPC) << "Error getting debug log string" << error.localizedDescription;
|
||||
dispatch_group_leave(group);
|
||||
return;
|
||||
} else if (debugLogString == nil) {
|
||||
qCWarning(lcFileProviderXPC) << "Debug log string is nil";
|
||||
dispatch_group_leave(group);
|
||||
return;
|
||||
}
|
||||
rcvdDebugLogString = [NSString stringWithString:debugLogString];
|
||||
[rcvdDebugLogString retain];
|
||||
dispatch_group_leave(group);
|
||||
}];
|
||||
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
|
||||
|
||||
QFile debugLogFile(filename);
|
||||
if (debugLogFile.open(QIODevice::WriteOnly)) {
|
||||
debugLogFile.write(rcvdDebugLogString.UTF8String);
|
||||
debugLogFile.close();
|
||||
qCInfo(lcFileProviderXPC) << "Debug log file written to" << filename;
|
||||
} else {
|
||||
qCWarning(lcFileProviderXPC) << "Could not open debug log file" << filename;
|
||||
}
|
||||
|
||||
[rcvdDebugLogString release];
|
||||
}
|
||||
|
||||
bool FileProviderXPC::fileProviderDomainReachable(const QString &fileProviderDomainIdentifier, const bool retry, const bool reconfigureOnFail)
|
||||
{
|
||||
|
||||
@ -1330,6 +1330,7 @@ void ConfigFile::setFileProviderDomainUuidForAccountId(const QString &accountId,
|
||||
if (accountId.isEmpty() || domainUuid.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
storeData(QStringLiteral("FileProviderDomainUuids"), accountId, domainUuid);
|
||||
storeData(QStringLiteral("FileProviderAccountIds"), domainUuid, accountId);
|
||||
}
|
||||
@ -1339,6 +1340,7 @@ QString ConfigFile::accountIdFromFileProviderDomainUuid(const QString &domainUui
|
||||
if (domainUuid.isEmpty()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return retrieveData(QStringLiteral("FileProviderAccountIds"), domainUuid).toString();
|
||||
}
|
||||
|
||||
@ -1347,11 +1349,29 @@ void ConfigFile::removeFileProviderDomainUuidMapping(const QString &accountId)
|
||||
if (accountId.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const QString domainUuid = fileProviderDomainUuidFromAccountId(accountId);
|
||||
|
||||
if (!domainUuid.isEmpty()) {
|
||||
removeData(QStringLiteral("FileProviderAccountIds"), domainUuid);
|
||||
}
|
||||
|
||||
removeData(QStringLiteral("FileProviderDomainUuids"), accountId);
|
||||
}
|
||||
|
||||
void ConfigFile::removeFileProviderDomainMappingByDomainIdentifier(const QString domainIdentifier)
|
||||
{
|
||||
if (domainIdentifier.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
removeData(QStringLiteral("FileProviderAccountIds"), domainIdentifier);
|
||||
|
||||
const QString accountIdentifier = accountIdFromFileProviderDomainUuid(domainIdentifier);
|
||||
|
||||
if (!accountIdentifier.isEmpty()) {
|
||||
removeData(QStringLiteral("FileProviderDomainUuids"), accountIdentifier);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -254,6 +254,7 @@ public:
|
||||
void setFileProviderDomainUuidForAccountId(const QString &accountId, const QString &domainUuid);
|
||||
[[nodiscard]] QString accountIdFromFileProviderDomainUuid(const QString &domainUuid) const;
|
||||
void removeFileProviderDomainUuidMapping(const QString &accountId);
|
||||
void removeFileProviderDomainMappingByDomainIdentifier(const QString domainIdentifier);
|
||||
|
||||
static constexpr char isVfsEnabledC[] = "isVfsEnabled";
|
||||
static constexpr char launchOnSystemStartupC[] = "launchOnSystemStartup";
|
||||
|
||||
Loading…
Reference in New Issue
Block a user