Fix blocking main thread when enumerating working set items in FileProviderEnumerator
authorClaudio Cambra <claudio.cambra@nextcloud.com>
Wed, 15 Mar 2023 13:03:19 +0000 (14:03 +0100)
committerClaudio Cambra <claudio.cambra@nextcloud.com>
Fri, 12 May 2023 05:28:46 +0000 (13:28 +0800)
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/FileProviderEnumerator.swift

index 58ef390537d628e388f3a1bdd46b73630fe1c8b5..f0dedb317058144f49ce7a0afbc02cf9d0820258 100644 (file)
@@ -95,7 +95,19 @@ class FileProviderEnumerator: NSObject, NSFileProviderEnumerator {
                 var allMetadatas: [NextcloudItemMetadataTable] = []
 
                 var serverError: NKError?
-                let dispatchGroup = DispatchGroup()  // TODO: Maybe own thread?
+
+                // Create a serial dispatch queue
+                let dispatchQueue = DispatchQueue(label: "workingSetItemEnumerationQueue", qos: .userInitiated)
+                let dispatchGroup = DispatchGroup() // To notify when all network tasks are done
+
+                dispatchGroup.notify(queue: DispatchQueue.main) {
+                    guard serverError == nil else {
+                        observer.finishEnumeratingWithError(serverError!.error)
+                        return
+                    }
+
+                    FileProviderEnumerator.completeEnumerationObserver(observer, ncKit: self.ncKit, numPage: 1, itemMetadatas: allMetadatas)
+                }
 
                 for directoryMetadata in directoryMetadatas {
                     guard directoryMetadata.etag != "" else {
@@ -103,40 +115,48 @@ class FileProviderEnumerator: NSObject, NSFileProviderEnumerator {
                         continue;
                     }
 
-                    dispatchGroup.enter()
-
-                    FileProviderEnumerator.readServerUrl(directoryMetadata.serverUrl, ncAccount: ncAccount, ncKit: ncKit) { metadatas, _, _, _, readError in
-                        guard readError == nil else {
-                            Logger.enumeration.error("Finishing enumeration of working set directory \(directoryMetadata.serverUrl, privacy: OSLogPrivacy.auto(mask: .hash)) with error \(readError!, privacy: .public)")
-
-                            let nkError = NKError(error: readError!)
-                            if nkError.isUnauthenticatedError || nkError.isCouldntConnectError {
-                                // If it is a critical error then stop, if not then continue
-                                serverError = nkError
-                            }
+                    dispatchGroup.enter() // Add to outer counter
 
+                    dispatchQueue.async {
+                        guard serverError == nil else {
+                            Logger.enumeration.info("Skipping enumeration of directory for working set: \(directoryMetadata.serverUrl, privacy: OSLogPrivacy.auto(mask: .hash)) as we have an error.")
                             dispatchGroup.leave()
-                            return
+                            return;
                         }
 
-                        if let metadatas = metadatas {
-                            allMetadatas += metadatas
-                        } else {
-                            allMetadatas += dbManager.itemMetadatas(account: self.ncAccount.ncKitAccount, serverUrl: directoryMetadata.serverUrl)
-                        }
+                        let currentNetworkTaskDispatchGroup = DispatchGroup() // To make this serial queue wait until this task is done
+                        currentNetworkTaskDispatchGroup.enter()
 
-                        dispatchGroup.leave()
-                    }
+                        FileProviderEnumerator.readServerUrl(directoryMetadata.serverUrl, ncAccount: self.ncAccount, ncKit: self.ncKit) { metadatas, _, _, _, readError in
+                            guard readError == nil else {
+                                Logger.enumeration.error("Finishing enumeration of working set directory \(directoryMetadata.serverUrl, privacy: OSLogPrivacy.auto(mask: .hash)) with error \(readError!, privacy: .public)")
 
-                    dispatchGroup.wait()
+                                let nkError = NKError(error: readError!)
+                                if nkError.isUnauthenticatedError || nkError.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.")
+                                    serverError = nkError
+                                }
 
-                    guard serverError == nil else {
-                        observer.finishEnumeratingWithError(serverError!.error)
-                        return
+                                currentNetworkTaskDispatchGroup.leave()
+                                return
+                            }
+
+                            if let metadatas = metadatas {
+                                allMetadatas += metadatas
+                            } else {
+                                allMetadatas += dbManager.itemMetadatas(account: self.ncAccount.ncKitAccount, serverUrl: directoryMetadata.serverUrl)
+                            }
+
+                            currentNetworkTaskDispatchGroup.leave()
+                        }
+
+                        currentNetworkTaskDispatchGroup.wait()
+                        dispatchGroup.leave() // Now lower outer counter
                     }
                 }
 
-                FileProviderEnumerator.completeEnumerationObserver(observer, ncKit: self.ncKit, numPage: 1, itemMetadatas: allMetadatas)
+                return
             } else {
                 Logger.enumeration.debug("Enumerating page \(page.rawValue) of working set for user: \(self.ncAccount.ncKitAccount, privacy: OSLogPrivacy.auto(mask: .hash))")
                 // TODO!