Fix renaming directories remotely fixing local sync state in file provider
authorClaudio Cambra <claudio.cambra@nextcloud.com>
Sat, 18 Mar 2023 03:41:47 +0000 (04:41 +0100)
committerClaudio Cambra <claudio.cambra@nextcloud.com>
Fri, 12 May 2023 05:29:55 +0000 (13:29 +0800)
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/Database/NextcloudFilesDatabaseManager.swift
shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/FileProviderEnumerator.swift

index 438abe0025aaf2305eafd8ed17b75dc5ebae70e5..edf402dd293c77c2952260367d040125a8ad75e9 100644 (file)
@@ -114,12 +114,9 @@ class NextcloudFilesDatabaseManager : NSObject {
         return itemMetadataFromOcId(ocId)
     }
 
-    private func processItemMetadatasToDelete(databaseToWriteTo: Realm,
-                                              existingMetadatas: Results<NextcloudItemMetadataTable>,
+    private func processItemMetadatasToDelete(existingMetadatas: Results<NextcloudItemMetadataTable>,
                                               updatedMetadatas: [NextcloudItemMetadataTable]) -> [NextcloudItemMetadataTable] {
 
-        assert(databaseToWriteTo.isInWriteTransaction)
-
         var deletedMetadatas: [NextcloudItemMetadataTable] = []
 
         for existingMetadata in existingMetadatas {
@@ -129,23 +126,18 @@ class NextcloudFilesDatabaseManager : NSObject {
             deletedMetadatas.append(metadataToDelete)
 
             Logger.ncFilesDatabase.debug("Deleting item metadata during update. ocID: \(existingMetadata.ocId, privacy: .public), etag: \(existingMetadata.etag, privacy: .public), fileName: \(existingMetadata.fileName, privacy: OSLogPrivacy.auto(mask: .hash))")
-
-            // Can't pass copies, we need the originals from the database
-            databaseToWriteTo.delete(ncDatabase().objects(NextcloudItemMetadataTable.self).filter("ocId == %@", metadataToDelete.ocId))
         }
 
         return deletedMetadatas
     }
 
-    private func processItemMetadatasToUpdate(databaseToWriteTo: Realm,
-                                              existingMetadatas: Results<NextcloudItemMetadataTable>,
+    private func processItemMetadatasToUpdate(existingMetadatas: Results<NextcloudItemMetadataTable>,
                                               updatedMetadatas: [NextcloudItemMetadataTable],
-                                              updateDirectoryEtags: Bool) -> (newMetadatas: [NextcloudItemMetadataTable], updatedMetadatas: [NextcloudItemMetadataTable]) {
-
-        assert(databaseToWriteTo.isInWriteTransaction)
+                                              updateDirectoryEtags: Bool) -> (newMetadatas: [NextcloudItemMetadataTable], updatedMetadatas: [NextcloudItemMetadataTable], directoriesNeedingRename: [NextcloudItemMetadataTable]) {
 
         var returningNewMetadatas: [NextcloudItemMetadataTable] = []
         var returningUpdatedMetadatas: [NextcloudItemMetadataTable] = []
+        var directoriesNeedingRename: [NextcloudItemMetadataTable] = []
 
         for updatedMetadata in updatedMetadatas {
             if let existingMetadata = existingMetadatas.first(where: { $0.ocId == updatedMetadata.ocId }) {
@@ -153,12 +145,20 @@ class NextcloudFilesDatabaseManager : NSObject {
                 if existingMetadata.status == NextcloudItemMetadataTable.Status.normal.rawValue &&
                     !existingMetadata.isInSameDatabaseStoreableRemoteState(updatedMetadata) {
 
-                    if !updateDirectoryEtags {
-                        updatedMetadata.etag = existingMetadata.etag
+                    if updatedMetadata.directory {
+
+                        if updatedMetadata.serverUrl != existingMetadata.serverUrl || updatedMetadata.fileName != existingMetadata.fileName {
+
+                            directoriesNeedingRename.append(NextcloudItemMetadataTable(value: updatedMetadata))
+                            updatedMetadata.etag = "" // Renaming doesn't change the etag so reset manually
+
+                        } else if !updateDirectoryEtags {
+                            updatedMetadata.etag = existingMetadata.etag
+                        }
                     }
 
-                    returningUpdatedMetadatas.append(NextcloudItemMetadataTable(value: updatedMetadata))
-                    databaseToWriteTo.add(updatedMetadata, update: .all)
+                    returningUpdatedMetadatas.append(updatedMetadata)
+
 
                     Logger.ncFilesDatabase.debug("Updated existing item metadata. ocID: \(updatedMetadata.ocId, privacy: .public), etag: \(updatedMetadata.etag, privacy: .public), fileName: \(updatedMetadata.fileName, privacy: OSLogPrivacy.auto(mask: .hash))")
                 } else {
@@ -166,34 +166,54 @@ class NextcloudFilesDatabaseManager : NSObject {
                 }
 
             } else { // This is a new metadata
-                returningNewMetadatas.append(NextcloudItemMetadataTable(value: updatedMetadata))
-                databaseToWriteTo.add(updatedMetadata, update: .all)
+                returningNewMetadatas.append(updatedMetadata)
 
                 Logger.ncFilesDatabase.debug("Created new item metadata during update. ocID: \(updatedMetadata.ocId, privacy: .public), etag: \(updatedMetadata.etag, privacy: .public), fileName: \(updatedMetadata.fileName, privacy: OSLogPrivacy.auto(mask: .hash))")
             }
         }
 
-        return (returningNewMetadatas, returningUpdatedMetadatas)
+        return (returningNewMetadatas, returningUpdatedMetadatas, directoriesNeedingRename)
     }
 
     func updateItemMetadatas(account: String, serverUrl: String, updatedMetadatas: [NextcloudItemMetadataTable], updateDirectoryEtags: Bool, completionHandler: @escaping(_ newMetadatas: [NextcloudItemMetadataTable]?, _ updatedMetadatas: [NextcloudItemMetadataTable]?, _ deletedMetadatas: [NextcloudItemMetadataTable]?) -> Void) {
         let database = ncDatabase()
 
         do {
+            let existingMetadatas = database.objects(NextcloudItemMetadataTable.self).filter("account == %@ AND serverUrl == %@ AND status == %@", account, serverUrl, NextcloudItemMetadataTable.Status.normal.rawValue)
+
+            let metadatasToDelete = processItemMetadatasToDelete(existingMetadatas: existingMetadatas,
+                                                                 updatedMetadatas: updatedMetadatas)
+
+            let metadatasToChange = processItemMetadatasToUpdate(existingMetadatas: existingMetadatas,
+                                                                 updatedMetadatas: updatedMetadatas,
+                                                                 updateDirectoryEtags: updateDirectoryEtags)
+
+            var metadatasToUpdate = metadatasToChange.updatedMetadatas
+            let metadatasToCreate = metadatasToChange.newMetadatas
+            let directoriesNeedingRename = metadatasToChange.directoriesNeedingRename
+
+            let metadatasToAdd = Array(metadatasToUpdate.map { NextcloudItemMetadataTable(value: $0) }) +
+                                 Array(metadatasToCreate.map { NextcloudItemMetadataTable(value: $0) })
+
             try database.write {
-                let existingMetadatas = database.objects(NextcloudItemMetadataTable.self).filter("account == %@ AND serverUrl == %@ AND status == %@", account, serverUrl, NextcloudItemMetadataTable.Status.normal.rawValue)
+                for metadata in metadatasToDelete {
+                    // Can't pass copies, we need the originals from the database
+                    database.delete(ncDatabase().objects(NextcloudItemMetadataTable.self).filter("ocId == %@", metadata.ocId))
+                }
 
-                let deletedMetadatas = processItemMetadatasToDelete(databaseToWriteTo: database,
-                                                                    existingMetadatas: existingMetadatas,
-                                                                    updatedMetadatas: updatedMetadatas)
+                for metadata in metadatasToAdd {
+                    database.add(metadata, update: .all)
+                }
 
-                let metadatasFromUpdate = processItemMetadatasToUpdate(databaseToWriteTo: database,
-                                                                       existingMetadatas: existingMetadatas,
-                                                                       updatedMetadatas: updatedMetadatas,
-                                                                       updateDirectoryEtags: updateDirectoryEtags)
+            }
 
-                completionHandler(metadatasFromUpdate.newMetadatas, metadatasFromUpdate.updatedMetadatas, deletedMetadatas)
+            for metadata in directoriesNeedingRename {
+                if let updatedDirectoryChildren = renameDirectoryAndPropagateToChildren(ocId: metadata.ocId, newServerUrl: metadata.serverUrl, newFileName: metadata.fileName) {
+                    metadatasToUpdate += updatedDirectoryChildren
+                }
             }
+
+            completionHandler(metadatasToCreate, metadatasToUpdate, metadatasToDelete)
         } catch let error {
             Logger.ncFilesDatabase.error("Could not update any item metadatas, received error: \(error.localizedDescription, privacy: .public)")
             completionHandler(nil, nil, nil)
@@ -366,23 +386,22 @@ class NextcloudFilesDatabaseManager : NSObject {
         }
     }
 
-    func renameDirectoryAndPropagateToChildren(ocId: String, newServerUrl: String, newFileName: String) {
+    func renameDirectoryAndPropagateToChildren(ocId: String, newServerUrl: String, newFileName: String) -> [NextcloudItemMetadataTable]? {
 
         let database = ncDatabase()
 
-        do {
-            guard let directoryMetadata = database.objects(NextcloudItemMetadataTable.self).filter("ocId == %@ AND directory == true", ocId).first else {
-                Logger.ncFilesDatabase.error("Could not find a directory with ocID \(ocId, privacy: .public), cannot proceed with recursive renaming")
-                return
-            }
-
-            let oldServerUrl = directoryMetadata.serverUrl + "/" + directoryMetadata.fileName
+        guard let directoryMetadata = database.objects(NextcloudItemMetadataTable.self).filter("ocId == %@ AND directory == true", ocId).first else {
+            Logger.ncFilesDatabase.error("Could not find a directory with ocID \(ocId, privacy: .public), cannot proceed with recursive renaming")
+            return nil
+        }
 
-            let childItemResults = database.objects(NextcloudItemMetadataTable.self).filter("account == %@ AND serverUrl BEGINSWITH %@", directoryMetadata.account, oldServerUrl)
+        let oldServerUrl = directoryMetadata.serverUrl + "/" + directoryMetadata.fileName
+        let childItemResults = database.objects(NextcloudItemMetadataTable.self).filter("account == %@ AND serverUrl BEGINSWITH %@", directoryMetadata.account, oldServerUrl)
 
-            renameItemMetadata(ocId: ocId, newServerUrl: newServerUrl, newFileName: newFileName)
-            Logger.ncFilesDatabase.debug("Renamed root renaming directory")
+        renameItemMetadata(ocId: ocId, newServerUrl: newServerUrl, newFileName: newFileName)
+        Logger.ncFilesDatabase.debug("Renamed root renaming directory")
 
+        do {
             try database.write {
                 for childItem in childItemResults {
                     let oldServerUrl = childItem.serverUrl
@@ -394,7 +413,12 @@ class NextcloudFilesDatabaseManager : NSObject {
             }
         } catch let error {
             Logger.ncFilesDatabase.error("Could not rename directory metadata with ocId: \(ocId, privacy: .public) to new serverUrl: \(newServerUrl), received error: \(error.localizedDescription, privacy: .public)")
+
+            return nil
         }
+
+        let updatedChildItemResults = database.objects(NextcloudItemMetadataTable.self).filter("account == %@ AND serverUrl BEGINSWITH %@", directoryMetadata.account, newServerUrl)
+        return sortedItemMetadatas(updatedChildItemResults)
     }
 
     func localFileMetadataFromOcId(_ ocId: String) -> NextcloudLocalFileMetadataTable? {
index 952c37307ccf0849ef35ecb9b86f1c5d1fe38548..711255dcb5c59a1965e802d885a14ef0e71c6ca7 100644 (file)
@@ -520,12 +520,7 @@ class FileProviderEnumerator: NSObject, NSFileProviderEnumerator {
         var updatedDirectories: [NextcloudItemMetadataTable] = []
         for updatedMetadata in allUpdatedMetadatas {
             if updatedMetadata.directory {
-                guard let directoryMetadata = dbManager.directoryMetadata(ocId: updatedMetadata.ocId) else {
-                    Logger.enumeration.error("Could not find matching directory metadata for updated item metadata, cannot scan for updates")
-                    continue
-                }
-
-                updatedDirectories.append(directoryMetadata)
+                updatedDirectories.append(updatedMetadata)
             }
         }
 
@@ -610,6 +605,11 @@ class FileProviderEnumerator: NSObject, NSFileProviderEnumerator {
                             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)
                         dbManager.updateItemMetadatas(account: ncKitAccount, serverUrl: serverUrl, updatedMetadatas: metadatas, updateDirectoryEtags: false) { newMetadatas, updatedMetadatas, deletedMetadatas in
                             completionHandler(metadatas, newMetadatas, updatedMetadatas, deletedMetadatas, nil)
                         }