From a0f2cbcf3142754e49a07bd3b3fc0afda098aedd Mon Sep 17 00:00:00 2001 From: Claudio Cambra Date: Mon, 15 Apr 2024 18:07:23 +0800 Subject: [PATCH] Use Enumerator in NextcloudFileProviderKit Signed-off-by: Claudio Cambra --- .../Extensions/Logger+Extensions.swift | 1 - .../FileProviderEnumerator+SyncEngine.swift | 406 --------------- .../FileProviderEnumerator.swift | 462 ------------------ .../FileProviderExtension.swift | 2 +- .../project.pbxproj | 8 - 5 files changed, 1 insertion(+), 878 deletions(-) delete mode 100644 shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/FileProviderEnumerator+SyncEngine.swift delete mode 100644 shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/FileProviderEnumerator.swift diff --git a/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/Extensions/Logger+Extensions.swift b/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/Extensions/Logger+Extensions.swift index dcb75ebbd..a9a5dd2e8 100644 --- a/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/Extensions/Logger+Extensions.swift +++ b/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/Extensions/Logger+Extensions.swift @@ -20,7 +20,6 @@ extension Logger { 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") diff --git a/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/FileProviderEnumerator+SyncEngine.swift b/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/FileProviderEnumerator+SyncEngine.swift deleted file mode 100644 index 6046d230a..000000000 --- a/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/FileProviderEnumerator+SyncEngine.swift +++ /dev/null @@ -1,406 +0,0 @@ -/* - * Copyright (C) 2023 by Claudio Cambra - * - * 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) - } - } - } -} diff --git a/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/FileProviderEnumerator.swift b/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/FileProviderEnumerator.swift deleted file mode 100644 index b566a58db..000000000 --- a/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/FileProviderEnumerator.swift +++ /dev/null @@ -1,462 +0,0 @@ -/* - * Copyright (C) 2022 by Claudio Cambra - * - * 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) - } - } -} diff --git a/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/FileProviderExtension.swift b/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/FileProviderExtension.swift index 6a2cfadf4..b6f45e4e6 100644 --- a/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/FileProviderExtension.swift +++ b/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/FileProviderExtension.swift @@ -804,7 +804,7 @@ import OSLog throw NSFileProviderError(.notAuthenticated) } - return FileProviderEnumerator( + return Enumerator( enumeratedItemIdentifier: containerItemIdentifier, ncAccount: ncAccount, ncKit: ncKit, diff --git a/shell_integration/MacOSX/NextcloudIntegration/NextcloudIntegration.xcodeproj/project.pbxproj b/shell_integration/MacOSX/NextcloudIntegration/NextcloudIntegration.xcodeproj/project.pbxproj index 9123dc7d2..effd6dd24 100644 --- a/shell_integration/MacOSX/NextcloudIntegration/NextcloudIntegration.xcodeproj/project.pbxproj +++ b/shell_integration/MacOSX/NextcloudIntegration/NextcloudIntegration.xcodeproj/project.pbxproj @@ -31,7 +31,6 @@ 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 */; }; @@ -47,7 +46,6 @@ 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 */; }; @@ -175,7 +173,6 @@ 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 = ""; }; 538E396E27F4765000FA63D5 /* FileProviderItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileProviderItem.swift; sourceTree = ""; }; - 538E397027F4765000FA63D5 /* FileProviderEnumerator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileProviderEnumerator.swift; sourceTree = ""; }; 538E397227F4765000FA63D5 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 538E397327F4765000FA63D5 /* FileProviderExt.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = FileProviderExt.entitlements; sourceTree = ""; }; 53903D0C2956164F00D0B308 /* NCDesktopClientSocketKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = NCDesktopClientSocketKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -190,7 +187,6 @@ 53B979802B84C81F002DA742 /* DocumentActionViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DocumentActionViewController.swift; sourceTree = ""; }; 53B979852B84C81F002DA742 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 53D666602B70C9A70042C03D /* FileProviderConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileProviderConfig.swift; sourceTree = ""; }; - 53ED471F29C5E64200795DB1 /* FileProviderEnumerator+SyncEngine.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "FileProviderEnumerator+SyncEngine.swift"; sourceTree = ""; }; 53ED472F29C9CE0B00795DB1 /* FileProviderExtension+ClientInterface.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "FileProviderExtension+ClientInterface.swift"; sourceTree = ""; }; 53FE144F2B8E0658006C4193 /* ShareTableViewDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareTableViewDataSource.swift; sourceTree = ""; }; 53FE14572B8E3A7C006C4193 /* FileProviderUIExtRelease.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = FileProviderUIExtRelease.entitlements; sourceTree = ""; }; @@ -307,8 +303,6 @@ 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 */, @@ -689,7 +683,6 @@ 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 */, @@ -698,7 +691,6 @@ 5350E4E92B0C534A00F276CB /* ClientCommunicationService.swift in Sources */, 5318AD9729BF493600CBB71C /* FileProviderMaterialisedEnumerationObserver.swift in Sources */, 5352B36C29DC44B50011CE03 /* FileProviderExtension+Thumbnailing.swift in Sources */, - 538E397127F4765000FA63D5 /* FileProviderEnumerator.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; -- 2.30.2