static let desktopClientConnection = Logger(
subsystem: subsystem, category: "desktopclientconnection")
static let fpUiExtensionService = Logger(subsystem: subsystem, category: "fpUiExtensionService")
- static let enumeration = Logger(subsystem: subsystem, category: "enumeration")
static let fileProviderExtension = Logger(
subsystem: subsystem, category: "fileproviderextension")
static let fileTransfer = Logger(subsystem: subsystem, category: "filetransfer")
+++ /dev/null
-/*
- * Copyright (C) 2023 by Claudio Cambra <claudio.cambra@nextcloud.com>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
- * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
- * for more details.
- */
-
-import FileProvider
-import NextcloudKit
-import NextcloudFileProviderKit
-import OSLog
-
-extension FileProviderEnumerator {
- func fullRecursiveScan(
- ncAccount: Account,
- ncKit: NextcloudKit,
- scanChangesOnly: Bool,
- completionHandler: @escaping (
- _ metadatas: [ItemMetadata],
- _ newMetadatas: [ItemMetadata],
- _ updatedMetadatas: [ItemMetadata],
- _ deletedMetadatas: [ItemMetadata],
- _ error: NKError?
- ) -> Void
- ) {
- let rootContainerDirectoryMetadata = ItemMetadata()
- rootContainerDirectoryMetadata.directory = true
- rootContainerDirectoryMetadata.ocId = NSFileProviderItemIdentifier.rootContainer.rawValue
-
- // Create a serial dispatch queue
- let dispatchQueue = DispatchQueue(
- label: "recursiveChangeEnumerationQueue", qos: .userInitiated)
-
- dispatchQueue.async {
- let results = self.scanRecursively(
- rootContainerDirectoryMetadata,
- ncAccount: ncAccount,
- ncKit: ncKit,
- scanChangesOnly: scanChangesOnly)
-
- // Run a check to ensure files deleted in one location are not updated in another (e.g. when moved)
- // The recursive scan provides us with updated/deleted metadatas only on a folder by folder basis;
- // so we need to check we are not simultaneously marking a moved file as deleted and updated
- var checkedDeletedMetadatas = results.deletedMetadatas
-
- for updatedMetadata in results.updatedMetadatas {
- guard
- let matchingDeletedMetadataIdx = checkedDeletedMetadatas.firstIndex(where: {
- $0.ocId == updatedMetadata.ocId
- })
- else {
- continue
- }
-
- checkedDeletedMetadatas.remove(at: matchingDeletedMetadataIdx)
- }
-
- DispatchQueue.main.async {
- completionHandler(
- results.metadatas, results.newMetadatas, results.updatedMetadatas,
- checkedDeletedMetadatas, results.error)
- }
- }
- }
-
- private func scanRecursively(
- _ directoryMetadata: ItemMetadata,
- ncAccount: Account,
- ncKit: NextcloudKit,
- scanChangesOnly: Bool
- ) -> (
- metadatas: [ItemMetadata],
- newMetadatas: [ItemMetadata],
- updatedMetadatas: [ItemMetadata],
- deletedMetadatas: [ItemMetadata],
- error: NKError?
- ) {
- if isInvalidated {
- return ([], [], [], [], nil)
- }
-
- assert(directoryMetadata.directory, "Can only recursively scan a directory.")
-
- // Will include results of recursive calls
- var allMetadatas: [ItemMetadata] = []
- var allNewMetadatas: [ItemMetadata] = []
- var allUpdatedMetadatas: [ItemMetadata] = []
- var allDeletedMetadatas: [ItemMetadata] = []
-
- let dbManager = FilesDatabaseManager.shared
- let dispatchGroup = DispatchGroup() // TODO: Maybe own thread?
-
- dispatchGroup.enter()
-
- var criticalError: NKError?
- let itemServerUrl =
- directoryMetadata.ocId == NSFileProviderItemIdentifier.rootContainer.rawValue
- ? ncAccount.davFilesUrl : directoryMetadata.serverUrl + "/" + directoryMetadata.fileName
-
- Logger.enumeration.debug("About to read: \(itemServerUrl, privacy: .public)")
-
- FileProviderEnumerator.readServerUrl(
- itemServerUrl, ncAccount: ncAccount, ncKit: ncKit, stopAtMatchingEtags: scanChangesOnly
- ) { metadatas, newMetadatas, updatedMetadatas, deletedMetadatas, readError in
-
- if readError != nil {
- let nkReadError = NKError(error: readError!)
-
- // Is the error is that we have found matching etags on this item, then ignore it
- // if we are doing a full rescan
- guard nkReadError.isNoChangesError, scanChangesOnly else {
- Logger.enumeration.error(
- "Finishing enumeration of changes at \(itemServerUrl, privacy: .public) with \(readError!.localizedDescription, privacy: .public)"
- )
-
- if nkReadError.isNotFoundError {
- Logger.enumeration.info(
- "404 error means item no longer exists. Deleting metadata and reporting as deletion without error"
- )
-
- if let deletedMetadatas =
- dbManager.deleteDirectoryAndSubdirectoriesMetadata(
- ocId: directoryMetadata.ocId)
- {
- allDeletedMetadatas += deletedMetadatas
- } else {
- Logger.enumeration.error(
- "An error occurred while trying to delete directory and children not found in recursive scan"
- )
- }
-
- } else if nkReadError.isNoChangesError { // All is well, just no changed etags
- Logger.enumeration.info(
- "Error was to say no changed files -- not bad error. No need to check children."
- )
-
- } else if nkReadError.isUnauthenticatedError
- || nkReadError.isCouldntConnectError
- {
- // If it is a critical error then stop, if not then continue
- Logger.enumeration.error(
- "Error will affect next enumerated items, so stopping enumeration.")
- criticalError = nkReadError
- }
-
- dispatchGroup.leave()
- return
- }
- }
-
- Logger.enumeration.info(
- "Finished reading serverUrl: \(itemServerUrl, privacy: .public) for user: \(ncAccount.ncKitAccount, privacy: .public)"
- )
-
- if let metadatas {
- allMetadatas += metadatas
- } else {
- Logger.enumeration.warning(
- "WARNING: Nil metadatas received for reading of changes at \(itemServerUrl, privacy: .public) for user: \(ncAccount.ncKitAccount, privacy: .public)"
- )
- }
-
- if let newMetadatas {
- allNewMetadatas += newMetadatas
- } else {
- Logger.enumeration.warning(
- "WARNING: Nil new metadatas received for reading of changes at \(itemServerUrl, privacy: .public) for user: \(ncAccount.ncKitAccount, privacy: .public)"
- )
- }
-
- if let updatedMetadatas {
- allUpdatedMetadatas += updatedMetadatas
- } else {
- Logger.enumeration.warning(
- "WARNING: Nil updated metadatas received for reading of changes at \(itemServerUrl, privacy: .public) for user: \(ncAccount.ncKitAccount, privacy: .public)"
- )
- }
-
- if let deletedMetadatas {
- allDeletedMetadatas += deletedMetadatas
- } else {
- Logger.enumeration.warning(
- "WARNING: Nil deleted metadatas received for reading of changes at \(itemServerUrl, privacy: .public) for user: \(ncAccount.ncKitAccount, privacy: .public)"
- )
- }
-
- dispatchGroup.leave()
- }
-
- dispatchGroup.wait()
-
- guard criticalError == nil else {
- Logger.enumeration.error(
- "Received critical error stopping further scanning: \(criticalError!.errorDescription, privacy: .public)"
- )
- return ([], [], [], [], error: criticalError)
- }
-
- var childDirectoriesToScan: [ItemMetadata] = []
- var candidateMetadatas: [ItemMetadata]
-
- if scanChangesOnly, fastEnumeration {
- candidateMetadatas = allUpdatedMetadatas
- } else if scanChangesOnly {
- candidateMetadatas = allUpdatedMetadatas + allNewMetadatas
- } else {
- candidateMetadatas = allMetadatas
- }
-
- for candidateMetadata in candidateMetadatas {
- if candidateMetadata.directory {
- childDirectoriesToScan.append(candidateMetadata)
- }
- }
-
- Logger.enumeration.debug("Candidate metadatas for further scan: \(candidateMetadatas, privacy: .public)")
-
- if childDirectoriesToScan.isEmpty {
- return (
- metadatas: allMetadatas, newMetadatas: allNewMetadatas,
- updatedMetadatas: allUpdatedMetadatas, deletedMetadatas: allDeletedMetadatas, nil
- )
- }
-
- for childDirectory in childDirectoriesToScan {
- Logger.enumeration.debug(
- "About to recursively scan: \(childDirectory.urlBase, privacy: .public) with etag: \(childDirectory.etag, privacy: .public)"
- )
- let childScanResult = scanRecursively(
- childDirectory, ncAccount: ncAccount, ncKit: ncKit, scanChangesOnly: scanChangesOnly
- )
-
- allMetadatas += childScanResult.metadatas
- allNewMetadatas += childScanResult.newMetadatas
- allUpdatedMetadatas += childScanResult.updatedMetadatas
- allDeletedMetadatas += childScanResult.deletedMetadatas
- }
-
- return (
- metadatas: allMetadatas, newMetadatas: allNewMetadatas,
- updatedMetadatas: allUpdatedMetadatas,
- deletedMetadatas: allDeletedMetadatas, nil
- )
- }
-
- static func handleDepth1ReadFileOrFolder(
- serverUrl: String,
- ncAccount: Account,
- files: [NKFile],
- error: NKError,
- completionHandler: @escaping (
- _ metadatas: [ItemMetadata]?,
- _ newMetadatas: [ItemMetadata]?,
- _ updatedMetadatas: [ItemMetadata]?,
- _ deletedMetadatas: [ItemMetadata]?,
- _ readError: Error?
- ) -> Void
- ) {
- guard error == .success else {
- Logger.enumeration.error(
- "1 depth readFileOrFolder of url: \(serverUrl, privacy: .public) did not complete successfully, received error: \(error.errorDescription, privacy: .public)"
- )
- completionHandler(nil, nil, nil, nil, error.error)
- return
- }
-
- Logger.enumeration.debug(
- "Starting async conversion of NKFiles for serverUrl: \(serverUrl, privacy: .public) for user: \(ncAccount.ncKitAccount, privacy: .public)"
- )
-
- let dbManager = FilesDatabaseManager.shared
-
- DispatchQueue.global(qos: .userInitiated).async {
- ItemMetadata.metadatasFromDirectoryReadNKFiles(
- files, account: ncAccount.ncKitAccount
- ) { directoryMetadata, _, metadatas in
-
- // STORE DATA FOR CURRENTLY SCANNED DIRECTORY
- // We have now scanned this directory's contents, so update with etag in order to not check again if not needed
- // unless it's the root container
- if serverUrl != ncAccount.davFilesUrl {
- dbManager.addItemMetadata(directoryMetadata)
- }
-
- // Don't update the etags for folders as we haven't checked their contents.
- // When we do a recursive check, if we update the etags now, we will think
- // that our local copies are up to date -- instead, leave them as the old.
- // They will get updated when they are the subject of a readServerUrl call.
- // (See above)
- let changedMetadatas = dbManager.updateItemMetadatas(
- account: ncAccount.ncKitAccount, serverUrl: serverUrl,
- updatedMetadatas: metadatas,
- updateDirectoryEtags: false)
-
- DispatchQueue.main.async {
- completionHandler(
- metadatas, changedMetadatas.newMetadatas, changedMetadatas.updatedMetadatas,
- changedMetadatas.deletedMetadatas, nil)
- }
- }
- }
- }
-
- static func readServerUrl(
- _ serverUrl: String,
- ncAccount: Account,
- ncKit: NextcloudKit,
- stopAtMatchingEtags: Bool = false,
- depth: String = "1",
- completionHandler: @escaping (
- _ metadatas: [ItemMetadata]?,
- _ newMetadatas: [ItemMetadata]?,
- _ updatedMetadatas: [ItemMetadata]?,
- _ deletedMetadatas: [ItemMetadata]?,
- _ readError: Error?
- ) -> Void
- ) {
- let dbManager = FilesDatabaseManager.shared
- let ncKitAccount = ncAccount.ncKitAccount
-
- Logger.enumeration.debug(
- "Starting to read serverUrl: \(serverUrl, privacy: .public) for user: \(ncAccount.ncKitAccount, privacy: .public) at depth \(depth, privacy: .public). NCKit info: userId: \(ncKit.nkCommonInstance.user, privacy: .public), password is empty: \(ncKit.nkCommonInstance.password == "" ? "EMPTY PASSWORD" : "NOT EMPTY PASSWORD"), urlBase: \(ncKit.nkCommonInstance.urlBase, privacy: .public), ncVersion: \(ncKit.nkCommonInstance.nextcloudVersion, privacy: .public)"
- )
-
- ncKit.readFileOrFolder(serverUrlFileName: serverUrl, depth: depth, showHiddenFiles: true) {
- _, files, _, error in
- guard error == .success else {
- Logger.enumeration.error(
- "\(depth, privacy: .public) depth readFileOrFolder of url: \(serverUrl, privacy: .public) did not complete successfully, received error: \(error.errorDescription, privacy: .public)"
- )
- completionHandler(nil, nil, nil, nil, error.error)
- return
- }
-
- guard let receivedFile = files.first else {
- Logger.enumeration.error(
- "Received no items from readFileOrFolder of \(serverUrl, privacy: .public), not much we can do..."
- )
- completionHandler(nil, nil, nil, nil, error.error)
- return
- }
-
- guard receivedFile.directory else {
- Logger.enumeration.debug(
- "Read item is a file. Converting NKfile for serverUrl: \(serverUrl, privacy: .public) for user: \(ncAccount.ncKitAccount, privacy: .public)"
- )
- let itemMetadata = ItemMetadata.fromNKFile(
- receivedFile, account: ncKitAccount)
- dbManager.addItemMetadata(itemMetadata) // TODO: Return some value when it is an update
- completionHandler([itemMetadata], nil, nil, nil, error.error)
- return
- }
-
- if stopAtMatchingEtags,
- let directoryMetadata = dbManager.directoryMetadata(
- account: ncKitAccount, serverUrl: serverUrl)
- {
- let directoryEtag = directoryMetadata.etag
-
- guard directoryEtag == "" || directoryEtag != receivedFile.etag else {
- Logger.enumeration.debug(
- "Read server url called with flag to stop enumerating at matching etags. Returning and providing soft error."
- )
-
- let description =
- "Fetched directory etag is same as that stored locally. Not fetching child items."
- let nkError = NKError(
- errorCode: NKError.noChangesErrorCode, errorDescription: description)
-
- let metadatas = dbManager.itemMetadatas(
- account: ncKitAccount, serverUrl: serverUrl)
-
- completionHandler(metadatas, nil, nil, nil, nkError.error)
- return
- }
- }
-
- if depth == "0" {
- if serverUrl != ncAccount.davFilesUrl {
- let metadata = ItemMetadata.fromNKFile(
- receivedFile, account: ncKitAccount)
- let isNew = dbManager.itemMetadataFromOcId(metadata.ocId) == nil
- let updatedMetadatas = isNew ? [] : [metadata]
- let newMetadatas = isNew ? [metadata] : []
-
- dbManager.addItemMetadata(metadata)
-
- DispatchQueue.main.async {
- completionHandler([metadata], newMetadatas, updatedMetadatas, nil, nil)
- }
- }
- } else {
- handleDepth1ReadFileOrFolder(
- serverUrl: serverUrl, ncAccount: ncAccount, files: files, error: error,
- completionHandler: completionHandler)
- }
- }
- }
-}
+++ /dev/null
-/*
- * Copyright (C) 2022 by Claudio Cambra <claudio.cambra@nextcloud.com>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
- * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
- * for more details.
- */
-
-import FileProvider
-import NextcloudKit
-import NextcloudFileProviderKit
-import OSLog
-
-class FileProviderEnumerator: NSObject, NSFileProviderEnumerator {
- private let enumeratedItemIdentifier: NSFileProviderItemIdentifier
- private var enumeratedItemMetadata: ItemMetadata?
- private var enumeratingSystemIdentifier: Bool {
- FileProviderEnumerator.isSystemIdentifier(enumeratedItemIdentifier)
- }
-
- // TODO: actually use this in NCKit and server requests
- private let anchor = NSFileProviderSyncAnchor(Date().description.data(using: .utf8)!)
- private static let maxItemsPerFileProviderPage = 100
- let ncAccount: Account
- let ncKit: NextcloudKit
- let fastEnumeration: Bool
- var serverUrl: String = ""
- var isInvalidated = false
-
- private static func isSystemIdentifier(_ identifier: NSFileProviderItemIdentifier) -> Bool {
- identifier == .rootContainer || identifier == .trashContainer || identifier == .workingSet
- }
-
- init(
- enumeratedItemIdentifier: NSFileProviderItemIdentifier,
- ncAccount: Account,
- ncKit: NextcloudKit,
- fastEnumeration: Bool = true
- ) {
- self.enumeratedItemIdentifier = enumeratedItemIdentifier
- self.ncAccount = ncAccount
- self.ncKit = ncKit
- self.fastEnumeration = fastEnumeration
-
- if FileProviderEnumerator.isSystemIdentifier(enumeratedItemIdentifier) {
- Logger.enumeration.debug(
- "Providing enumerator for a system defined container: \(enumeratedItemIdentifier.rawValue, privacy: .public)"
- )
- serverUrl = ncAccount.davFilesUrl
- } else {
- Logger.enumeration.debug(
- "Providing enumerator for item with identifier: \(enumeratedItemIdentifier.rawValue, privacy: .public)"
- )
- let dbManager = FilesDatabaseManager.shared
-
- enumeratedItemMetadata = dbManager.itemMetadataFromFileProviderItemIdentifier(
- enumeratedItemIdentifier)
- if enumeratedItemMetadata != nil {
- serverUrl =
- enumeratedItemMetadata!.serverUrl + "/" + enumeratedItemMetadata!.fileName
- } else {
- Logger.enumeration.error(
- "Could not find itemMetadata for file with identifier: \(enumeratedItemIdentifier.rawValue, privacy: .public)"
- )
- }
- }
-
- Logger.enumeration.info(
- "Set up enumerator for user: \(self.ncAccount.ncKitAccount, privacy: .public) with serverUrl: \(self.serverUrl, privacy: .public)"
- )
- super.init()
- }
-
- func invalidate() {
- Logger.enumeration.debug(
- "Enumerator is being invalidated for item with identifier: \(self.enumeratedItemIdentifier.rawValue, privacy: .public)"
- )
- isInvalidated = true
- }
-
- // MARK: - Protocol methods
-
- func enumerateItems(
- for observer: NSFileProviderEnumerationObserver, startingAt page: NSFileProviderPage
- ) {
- Logger.enumeration.debug(
- "Received enumerate items request for enumerator with user: \(self.ncAccount.ncKitAccount, privacy: .public) with serverUrl: \(self.serverUrl, privacy: .public)"
- )
- /*
- - inspect the page to determine whether this is an initial or a follow-up request (TODO)
-
- If this is an enumerator for a directory, the root container or all directories:
- - perform a server request to fetch directory contents
- If this is an enumerator for the working set:
- - perform a server request to update your local database
- - fetch the working set from your local database
-
- - inform the observer about the items returned by the server (possibly multiple times)
- - inform the observer that you are finished with this page
- */
-
- if enumeratedItemIdentifier == .trashContainer {
- Logger.enumeration.debug(
- "Enumerating trash set for user: \(self.ncAccount.ncKitAccount, privacy: .public) with serverUrl: \(self.serverUrl, privacy: .public)"
- )
- // TODO!
-
- observer.finishEnumerating(upTo: nil)
- return
- }
-
- // Handle the working set as if it were the root container
- // If we do a full server scan per the recommendations of the File Provider documentation,
- // we will be stuck for a huge period of time without being able to access files as the
- // entire server gets scanned. Instead, treat the working set as the root container here.
- // Then, when we enumerate changes, we'll go through everything -- while we can still
- // navigate a little bit in Finder, file picker, etc
-
- guard serverUrl != "" else {
- Logger.enumeration.error(
- "Enumerator has empty serverUrl -- can't enumerate that! For identifier: \(self.enumeratedItemIdentifier.rawValue, privacy: .public)"
- )
- observer.finishEnumeratingWithError(NSFileProviderError(.noSuchItem))
- return
- }
-
- // TODO: Make better use of pagination and handle paging properly
- if page == NSFileProviderPage.initialPageSortedByDate as NSFileProviderPage
- || page == NSFileProviderPage.initialPageSortedByName as NSFileProviderPage
- {
- Logger.enumeration.debug(
- "Enumerating initial page for user: \(self.ncAccount.ncKitAccount, privacy: .public) with serverUrl: \(self.serverUrl, privacy: .public)"
- )
-
- FileProviderEnumerator.readServerUrl(serverUrl, ncAccount: ncAccount, ncKit: ncKit) {
- metadatas, _, _, _, readError in
-
- guard readError == nil else {
- Logger.enumeration.error(
- "Finishing enumeration for user: \(self.ncAccount.ncKitAccount, privacy: .public) with serverUrl: \(self.serverUrl, privacy: .public) with error \(readError!.localizedDescription, privacy: .public)"
- )
-
- let nkReadError = NKError(error: readError!)
- observer.finishEnumeratingWithError(nkReadError.fileProviderError)
- return
- }
-
- guard let metadatas else {
- Logger.enumeration.error(
- "Finishing enumeration for user: \(self.ncAccount.ncKitAccount, privacy: .public) with serverUrl: \(self.serverUrl, privacy: .public) with invalid metadatas."
- )
- observer.finishEnumeratingWithError(NSFileProviderError(.cannotSynchronize))
- return
- }
-
- Logger.enumeration.info(
- "Finished reading serverUrl: \(self.serverUrl, privacy: .public) for user: \(self.ncAccount.ncKitAccount, privacy: .public). Processed \(metadatas.count) metadatas"
- )
-
- FileProviderEnumerator.completeEnumerationObserver(
- observer, ncKit: self.ncKit, numPage: 1, itemMetadatas: metadatas)
- }
-
- return
- }
-
- let numPage = Int(String(data: page.rawValue, encoding: .utf8)!)!
- Logger.enumeration.debug(
- "Enumerating page \(numPage, privacy: .public) for user: \(self.ncAccount.ncKitAccount, privacy: .public) with serverUrl: \(self.serverUrl, privacy: .public)"
- )
- // TODO: Handle paging properly
- // FileProviderEnumerator.completeObserver(observer, ncKit: ncKit, numPage: numPage, itemMetadatas: nil)
- observer.finishEnumerating(upTo: nil)
- }
-
- func enumerateChanges(
- for observer: NSFileProviderChangeObserver, from anchor: NSFileProviderSyncAnchor
- ) {
- Logger.enumeration.debug(
- "Received enumerate changes request for enumerator for user: \(self.ncAccount.ncKitAccount, privacy: .public) with serverUrl: \(self.serverUrl, privacy: .public)"
- )
- /*
- - query the server for updates since the passed-in sync anchor (TODO)
-
- If this is an enumerator for the working set:
- - note the changes in your local database
-
- - inform the observer about item deletions and updates (modifications + insertions)
- - inform the observer when you have finished enumerating up to a subsequent sync anchor
- */
-
- if enumeratedItemIdentifier == .workingSet {
- Logger.enumeration.debug(
- "Enumerating changes in working set for user: \(self.ncAccount.ncKitAccount, privacy: .public)"
- )
-
- // Unlike when enumerating items we can't progressively enumerate items as we need to wait to resolve which items are truly deleted and which
- // have just been moved elsewhere.
- fullRecursiveScan(
- ncAccount: ncAccount,
- ncKit: ncKit,
- scanChangesOnly: true
- ) { _, newMetadatas, updatedMetadatas, deletedMetadatas, error in
-
- if self.isInvalidated {
- Logger.enumeration.info(
- "Enumerator invalidated during working set change scan. For user: \(self.ncAccount.ncKitAccount, privacy: .public)"
- )
- observer.finishEnumeratingWithError(NSFileProviderError(.cannotSynchronize))
- return
- }
-
- guard error == nil else {
- Logger.enumeration.info(
- "Finished recursive change enumeration of working set for user: \(self.ncAccount.ncKitAccount, privacy: .public) with error: \(error!.errorDescription, privacy: .public)"
- )
- observer.finishEnumeratingWithError(error!.fileProviderError)
- return
- }
-
- Logger.enumeration.info(
- "Finished recursive change enumeration of working set for user: \(self.ncAccount.ncKitAccount, privacy: .public). Enumerating items."
- )
-
- FileProviderEnumerator.completeChangesObserver(
- observer,
- anchor: anchor,
- ncKit: self.ncKit,
- newMetadatas: newMetadatas,
- updatedMetadatas: updatedMetadatas,
- deletedMetadatas: deletedMetadatas)
- }
- return
- } else if enumeratedItemIdentifier == .trashContainer {
- Logger.enumeration.debug(
- "Enumerating changes in trash set for user: \(self.ncAccount.ncKitAccount, privacy: .public)"
- )
- // TODO!
-
- observer.finishEnumeratingChanges(upTo: anchor, moreComing: false)
- return
- }
-
- Logger.enumeration.info(
- "Enumerating changes for user: \(self.ncAccount.ncKitAccount, privacy: .public) with serverUrl: \(self.serverUrl, privacy: .public)"
- )
-
- // No matter what happens here we finish enumeration in some way, either from the error
- // handling below or from the completeChangesObserver
- // TODO: Move to the sync engine extension
- FileProviderEnumerator.readServerUrl(
- serverUrl, ncAccount: ncAccount, ncKit: ncKit, stopAtMatchingEtags: true
- ) { _, newMetadatas, updatedMetadatas, deletedMetadatas, readError in
-
- // If we get a 404 we might add more deleted metadatas
- var currentDeletedMetadatas: [ItemMetadata] = []
- if let notNilDeletedMetadatas = deletedMetadatas {
- currentDeletedMetadatas = notNilDeletedMetadatas
- }
-
- guard readError == nil else {
- Logger.enumeration.error(
- "Finishing enumeration of changes for user: \(self.ncAccount.ncKitAccount, privacy: .public) with serverUrl: \(self.serverUrl, privacy: .public) with error: \(readError!.localizedDescription, privacy: .public)"
- )
-
- let nkReadError = NKError(error: readError!)
- let fpError = nkReadError.fileProviderError
-
- if nkReadError.isNotFoundError {
- Logger.enumeration.info(
- "404 error means item no longer exists. Deleting metadata and reporting \(self.serverUrl, privacy: .public) as deletion without error"
- )
-
- guard let itemMetadata = self.enumeratedItemMetadata else {
- Logger.enumeration.error(
- "Invalid enumeratedItemMetadata, could not delete metadata nor report deletion"
- )
- observer.finishEnumeratingWithError(fpError)
- return
- }
-
- let dbManager = FilesDatabaseManager.shared
- if itemMetadata.directory {
- if let deletedDirectoryMetadatas =
- dbManager.deleteDirectoryAndSubdirectoriesMetadata(
- ocId: itemMetadata.ocId)
- {
- currentDeletedMetadatas += deletedDirectoryMetadatas
- } else {
- Logger.enumeration.error(
- "Something went wrong when recursively deleting directory not found."
- )
- }
- } else {
- dbManager.deleteItemMetadata(ocId: itemMetadata.ocId)
- }
-
- FileProviderEnumerator.completeChangesObserver(
- observer, anchor: anchor, ncKit: self.ncKit, newMetadatas: nil,
- updatedMetadatas: nil,
- deletedMetadatas: [itemMetadata])
- return
- } else if nkReadError.isNoChangesError { // All is well, just no changed etags
- Logger.enumeration.info(
- "Error was to say no changed files -- not bad error. Finishing change enumeration."
- )
- observer.finishEnumeratingChanges(upTo: anchor, moreComing: false)
- return
- }
-
- observer.finishEnumeratingWithError(fpError)
- return
- }
-
- Logger.enumeration.info(
- "Finished reading serverUrl: \(self.serverUrl, privacy: .public) for user: \(self.ncAccount.ncKitAccount, privacy: .public)"
- )
-
- FileProviderEnumerator.completeChangesObserver(
- observer,
- anchor: anchor,
- ncKit: self.ncKit,
- newMetadatas: newMetadatas,
- updatedMetadatas: updatedMetadatas,
- deletedMetadatas: deletedMetadatas)
- }
- }
-
- func currentSyncAnchor(completionHandler: @escaping (NSFileProviderSyncAnchor?) -> Void) {
- completionHandler(anchor)
- }
-
- // MARK: - Helper methods
-
- private static func metadatasToFileProviderItems(
- _ itemMetadatas: [ItemMetadata], ncKit: NextcloudKit,
- completionHandler: @escaping (_ items: [NSFileProviderItem]) -> Void
- ) {
- var items: [NSFileProviderItem] = []
-
- let conversionQueue = DispatchQueue(
- label: "metadataToItemConversionQueue", qos: .userInitiated, attributes: .concurrent)
- let appendQueue = DispatchQueue(label: "enumeratorItemAppendQueue", qos: .userInitiated) // Serial queue
- let dispatchGroup = DispatchGroup()
-
- for itemMetadata in itemMetadatas {
- conversionQueue.async(group: dispatchGroup) {
- if itemMetadata.e2eEncrypted {
- Logger.enumeration.info(
- "Skipping encrypted metadata in enumeration: \(itemMetadata.ocId, privacy: .public) \(itemMetadata.fileName, privacy: .public)"
- )
- return
- }
-
- if let parentItemIdentifier = FilesDatabaseManager.shared
- .parentItemIdentifierFromMetadata(itemMetadata)
- {
- let item = FileProviderItem(
- metadata: itemMetadata, parentItemIdentifier: parentItemIdentifier,
- ncKit: ncKit)
- Logger.enumeration.debug(
- "Will enumerate item with ocId: \(itemMetadata.ocId, privacy: .public) and name: \(itemMetadata.fileName, privacy: .public)"
- )
-
- appendQueue.async(group: dispatchGroup) {
- items.append(item)
- }
- } else {
- Logger.enumeration.error(
- "Could not get valid parentItemIdentifier for item with ocId: \(itemMetadata.ocId, privacy: .public) and name: \(itemMetadata.fileName, privacy: .public), skipping enumeration"
- )
- }
- }
- }
-
- dispatchGroup.notify(queue: DispatchQueue.main) {
- completionHandler(items)
- }
- }
-
- private static func fileProviderPageforNumPage(_ numPage: Int) -> NSFileProviderPage {
- NSFileProviderPage("\(numPage)".data(using: .utf8)!)
- }
-
- private static func completeEnumerationObserver(
- _ observer: NSFileProviderEnumerationObserver, ncKit: NextcloudKit, numPage: Int,
- itemMetadatas: [ItemMetadata]
- ) {
- metadatasToFileProviderItems(itemMetadatas, ncKit: ncKit) { items in
- observer.didEnumerate(items)
- Logger.enumeration.info("Did enumerate \(items.count) items")
-
- // TODO: Handle paging properly
- /*
- if items.count == maxItemsPerFileProviderPage {
- let nextPage = numPage + 1
- let providerPage = NSFileProviderPage("\(nextPage)".data(using: .utf8)!)
- observer.finishEnumerating(upTo: providerPage)
- } else {
- observer.finishEnumerating(upTo: nil)
- }
- */
- observer.finishEnumerating(upTo: fileProviderPageforNumPage(numPage))
- }
- }
-
- private static func completeChangesObserver(
- _ observer: NSFileProviderChangeObserver, anchor: NSFileProviderSyncAnchor,
- ncKit: NextcloudKit,
- newMetadatas: [ItemMetadata]?,
- updatedMetadatas: [ItemMetadata]?,
- deletedMetadatas: [ItemMetadata]?
- ) {
- guard newMetadatas != nil || updatedMetadatas != nil || deletedMetadatas != nil else {
- Logger.enumeration.error(
- "Received invalid newMetadatas, updatedMetadatas or deletedMetadatas. Finished enumeration of changes with error."
- )
- observer.finishEnumeratingWithError(NSFileProviderError(.noSuchItem))
- return
- }
-
- // Observer does not care about new vs updated, so join
- var allUpdatedMetadatas: [ItemMetadata] = []
- var allDeletedMetadatas: [ItemMetadata] = []
-
- if let newMetadatas {
- allUpdatedMetadatas += newMetadatas
- }
-
- if let updatedMetadatas {
- allUpdatedMetadatas += updatedMetadatas
- }
-
- if let deletedMetadatas {
- allDeletedMetadatas = deletedMetadatas
- }
-
- let allFpItemDeletionsIdentifiers = Array(
- allDeletedMetadatas.map { NSFileProviderItemIdentifier($0.ocId) })
- if !allFpItemDeletionsIdentifiers.isEmpty {
- observer.didDeleteItems(withIdentifiers: allFpItemDeletionsIdentifiers)
- }
-
- metadatasToFileProviderItems(allUpdatedMetadatas, ncKit: ncKit) { updatedItems in
-
- if !updatedItems.isEmpty {
- observer.didUpdate(updatedItems)
- }
-
- Logger.enumeration.info(
- "Processed \(updatedItems.count) new or updated metadatas, \(allDeletedMetadatas.count) deleted metadatas."
- )
- observer.finishEnumeratingChanges(upTo: anchor, moreComing: false)
- }
- }
-}
throw NSFileProviderError(.notAuthenticated)
}
- return FileProviderEnumerator(
+ return Enumerator(
enumeratedItemIdentifier: containerItemIdentifier,
ncAccount: ncAccount,
ncKit: ncKit,
538E396A27F4765000FA63D5 /* UniformTypeIdentifiers.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 538E396927F4765000FA63D5 /* UniformTypeIdentifiers.framework */; };
538E396D27F4765000FA63D5 /* FileProviderExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 538E396C27F4765000FA63D5 /* FileProviderExtension.swift */; };
538E396F27F4765000FA63D5 /* FileProviderItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 538E396E27F4765000FA63D5 /* FileProviderItem.swift */; };
- 538E397127F4765000FA63D5 /* FileProviderEnumerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 538E397027F4765000FA63D5 /* FileProviderEnumerator.swift */; };
538E397627F4765000FA63D5 /* FileProviderExt.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 538E396727F4765000FA63D5 /* FileProviderExt.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
53903D1E2956164F00D0B308 /* NCDesktopClientSocketKit.h in Headers */ = {isa = PBXBuildFile; fileRef = 53903D0E2956164F00D0B308 /* NCDesktopClientSocketKit.h */; settings = {ATTRIBUTES = (Public, ); }; };
53903D212956164F00D0B308 /* NCDesktopClientSocketKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 53903D0C2956164F00D0B308 /* NCDesktopClientSocketKit.framework */; };
53B979812B84C81F002DA742 /* DocumentActionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53B979802B84C81F002DA742 /* DocumentActionViewController.swift */; };
53C331B22BCD28C30093D38B /* NextcloudFileProviderKit in Frameworks */ = {isa = PBXBuildFile; productRef = 53C331B12BCD28C30093D38B /* NextcloudFileProviderKit */; };
53D666612B70C9A70042C03D /* FileProviderConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53D666602B70C9A70042C03D /* FileProviderConfig.swift */; };
- 53ED472029C5E64200795DB1 /* FileProviderEnumerator+SyncEngine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53ED471F29C5E64200795DB1 /* FileProviderEnumerator+SyncEngine.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 */; };
538E396927F4765000FA63D5 /* UniformTypeIdentifiers.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UniformTypeIdentifiers.framework; path = System/Library/Frameworks/UniformTypeIdentifiers.framework; sourceTree = SDKROOT; };
538E396C27F4765000FA63D5 /* FileProviderExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileProviderExtension.swift; sourceTree = "<group>"; };
538E396E27F4765000FA63D5 /* FileProviderItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileProviderItem.swift; sourceTree = "<group>"; };
- 538E397027F4765000FA63D5 /* FileProviderEnumerator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileProviderEnumerator.swift; sourceTree = "<group>"; };
538E397227F4765000FA63D5 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
538E397327F4765000FA63D5 /* FileProviderExt.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = FileProviderExt.entitlements; sourceTree = "<group>"; };
53903D0C2956164F00D0B308 /* NCDesktopClientSocketKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = NCDesktopClientSocketKit.framework; sourceTree = BUILT_PRODUCTS_DIR; };
53B979802B84C81F002DA742 /* DocumentActionViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DocumentActionViewController.swift; sourceTree = "<group>"; };
53B979852B84C81F002DA742 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
53D666602B70C9A70042C03D /* FileProviderConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileProviderConfig.swift; sourceTree = "<group>"; };
- 53ED471F29C5E64200795DB1 /* FileProviderEnumerator+SyncEngine.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "FileProviderEnumerator+SyncEngine.swift"; sourceTree = "<group>"; };
53ED472F29C9CE0B00795DB1 /* FileProviderExtension+ClientInterface.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "FileProviderExtension+ClientInterface.swift"; sourceTree = "<group>"; };
53FE144F2B8E0658006C4193 /* ShareTableViewDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareTableViewDataSource.swift; sourceTree = "<group>"; };
53FE14572B8E3A7C006C4193 /* FileProviderUIExtRelease.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = FileProviderUIExtRelease.entitlements; sourceTree = "<group>"; };
5352E85929B7BFB4002CE85C /* Extensions */,
5350E4C72B0C368B00F276CB /* Services */,
53D666602B70C9A70042C03D /* FileProviderConfig.swift */,
- 538E397027F4765000FA63D5 /* FileProviderEnumerator.swift */,
- 53ED471F29C5E64200795DB1 /* FileProviderEnumerator+SyncEngine.swift */,
538E396C27F4765000FA63D5 /* FileProviderExtension.swift */,
53ED472F29C9CE0B00795DB1 /* FileProviderExtension+ClientInterface.swift */,
5352B36B29DC44B50011CE03 /* FileProviderExtension+Thumbnailing.swift */,
53ED473029C9CE0B00795DB1 /* FileProviderExtension+ClientInterface.swift in Sources */,
538E396D27F4765000FA63D5 /* FileProviderExtension.swift in Sources */,
536EFBF7295CF58100F4CB13 /* FileProviderSocketLineProcessor.swift in Sources */,
- 53ED472029C5E64200795DB1 /* FileProviderEnumerator+SyncEngine.swift in Sources */,
5318AD9929BF58D000CBB71C /* NKError+Extensions.swift in Sources */,
537630972B860D920026BFAB /* FPUIExtensionService.swift in Sources */,
535AE30E29C0A2CC0042A9BA /* Logger+Extensions.swift in Sources */,
5350E4E92B0C534A00F276CB /* ClientCommunicationService.swift in Sources */,
5318AD9729BF493600CBB71C /* FileProviderMaterialisedEnumerationObserver.swift in Sources */,
5352B36C29DC44B50011CE03 /* FileProviderExtension+Thumbnailing.swift in Sources */,
- 538E397127F4765000FA63D5 /* FileProviderEnumerator.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};