Use Enumerator in NextcloudFileProviderKit
authorClaudio Cambra <claudio.cambra@nextcloud.com>
Mon, 15 Apr 2024 10:07:23 +0000 (18:07 +0800)
committerbackportbot[bot] <backportbot[bot]@users.noreply.github.com>
Wed, 17 Apr 2024 08:44:12 +0000 (08:44 +0000)
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/Extensions/Logger+Extensions.swift
shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/FileProviderEnumerator+SyncEngine.swift [deleted file]
shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/FileProviderEnumerator.swift [deleted file]
shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/FileProviderExtension.swift
shell_integration/MacOSX/NextcloudIntegration/NextcloudIntegration.xcodeproj/project.pbxproj

index dcb75ebbdc5dd962214b530905167eec3f4b345b..a9a5dd2e81354091701d5f24f9ab8720b8c7b51f 100644 (file)
@@ -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 (file)
index 6046d23..0000000
+++ /dev/null
@@ -1,406 +0,0 @@
-/*
- * 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)
-            }
-        }
-    }
-}
diff --git a/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/FileProviderEnumerator.swift b/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/FileProviderEnumerator.swift
deleted file mode 100644 (file)
index b566a58..0000000
+++ /dev/null
@@ -1,462 +0,0 @@
-/*
- * 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)
-        }
-    }
-}
index 6a2cfadf40664b6e1af04ecde55f6eeaa0988eff..b6f45e4e68355011a610dcc7112a9743109be0cc 100644 (file)
@@ -804,7 +804,7 @@ import OSLog
             throw NSFileProviderError(.notAuthenticated)
         }
 
-        return FileProviderEnumerator(
+        return Enumerator(
             enumeratedItemIdentifier: containerItemIdentifier,
             ncAccount: ncAccount,
             ncKit: ncKit,
index 9123dc7d2416fdd0f1bcecab7e623eee67c550a0..effd6dd24fd978cbbc77eeaf3c3f2e721e53fd4c 100644 (file)
@@ -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 */; };
                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;
                };