Move file provider database files into own folder
authorClaudio Cambra <claudio.cambra@nextcloud.com>
Mon, 13 Mar 2023 11:30:27 +0000 (12:30 +0100)
committerClaudio Cambra <claudio.cambra@nextcloud.com>
Fri, 12 May 2023 05:21:29 +0000 (13:21 +0800)
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/Database/NextcloudFilesDatabaseManager.swift [new file with mode: 0644]
shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/Database/NextcloudFilesDatabaseTables.swift [new file with mode: 0644]
shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/NextcloudFilesDatabaseManager.swift [deleted file]
shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/NextcloudFilesDatabaseTables.swift [deleted file]

diff --git a/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/Database/NextcloudFilesDatabaseManager.swift b/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/Database/NextcloudFilesDatabaseManager.swift
new file mode 100644 (file)
index 0000000..dac9ec4
--- /dev/null
@@ -0,0 +1,710 @@
+/*
+ * 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 Foundation
+import RealmSwift
+import FileProvider
+import NextcloudKit
+
+class NextcloudFilesDatabaseManager : NSObject {
+    static let shared = {
+        return NextcloudFilesDatabaseManager();
+    }()
+
+    let relativeDatabaseFolderPath = "Database/"
+    let databaseFilename = "fileproviderextdatabase.realm"
+    let relativeDatabaseFilePath: String
+    var databasePath: URL?
+
+    let schemaVersion: UInt64 = 100
+
+    override init() {
+        self.relativeDatabaseFilePath = self.relativeDatabaseFolderPath + self.databaseFilename
+
+        guard let fileProviderDataDirUrl = pathForFileProviderExtData() else {
+            super.init()
+            return
+        }
+
+        self.databasePath = fileProviderDataDirUrl.appendingPathComponent(self.relativeDatabaseFilePath)
+
+        // Disable file protection for directory DB
+        // https://docs.mongodb.com/realm/sdk/ios/examples/configure-and-open-a-realm/#std-label-ios-open-a-local-realm
+        let dbFolder = fileProviderDataDirUrl.appendingPathComponent(self.relativeDatabaseFolderPath)
+        let dbFolderPath = dbFolder.path
+        do {
+            try FileManager.default.createDirectory(at: dbFolder, withIntermediateDirectories: true)
+            try FileManager.default.setAttributes([FileAttributeKey.protectionKey: FileProtectionType.completeUntilFirstUserAuthentication], ofItemAtPath: dbFolderPath)
+        } catch let error {
+            NSLog("Could not set permission level for File Provider database folder, received error: %@", error.localizedDescription)
+        }
+
+        let config = Realm.Configuration(
+            fileURL: self.databasePath,
+            schemaVersion: self.schemaVersion,
+            objectTypes: [NextcloudItemMetadataTable.self, NextcloudDirectoryMetadataTable.self, NextcloudLocalFileMetadataTable.self]
+        )
+
+        Realm.Configuration.defaultConfiguration = config
+
+        do {
+            _ = try Realm()
+            NSLog("Successfully started Realm db for FileProviderExt")
+        } catch let error as NSError {
+            NSLog("Error opening Realm db: %@", error.localizedDescription)
+        }
+
+        super.init()
+    }
+
+    private func ncDatabase() -> Realm {
+        let realm = try! Realm()
+        realm.refresh()
+        return realm
+    }
+
+    func anyItemMetadatasForAccount(_ account: String) -> Bool {
+        return !ncDatabase().objects(NextcloudItemMetadataTable.self).filter("account == %@", account).isEmpty
+    }
+
+    func itemMetadataFromOcId(_ ocId: String) -> NextcloudItemMetadataTable? {
+        // Realm objects are live-fire, i.e. they will be changed and invalidated according to changes in the db
+        // Let's therefore create a copy
+        if let itemMetadata = ncDatabase().objects(NextcloudItemMetadataTable.self).filter("ocId == %@", ocId).first {
+            return NextcloudItemMetadataTable(value: itemMetadata)
+        }
+
+        return nil
+    }
+
+    private func sortedItemMetadatas(_ metadatas: Results<NextcloudItemMetadataTable>) -> [NextcloudItemMetadataTable] {
+        let sortedMetadatas = metadatas.sorted(byKeyPath: "fileName", ascending: true)
+        return Array(sortedMetadatas.map { NextcloudItemMetadataTable(value: $0) })
+    }
+
+    func itemMetadatas(account: String) -> [NextcloudItemMetadataTable] {
+        let metadatas = ncDatabase().objects(NextcloudItemMetadataTable.self).filter("account == %@", account)
+        return sortedItemMetadatas(metadatas)
+    }
+
+    func itemMetadatas(account: String, serverUrl: String) -> [NextcloudItemMetadataTable] {
+        let metadatas = ncDatabase().objects(NextcloudItemMetadataTable.self).filter("account == %@ AND serverUrl == %@", account, serverUrl)
+        return sortedItemMetadatas(metadatas)
+    }
+
+    func itemMetadatas(account: String, serverUrl: String, status: NextcloudItemMetadataTable.Status) -> [NextcloudItemMetadataTable] {
+        let metadatas = ncDatabase().objects(NextcloudItemMetadataTable.self).filter("account == %@ AND serverUrl == %@ AND status == %@", account, serverUrl, status.rawValue)
+        return sortedItemMetadatas(metadatas)
+    }
+
+    func itemMetadataFromFileProviderItemIdentifier(_ identifier: NSFileProviderItemIdentifier) -> NextcloudItemMetadataTable? {
+        let ocId = identifier.rawValue
+        return itemMetadataFromOcId(ocId)
+    }
+
+    private func processItemMetadatasToDelete(databaseToWriteTo: Realm,
+                                              existingMetadatas: Results<NextcloudItemMetadataTable>,
+                                              updatedMetadatas: [NextcloudItemMetadataTable]) -> [NextcloudItemMetadataTable] {
+
+        assert(databaseToWriteTo.isInWriteTransaction)
+
+        var deletedMetadatas: [NextcloudItemMetadataTable] = []
+
+        for existingMetadata in existingMetadatas {
+            guard !updatedMetadatas.contains(where: { $0.ocId == existingMetadata.ocId }),
+                    let metadataToDelete = itemMetadataFromOcId(existingMetadata.ocId) else { continue }
+
+            deletedMetadatas.append(metadataToDelete)
+
+            NSLog("""
+                    Deleting metadata.
+                    ocID: %@,
+                    fileName: %@,
+                    etag: %@
+                  """
+                  , metadataToDelete.ocId, metadataToDelete.fileName, metadataToDelete.etag)
+
+            // 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>,
+                                              updatedMetadatas: [NextcloudItemMetadataTable]) -> (newMetadatas: [NextcloudItemMetadataTable], updatedMetadatas: [NextcloudItemMetadataTable]) {
+
+        assert(databaseToWriteTo.isInWriteTransaction)
+
+        var returningNewMetadatas: [NextcloudItemMetadataTable] = []
+        var returningUpdatedMetadatas: [NextcloudItemMetadataTable] = []
+
+        for updatedMetadata in updatedMetadatas {
+            if let existingMetadata = existingMetadatas.first(where: { $0.ocId == updatedMetadata.ocId }) {
+
+                if existingMetadata.status == NextcloudItemMetadataTable.Status.normal.rawValue &&
+                    !existingMetadata.isInSameDatabaseStoreableRemoteState(updatedMetadata) {
+
+                    returningUpdatedMetadatas.append(NextcloudItemMetadataTable(value: updatedMetadata))
+                    databaseToWriteTo.add(updatedMetadata, update: .all)
+                    
+                    NSLog("""
+                            Updated existing metadata.
+                            ocID: %@,
+                            fileName: %@,
+                            etag: %@
+                          """
+                          , updatedMetadata.ocId, updatedMetadata.fileName, updatedMetadata.etag)
+                } else {
+                    NSLog("""
+                              Skipping metadata update as received metadata status is same as existing,
+                              or metadata is currently being downloaded/uploaded:
+
+                              ocID: %@,
+                              fileName: %@,
+                              etag: %@
+                          """
+                          , updatedMetadata.ocId, updatedMetadata.fileName, updatedMetadata.etag)
+                }
+
+            } else { // This is a new metadata
+                returningNewMetadatas.append(NextcloudItemMetadataTable(value: updatedMetadata))
+                databaseToWriteTo.add(updatedMetadata, update: .all)
+
+                NSLog("""
+                        Created new metadata.
+                        ocID: %@,
+                        fileName: %@,
+                        etag: %@
+                      """
+                      , updatedMetadata.ocId, updatedMetadata.fileName, updatedMetadata.etag)
+            }
+        }
+
+        return (returningNewMetadatas, returningUpdatedMetadatas)
+    }
+
+    func updateItemMetadatas(account: String, serverUrl: String, updatedMetadatas: [NextcloudItemMetadataTable], completionHandler: @escaping(_ newMetadatas: [NextcloudItemMetadataTable]?, _ updatedMetadatas: [NextcloudItemMetadataTable]?, _ deletedMetadatas: [NextcloudItemMetadataTable]?) -> Void) {
+        let database = ncDatabase()
+
+        do {
+            try database.write {
+                let existingMetadatas = database.objects(NextcloudItemMetadataTable.self).filter("account == %@ AND serverUrl == %@ AND status == %@", account, serverUrl, NextcloudItemMetadataTable.Status.normal.rawValue)
+
+                let deletedMetadatas = processItemMetadatasToDelete(databaseToWriteTo: database,
+                                                                    existingMetadatas: existingMetadatas,
+                                                                    updatedMetadatas: updatedMetadatas)
+
+                let metadatasFromUpdate = processItemMetadatasToUpdate(databaseToWriteTo: database,
+                                                                       existingMetadatas: existingMetadatas,
+                                                                       updatedMetadatas: updatedMetadatas)
+
+                completionHandler(metadatasFromUpdate.newMetadatas, metadatasFromUpdate.updatedMetadatas, deletedMetadatas)
+            }
+        } catch let error {
+            NSLog("Could not update any metadatas, received error: %@", error.localizedDescription)
+            completionHandler(nil, nil, nil)
+        }
+    }
+
+    func setStatusForItemMetadata(_ metadata: NextcloudItemMetadataTable, status: NextcloudItemMetadataTable.Status) -> NextcloudItemMetadataTable? {
+        let database = ncDatabase()
+        var result: NextcloudItemMetadataTable?
+
+        do {
+            try database.write {
+                result = database.objects(NextcloudItemMetadataTable.self).filter("ocId == %@", metadata.ocId).first
+                result?.status = status.rawValue
+            }
+        } catch let error {
+            NSLog("Could not update status for item metadata with ocID: %@ and filename: %@, received error: %@", metadata.ocId, metadata.fileNameView, error.localizedDescription)
+        }
+
+        if result != nil {
+            return NextcloudItemMetadataTable(value: result!)
+        }
+
+        return nil
+    }
+
+    func addItemMetadata(_ metadata: NextcloudItemMetadataTable) {
+        let database = ncDatabase()
+
+        do {
+            try database.write {
+                database.add(metadata, update: .all)
+                NSLog("""
+                        Created new metadata (addItemMetadata).
+                        ocID: %@,
+                        fileName: %@,
+                        etag: %@
+                      """
+                      , metadata.ocId, metadata.fileName, metadata.etag)
+            }
+        } catch let error {
+            NSLog("Could not add item metadata with ocID: %@ and filename: %@, received error: %@", metadata.ocId, metadata.fileNameView, error.localizedDescription)
+        }
+    }
+
+    func deleteItemMetadata(ocId: String) {
+        let database = ncDatabase()
+
+        do {
+            try database.write {
+                let results = database.objects(NextcloudItemMetadataTable.self).filter("ocId == %@", ocId)
+                database.delete(results)
+            }
+        } catch let error {
+            NSLog("Could not delete item metadata with ocId: %@, received error: %@", ocId, error.localizedDescription)
+        }
+    }
+
+    func renameItemMetadata(ocId: String, newServerUrl: String, newFileName: String) {
+        let database = ncDatabase()
+
+        do {
+            try database.write {
+                guard let itemMetadata = database.objects(NextcloudItemMetadataTable.self).filter("ocId == %@", ocId).first else {
+                    NSLog("Could not find an item with ocID %@ to rename to %@", ocId, newFileName)
+                    return
+                }
+
+                let oldFileName = itemMetadata.fileName
+                let oldServerUrl = itemMetadata.serverUrl
+
+                itemMetadata.fileName = newFileName
+                itemMetadata.fileNameView = newFileName
+                itemMetadata.serverUrl = newServerUrl
+
+                database.add(itemMetadata, update: .all)
+
+                NSLog("Renamed item %@ to %@, moved from serverUrl: %@ to serverUrl: %@", oldFileName, newFileName, oldServerUrl, newServerUrl)
+            }
+        } catch let error {
+            NSLog("Could not rename filename of item metadata with ocID: %@ to proposed name %@, received error: %@", ocId, newFileName, error.localizedDescription)
+        }
+    }
+
+    func directoryMetadata(account: String, serverUrl: String) -> NextcloudDirectoryMetadataTable? {
+        if let metadata = ncDatabase().objects(NextcloudDirectoryMetadataTable.self).filter("account == %@ AND serverUrl == %@", account, serverUrl).first {
+            return NextcloudDirectoryMetadataTable(value: metadata)
+        }
+
+        return nil
+    }
+
+    func directoryMetadata(ocId: String) -> NextcloudDirectoryMetadataTable? {
+        if let metadata = ncDatabase().objects(NextcloudDirectoryMetadataTable.self).filter("ocId == %@", ocId).first {
+            return NextcloudDirectoryMetadataTable(value: metadata)
+        }
+
+        return nil
+    }
+
+    private func sortedDirectoryMetadatas(_ metadatas: Results<NextcloudDirectoryMetadataTable>) -> [NextcloudDirectoryMetadataTable] {
+        let sortedMetadatas = metadatas.sorted(byKeyPath: "serverUrl", ascending: true)
+        return Array(sortedMetadatas.map { NextcloudDirectoryMetadataTable(value: $0) })
+    }
+
+    func childDirectoriesForDirectory(_ directoryMetadata: NextcloudDirectoryMetadataTable) -> [NextcloudDirectoryMetadataTable] {
+        let metadatas = ncDatabase().objects(NextcloudDirectoryMetadataTable.self).filter("serverUrl BEGINSWITH %@ AND ocId != %@", directoryMetadata.serverUrl, directoryMetadata.account)
+        return sortedDirectoryMetadatas(metadatas)
+    }
+
+    func parentDirectoryMetadataForItem(_ itemMetadata: NextcloudItemMetadataTable) -> NextcloudDirectoryMetadataTable? {
+        return directoryMetadata(account: itemMetadata.account, serverUrl: itemMetadata.serverUrl)
+    }
+
+    func directoryMetadatas(account: String) -> [NextcloudDirectoryMetadataTable] {
+        let metadatas = ncDatabase().objects(NextcloudDirectoryMetadataTable.self).filter("account == %@", account)
+        return sortedDirectoryMetadatas(metadatas)
+    }
+
+    func directoryMetadatas(account: String, parentDirectoryServerUrl: String) -> [NextcloudDirectoryMetadataTable] {
+        let metadatas = ncDatabase().objects(NextcloudDirectoryMetadataTable.self).filter("account == %@ AND parentDirectoryServerUrl == %@", account, parentDirectoryServerUrl)
+        return sortedDirectoryMetadatas(metadatas)
+    }
+
+    private func processDirectoryMetadatasToDelete(databaseToWriteTo: Realm,
+                                           existingDirectoryMetadatas: Results<NextcloudDirectoryMetadataTable>,
+                                           updatedDirectoryMetadatas: [NextcloudDirectoryMetadataTable]) {
+
+        for existingMetadata in existingDirectoryMetadatas {
+            guard !updatedDirectoryMetadatas.contains(where: { $0.ocId == existingMetadata.ocId }),
+                  let metadataToDelete = directoryMetadata(ocId: existingMetadata.ocId) else { continue }
+
+            NSLog("""
+                    Deleting directory metadata.
+                    ocID: %@,
+                    serverUrl: %@,
+                    etag: %@
+                  """
+                  , metadataToDelete.ocId, metadataToDelete.serverUrl, metadataToDelete.etag)
+
+            self.deleteDirectoryAndSubdirectoriesMetadata(ocId: metadataToDelete.ocId)
+        }
+    }
+
+    private func processDirectoryMetadatasToUpdate(databaseToWriteTo: Realm,
+                                           existingDirectoryMetadatas: Results<NextcloudDirectoryMetadataTable>,
+                                           updatedDirectoryMetadatas: [NextcloudDirectoryMetadataTable]) {
+
+        assert(databaseToWriteTo.isInWriteTransaction)
+
+        for updatedMetadata in updatedDirectoryMetadatas {
+            if let existingMetadata = existingDirectoryMetadatas.first(where: { $0.ocId == updatedMetadata.ocId }) {
+
+                if !existingMetadata.isInSameRemoteState(updatedMetadata) {
+
+                    databaseToWriteTo.add(NextcloudDirectoryMetadataTable(value: updatedMetadata), update: .all)
+                    NSLog("""
+                            Updated existing directory metadata.
+                            ocID: %@,
+                            serverUrl: %@,
+                            etag: %@
+                          """
+                          , updatedMetadata.ocId, updatedMetadata.serverUrl, updatedMetadata.etag)
+                }
+                // Don't update under other circumstances in which the metadata already exists
+
+            } else { // This is a new metadata
+                databaseToWriteTo.add(NextcloudDirectoryMetadataTable(value: updatedMetadata), update: .all)
+                NSLog("""
+                        Created new metadata.
+                        ocID: %@,
+                        serverUrl: %@,
+                        etag: %@
+                      """
+                      , updatedMetadata.ocId, updatedMetadata.serverUrl, updatedMetadata.etag)
+            }
+        }
+    }
+
+    func updateDirectoryMetadatas(account: String, parentDirectoryServerUrl: String, updatedDirectoryMetadatas: [NextcloudDirectoryMetadataTable]) {
+        let database = ncDatabase()
+
+        let existingDirectoryMetadatas = ncDatabase().objects(NextcloudDirectoryMetadataTable.self).filter("account == %@ AND parentDirectoryServerUrl == %@", account, parentDirectoryServerUrl)
+
+        // Actual db writing handled internally
+        processDirectoryMetadatasToDelete(databaseToWriteTo: database,
+                                          existingDirectoryMetadatas: existingDirectoryMetadatas,
+                                          updatedDirectoryMetadatas: updatedDirectoryMetadatas)
+
+        do {
+            try database.write {
+
+                processDirectoryMetadatasToUpdate(databaseToWriteTo: database,
+                                                  existingDirectoryMetadatas: existingDirectoryMetadatas,
+                                                  updatedDirectoryMetadatas: updatedDirectoryMetadatas)
+            }
+        } catch let error {
+            NSLog("Could not update directory metadatas, received error: %@", error.localizedDescription)
+        }
+    }
+
+    func directoryMetadataFromItemMetadata(directoryItemMetadata: NextcloudItemMetadataTable, recordEtag: Bool = false) -> NextcloudDirectoryMetadataTable {
+        var newDirectoryMetadata = NextcloudDirectoryMetadataTable()
+        let directoryOcId = directoryItemMetadata.ocId
+
+        if let existingDirectoryMetadata = directoryMetadata(ocId: directoryOcId) {
+            newDirectoryMetadata = existingDirectoryMetadata
+        }
+
+        if recordEtag {
+            newDirectoryMetadata.etag = directoryItemMetadata.etag
+        }
+
+        newDirectoryMetadata.ocId = directoryOcId
+        newDirectoryMetadata.fileId = directoryItemMetadata.fileId
+        newDirectoryMetadata.parentDirectoryServerUrl = directoryItemMetadata.serverUrl
+        newDirectoryMetadata.serverUrl = directoryItemMetadata.serverUrl + "/" + directoryItemMetadata.fileNameView
+        newDirectoryMetadata.account = directoryItemMetadata.account
+        newDirectoryMetadata.e2eEncrypted = directoryItemMetadata.e2eEncrypted
+        newDirectoryMetadata.favorite = directoryItemMetadata.favorite
+        newDirectoryMetadata.permissions = directoryItemMetadata.permissions
+
+        return newDirectoryMetadata
+    }
+
+    func updateDirectoryMetadatasFromItemMetadatas(account: String, parentDirectoryServerUrl: String, updatedDirectoryItemMetadatas: [NextcloudItemMetadataTable], recordEtag: Bool = false) {
+
+        var updatedDirMetadatas: [NextcloudDirectoryMetadataTable] = []
+
+        for directoryItemMetadata in updatedDirectoryItemMetadatas {
+            let newDirectoryMetadata = directoryMetadataFromItemMetadata(directoryItemMetadata: directoryItemMetadata, recordEtag: recordEtag)
+            updatedDirMetadatas.append(newDirectoryMetadata)
+        }
+
+        updateDirectoryMetadatas(account: account, parentDirectoryServerUrl: parentDirectoryServerUrl, updatedDirectoryMetadatas: updatedDirMetadatas)
+    }
+
+    func addDirectoryMetadata(_ metadata: NextcloudDirectoryMetadataTable) {
+        let database = ncDatabase()
+
+        do {
+            try database.write {
+                database.add(metadata, update: .all)
+                NSLog("""
+                        Created new metadata (addDirectoryMetadata).
+                        ocID: %@,
+                        serverUrl: %@,
+                        etag: %@
+                      """
+                      , metadata.ocId, metadata.serverUrl, metadata.etag)
+            }
+        } catch let error {
+            NSLog("Could not add item metadata with ocID: %@ and serverUrl: %@, received error: %@", metadata.ocId, metadata.serverUrl, error.localizedDescription)
+        }
+    }
+
+    // Deletes all metadatas related to the info of the directory provided
+    func deleteDirectoryAndSubdirectoriesMetadata(ocId: String) {
+        let database = ncDatabase()
+        guard let directoryMetadata = database.objects(NextcloudDirectoryMetadataTable.self).filter("ocId == %@", ocId).first else {
+            NSLog("Could not find directory metadata for ocId %@. Not proceeding with deletion", ocId)
+            return
+        }
+
+        let results = database.objects(NextcloudDirectoryMetadataTable.self).filter("account == %@ AND serverUrl BEGINSWITH %@", directoryMetadata.account, directoryMetadata.serverUrl)
+
+        for result in results {
+            deleteItemMetadata(ocId: result.ocId)
+            deleteLocalFileMetadata(ocId: result.ocId)
+        }
+
+        do {
+            try database.write {
+                database.delete(results)
+            }
+        } catch let error {
+            NSLog("Could not delete directory metadata with ocId: %@ and serverUrl: %@, received error: %@", directoryMetadata.ocId, directoryMetadata.serverUrl, error.localizedDescription)
+        }
+    }
+
+    func renameDirectoryAndPropagateToChildren(ocId: String, newServerUrl: String, newFileName: String) {
+
+        let database = ncDatabase()
+
+        do {
+            try database.write {
+                guard let directoryTableResult = database.objects(NextcloudDirectoryMetadataTable.self).filter("ocId == %@", ocId).first,
+                      let directoryItemResult = database.objects(NextcloudItemMetadataTable.self).filter("ocId == %@", ocId).first else {
+                    NSLog("Could not find a directory with ocID %@", ocId)
+                    return
+                }
+
+                let oldServerUrl = directoryTableResult.serverUrl
+
+                let childItemResults = database.objects(NextcloudItemMetadataTable.self).filter("account == %@ AND serverUrl BEGINSWITH %@", directoryTableResult.account, oldServerUrl)
+                let childDirectoryResults = database.objects(NextcloudDirectoryMetadataTable.self).filter("account == %@ AND serverUrl BEGINSWITH %@", directoryTableResult.account, oldServerUrl)
+
+                directoryTableResult.serverUrl = newServerUrl
+                database.add(directoryTableResult, update: .all)
+                directoryItemResult.fileName = newFileName
+                directoryItemResult.fileNameView = newFileName
+                database.add(directoryItemResult, update: .all)
+                NSLog("Renamed directory at %@ to %@", oldServerUrl, newServerUrl)
+
+                for childItem in childItemResults {
+                    let oldServerUrl = childItem.serverUrl
+                    let movedServerUrl = oldServerUrl.replacingOccurrences(of: oldServerUrl, with: newServerUrl)
+                    childItem.serverUrl = movedServerUrl
+                    database.add(childItem, update: .all)
+                    NSLog("Moved childItem at %@ to %@", oldServerUrl, movedServerUrl)
+                }
+
+                for childDirectory in childDirectoryResults {
+                    let oldServerUrl = childDirectory.serverUrl
+                    let oldParentServerUrl = childDirectory.parentDirectoryServerUrl
+                    let movedServerUrl = oldServerUrl.replacingOccurrences(of: oldServerUrl, with: newServerUrl)
+                    let movedParentServerUrl = oldServerUrl.replacingOccurrences(of: oldParentServerUrl, with: newServerUrl)
+                    childDirectory.serverUrl = movedServerUrl
+                    childDirectory.parentDirectoryServerUrl = movedParentServerUrl
+                    database.add(childDirectory, update: .all)
+                    NSLog("Moved childDirectory at %@ to %@", oldServerUrl, movedServerUrl)
+                }
+            }
+        } catch let error {
+            NSLog("Could not rename directory metadata with ocId: %@ serverUrl: %@, received error: %@", ocId, newServerUrl, error.localizedDescription)
+        }
+    }
+
+    func localFileMetadataFromOcId(_ ocId: String) -> NextcloudLocalFileMetadataTable? {
+        if let metadata = ncDatabase().objects(NextcloudLocalFileMetadataTable.self).filter("ocId == %@", ocId).first {
+            return NextcloudLocalFileMetadataTable(value: metadata)
+        }
+
+        return nil
+    }
+
+    func addLocalFileMetadataFromItemMetadata(_ itemMetadata: NextcloudItemMetadataTable) {
+        let database = ncDatabase()
+
+        do {
+            try database.write {
+                let newLocalFileMetadata = NextcloudLocalFileMetadataTable()
+
+                newLocalFileMetadata.ocId = itemMetadata.ocId
+                newLocalFileMetadata.fileName = itemMetadata.fileName
+                newLocalFileMetadata.account = itemMetadata.account
+                newLocalFileMetadata.etag = itemMetadata.etag
+                newLocalFileMetadata.exifDate = Date()
+                newLocalFileMetadata.exifLatitude = "-1"
+                newLocalFileMetadata.exifLongitude = "-1"
+
+                database.add(newLocalFileMetadata, update: .all)
+            }
+        } catch let error {
+            NSLog("Could not add local file metadata from item metadata with ocID: %@ and filename: %@, received error: %@", itemMetadata.ocId, itemMetadata.fileNameView, error.localizedDescription)
+        }
+    }
+
+    func deleteLocalFileMetadata(ocId: String) {
+        let database = ncDatabase()
+
+        do {
+            try database.write {
+                let results = database.objects(NextcloudLocalFileMetadataTable.self).filter("ocId == %@", ocId)
+                database.delete(results)
+            }
+        } catch let error {
+            NSLog("Could not delete local file metadata with ocId: %@, received error: %@", ocId, error.localizedDescription)
+        }
+    }
+
+    private func sortedLocalFileMetadatas(_ metadatas: Results<NextcloudLocalFileMetadataTable>) -> [NextcloudLocalFileMetadataTable] {
+        let sortedMetadatas = metadatas.sorted(byKeyPath: "fileName", ascending: true)
+        return Array(sortedMetadatas.map { NextcloudLocalFileMetadataTable(value: $0) })
+    }
+
+    func localFileMetadatas(account: String) -> [NextcloudLocalFileMetadataTable] {
+        let results = ncDatabase().objects(NextcloudLocalFileMetadataTable.self).filter("account == %@", account)
+        return sortedLocalFileMetadatas(results)
+    }
+
+    func localFileItemMetadatas(account: String) -> [NextcloudItemMetadataTable] {
+        let localFileMetadatas = localFileMetadatas(account: account)
+        let localFileMetadatasOcIds = Array(localFileMetadatas.map { $0.ocId })
+
+        var itemMetadatas: [NextcloudItemMetadataTable] = []
+
+        for ocId in localFileMetadatasOcIds {
+            guard let itemMetadata = itemMetadataFromOcId(ocId) else {
+                NSLog("Could not find matching item metadata for local file metadata with ocId: %@ with request from account: %@", ocId, account)
+                continue;
+            }
+
+            itemMetadatas.append(NextcloudItemMetadataTable(value: itemMetadata))
+        }
+
+        return itemMetadatas
+    }
+
+    func convertNKFileToItemMetadata(_ file: NKFile, account: String) -> NextcloudItemMetadataTable {
+
+        let metadata = NextcloudItemMetadataTable()
+
+        metadata.account = account
+        metadata.checksums = file.checksums
+        metadata.commentsUnread = file.commentsUnread
+        metadata.contentType = file.contentType
+        if let date = file.creationDate {
+            metadata.creationDate = date as Date
+        } else {
+            metadata.creationDate = file.date as Date
+        }
+        metadata.dataFingerprint = file.dataFingerprint
+        metadata.date = file.date as Date
+        metadata.directory = file.directory
+        metadata.downloadURL = file.downloadURL
+        metadata.e2eEncrypted = file.e2eEncrypted
+        metadata.etag = file.etag
+        metadata.favorite = file.favorite
+        metadata.fileId = file.fileId
+        metadata.fileName = file.fileName
+        metadata.fileNameView = file.fileName
+        metadata.hasPreview = file.hasPreview
+        metadata.iconName = file.iconName
+        metadata.mountType = file.mountType
+        metadata.name = file.name
+        metadata.note = file.note
+        metadata.ocId = file.ocId
+        metadata.ownerId = file.ownerId
+        metadata.ownerDisplayName = file.ownerDisplayName
+        metadata.lock = file.lock
+        metadata.lockOwner = file.lockOwner
+        metadata.lockOwnerEditor = file.lockOwnerEditor
+        metadata.lockOwnerType = file.lockOwnerType
+        metadata.lockOwnerDisplayName = file.lockOwnerDisplayName
+        metadata.lockTime = file.lockTime
+        metadata.lockTimeOut = file.lockTimeOut
+        metadata.path = file.path
+        metadata.permissions = file.permissions
+        metadata.quotaUsedBytes = file.quotaUsedBytes
+        metadata.quotaAvailableBytes = file.quotaAvailableBytes
+        metadata.richWorkspace = file.richWorkspace
+        metadata.resourceType = file.resourceType
+        metadata.serverUrl = file.serverUrl
+        metadata.sharePermissionsCollaborationServices = file.sharePermissionsCollaborationServices
+        for element in file.sharePermissionsCloudMesh {
+            metadata.sharePermissionsCloudMesh.append(element)
+        }
+        for element in file.shareType {
+            metadata.shareType.append(element)
+        }
+        metadata.size = file.size
+        metadata.classFile = file.classFile
+        //FIXME: iOS 12.0,* don't detect UTI text/markdown, text/x-markdown
+        if (metadata.contentType == "text/markdown" || metadata.contentType == "text/x-markdown") && metadata.classFile == NKCommon.TypeClassFile.unknow.rawValue {
+            metadata.classFile = NKCommon.TypeClassFile.document.rawValue
+        }
+        if let date = file.uploadDate {
+            metadata.uploadDate = date as Date
+        } else {
+            metadata.uploadDate = file.date as Date
+        }
+        metadata.urlBase = file.urlBase
+        metadata.user = file.user
+        metadata.userId = file.userId
+
+        // Support for finding the correct filename for e2ee files should go here
+
+        return metadata
+    }
+
+    func convertNKFilesFromDirectoryReadToItemMetadatas(_ files: [NKFile], account: String, completionHandler: @escaping (_ directoryMetadata: NextcloudItemMetadataTable, _ childDirectoriesMetadatas: [NextcloudItemMetadataTable], _ metadatas: [NextcloudItemMetadataTable]) -> Void) {
+
+        var directoryMetadataSet = false
+        var directoryMetadata = NextcloudItemMetadataTable()
+        var childDirectoriesMetadatas: [NextcloudItemMetadataTable] = []
+        var metadatas: [NextcloudItemMetadataTable] = []
+
+        for file in files {
+            let metadata = convertNKFileToItemMetadata(file, account: account)
+
+            if metadatas.isEmpty && !directoryMetadataSet {
+                directoryMetadata = metadata;
+                directoryMetadataSet = true;
+            } else {
+                metadatas.append(metadata)
+                if metadata.directory {
+                    childDirectoriesMetadatas.append(metadata)
+                }
+            }
+        }
+
+        completionHandler(directoryMetadata, childDirectoriesMetadatas, metadatas)
+    }
+}
diff --git a/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/Database/NextcloudFilesDatabaseTables.swift b/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/Database/NextcloudFilesDatabaseTables.swift
new file mode 100644 (file)
index 0000000..59d759d
--- /dev/null
@@ -0,0 +1,234 @@
+/*
+ * 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 Foundation
+import RealmSwift
+import FileProvider
+import NextcloudKit
+
+class NextcloudItemMetadataTable: Object {
+    enum Status: Int {
+        case downloadError = -4
+        case downloading = -3
+        case inDownload = -2
+        case waitDownload = -1
+
+        case normal = 0
+
+        case waitUpload = 1
+        case inUpload = 2
+        case uploading = 3
+        case uploadError = 4
+    }
+
+    enum SharePermissions: Int {
+        case readShare = 1
+        case updateShare = 2
+        case createShare = 4
+        case deleteShare = 8
+        case shareShare = 16
+
+        case maxFileShare = 19
+        case maxFolderShare = 31
+    }
+
+    override func isEqual(_ object: Any?) -> Bool {
+        if let object = object as? NextcloudItemMetadataTable {
+            return self.fileId == object.fileId &&
+                   self.account == object.account &&
+                   self.path == object.path &&
+                   self.fileName == object.fileName
+        }
+
+        return false
+    }
+
+    func isInSameDatabaseStoreableRemoteState(_ comparingMetadata: NextcloudItemMetadataTable) -> Bool {
+        return comparingMetadata.etag == self.etag &&
+            comparingMetadata.fileNameView == self.fileNameView &&
+            comparingMetadata.date == self.date &&
+            comparingMetadata.permissions == self.permissions &&
+            comparingMetadata.hasPreview == self.hasPreview &&
+            comparingMetadata.note == self.note &&
+            comparingMetadata.lock == self.lock &&
+            comparingMetadata.sharePermissionsCollaborationServices == self.sharePermissionsCollaborationServices &&
+            comparingMetadata.favorite == self.favorite
+    }
+
+    @Persisted(primaryKey: true) var ocId: String
+    @Persisted var account = ""
+    @Persisted var assetLocalIdentifier = ""
+    @Persisted var checksums = ""
+    @Persisted var chunk: Bool = false
+    @Persisted var classFile = ""
+    @Persisted var commentsUnread: Bool = false
+    @Persisted var contentType = ""
+    @Persisted var creationDate = Date()
+    @Persisted var dataFingerprint = ""
+    @Persisted var date = Date()
+    @Persisted var directory: Bool = false
+    @Persisted var deleteAssetLocalIdentifier: Bool = false
+    @Persisted var downloadURL = ""
+    @Persisted var e2eEncrypted: Bool = false
+    @Persisted var edited: Bool = false
+    @Persisted var etag = ""
+    @Persisted var etagResource = ""
+    @Persisted var favorite: Bool = false
+    @Persisted var fileId = ""
+    @Persisted var fileName = ""
+    @Persisted var fileNameView = ""
+    @Persisted var hasPreview: Bool = false
+    @Persisted var iconName = ""
+    @Persisted var iconUrl = ""
+    @Persisted var isExtractFile: Bool = false
+    @Persisted var livePhoto: Bool = false
+    @Persisted var mountType = ""
+    @Persisted var name = "" // for unifiedSearch is the provider.id
+    @Persisted var note = ""
+    @Persisted var ownerId = ""
+    @Persisted var ownerDisplayName = ""
+    @Persisted var lock = false
+    @Persisted var lockOwner = ""
+    @Persisted var lockOwnerEditor = ""
+    @Persisted var lockOwnerType = 0
+    @Persisted var lockOwnerDisplayName = ""
+    @Persisted var lockTime: Date?
+    @Persisted var lockTimeOut: Date?
+    @Persisted var path = ""
+    @Persisted var permissions = ""
+    @Persisted var quotaUsedBytes: Int64 = 0
+    @Persisted var quotaAvailableBytes: Int64 = 0
+    @Persisted var resourceType = ""
+    @Persisted var richWorkspace: String?
+    @Persisted var serverUrl = "" // For parent directory!!
+    @Persisted var session = ""
+    @Persisted var sessionError = ""
+    @Persisted var sessionSelector = ""
+    @Persisted var sessionTaskIdentifier: Int = 0
+    @Persisted var sharePermissionsCollaborationServices: Int = 0
+    let sharePermissionsCloudMesh = List<String>() // TODO: Find a way to compare these in remote state check
+    let shareType = List<Int>()
+    @Persisted var size: Int64 = 0
+    @Persisted var status: Int = 0
+    @Persisted var subline: String?
+    @Persisted var trashbinFileName = ""
+    @Persisted var trashbinOriginalLocation = ""
+    @Persisted var trashbinDeletionTime = Date()
+    @Persisted var uploadDate = Date()
+    @Persisted var url = ""
+    @Persisted var urlBase = ""
+    @Persisted var user = ""
+    @Persisted var userId = ""
+
+    var fileExtension: String {
+        (fileNameView as NSString).pathExtension
+    }
+
+    var fileNoExtension: String {
+        (fileNameView as NSString).deletingPathExtension
+    }
+
+    var isRenameable: Bool {
+        return lock
+    }
+
+    var isPrintable: Bool {
+        if isDocumentViewableOnly {
+            return false
+        }
+        if ["application/pdf", "com.adobe.pdf"].contains(contentType) || contentType.hasPrefix("text/") || classFile == NKCommon.TypeClassFile.image.rawValue {
+            return true
+        }
+        return false
+    }
+
+    var isDocumentViewableOnly: Bool {
+        return sharePermissionsCollaborationServices == SharePermissions.readShare.rawValue &&
+            classFile == NKCommon.TypeClassFile.document.rawValue
+    }
+
+    var isCopyableInPasteboard: Bool {
+        !isDocumentViewableOnly && !directory
+    }
+
+    var isModifiableWithQuickLook: Bool {
+        if directory || isDocumentViewableOnly {
+            return false
+        }
+        return contentType == "com.adobe.pdf" || contentType == "application/pdf" || classFile == NKCommon.TypeClassFile.image.rawValue
+    }
+
+    var isSettableOnOffline: Bool {
+        return session.isEmpty && !isDocumentViewableOnly
+    }
+
+    var canOpenIn: Bool {
+        return session.isEmpty && !isDocumentViewableOnly && !directory
+    }
+
+    var isDownloadUpload: Bool {
+        return status == Status.inDownload.rawValue ||
+            status == Status.downloading.rawValue ||
+            status == Status.inUpload.rawValue ||
+            status == Status.uploading.rawValue
+    }
+
+    var isDownload: Bool {
+        status == Status.inDownload.rawValue || status == Status.downloading.rawValue
+    }
+
+    var isUpload: Bool {
+        status == Status.inUpload.rawValue || status == Status.uploading.rawValue
+    }
+
+    /// Returns false if the user is lokced out of the file. I.e. The file is locked but by somone else
+    func canUnlock(as user: String) -> Bool {
+        return !lock || (lockOwner == user && lockOwnerType == 0)
+    }
+}
+
+class NextcloudDirectoryMetadataTable: Object {
+    func isInSameRemoteState(_ comparingMetadata: NextcloudDirectoryMetadataTable) -> Bool {
+        return comparingMetadata.etag == self.etag &&
+            comparingMetadata.e2eEncrypted == self.e2eEncrypted &&
+            comparingMetadata.favorite == self.favorite &&
+            comparingMetadata.permissions == self.permissions
+    }
+
+    @Persisted(primaryKey: true) var ocId: String
+    @Persisted var account = ""
+    @Persisted var colorFolder: String?
+    @Persisted var e2eEncrypted: Bool = false
+    @Persisted var etag = ""
+    @Persisted var favorite: Bool = false
+    @Persisted var fileId = ""
+    @Persisted var offline: Bool = false
+    @Persisted var permissions = ""
+    @Persisted var richWorkspace: String?
+    @Persisted var serverUrl = ""
+    @Persisted var parentDirectoryServerUrl = ""
+}
+
+class NextcloudLocalFileMetadataTable: Object {
+    @Persisted(primaryKey: true) var ocId: String
+    @Persisted var account = ""
+    @Persisted var etag = ""
+    @Persisted var exifDate: Date?
+    @Persisted var exifLatitude = ""
+    @Persisted var exifLongitude = ""
+    @Persisted var exifLensModel: String?
+    @Persisted var favorite: Bool = false
+    @Persisted var fileName = ""
+    @Persisted var offline: Bool = false
+}
diff --git a/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/NextcloudFilesDatabaseManager.swift b/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/NextcloudFilesDatabaseManager.swift
deleted file mode 100644 (file)
index dac9ec4..0000000
+++ /dev/null
@@ -1,710 +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 Foundation
-import RealmSwift
-import FileProvider
-import NextcloudKit
-
-class NextcloudFilesDatabaseManager : NSObject {
-    static let shared = {
-        return NextcloudFilesDatabaseManager();
-    }()
-
-    let relativeDatabaseFolderPath = "Database/"
-    let databaseFilename = "fileproviderextdatabase.realm"
-    let relativeDatabaseFilePath: String
-    var databasePath: URL?
-
-    let schemaVersion: UInt64 = 100
-
-    override init() {
-        self.relativeDatabaseFilePath = self.relativeDatabaseFolderPath + self.databaseFilename
-
-        guard let fileProviderDataDirUrl = pathForFileProviderExtData() else {
-            super.init()
-            return
-        }
-
-        self.databasePath = fileProviderDataDirUrl.appendingPathComponent(self.relativeDatabaseFilePath)
-
-        // Disable file protection for directory DB
-        // https://docs.mongodb.com/realm/sdk/ios/examples/configure-and-open-a-realm/#std-label-ios-open-a-local-realm
-        let dbFolder = fileProviderDataDirUrl.appendingPathComponent(self.relativeDatabaseFolderPath)
-        let dbFolderPath = dbFolder.path
-        do {
-            try FileManager.default.createDirectory(at: dbFolder, withIntermediateDirectories: true)
-            try FileManager.default.setAttributes([FileAttributeKey.protectionKey: FileProtectionType.completeUntilFirstUserAuthentication], ofItemAtPath: dbFolderPath)
-        } catch let error {
-            NSLog("Could not set permission level for File Provider database folder, received error: %@", error.localizedDescription)
-        }
-
-        let config = Realm.Configuration(
-            fileURL: self.databasePath,
-            schemaVersion: self.schemaVersion,
-            objectTypes: [NextcloudItemMetadataTable.self, NextcloudDirectoryMetadataTable.self, NextcloudLocalFileMetadataTable.self]
-        )
-
-        Realm.Configuration.defaultConfiguration = config
-
-        do {
-            _ = try Realm()
-            NSLog("Successfully started Realm db for FileProviderExt")
-        } catch let error as NSError {
-            NSLog("Error opening Realm db: %@", error.localizedDescription)
-        }
-
-        super.init()
-    }
-
-    private func ncDatabase() -> Realm {
-        let realm = try! Realm()
-        realm.refresh()
-        return realm
-    }
-
-    func anyItemMetadatasForAccount(_ account: String) -> Bool {
-        return !ncDatabase().objects(NextcloudItemMetadataTable.self).filter("account == %@", account).isEmpty
-    }
-
-    func itemMetadataFromOcId(_ ocId: String) -> NextcloudItemMetadataTable? {
-        // Realm objects are live-fire, i.e. they will be changed and invalidated according to changes in the db
-        // Let's therefore create a copy
-        if let itemMetadata = ncDatabase().objects(NextcloudItemMetadataTable.self).filter("ocId == %@", ocId).first {
-            return NextcloudItemMetadataTable(value: itemMetadata)
-        }
-
-        return nil
-    }
-
-    private func sortedItemMetadatas(_ metadatas: Results<NextcloudItemMetadataTable>) -> [NextcloudItemMetadataTable] {
-        let sortedMetadatas = metadatas.sorted(byKeyPath: "fileName", ascending: true)
-        return Array(sortedMetadatas.map { NextcloudItemMetadataTable(value: $0) })
-    }
-
-    func itemMetadatas(account: String) -> [NextcloudItemMetadataTable] {
-        let metadatas = ncDatabase().objects(NextcloudItemMetadataTable.self).filter("account == %@", account)
-        return sortedItemMetadatas(metadatas)
-    }
-
-    func itemMetadatas(account: String, serverUrl: String) -> [NextcloudItemMetadataTable] {
-        let metadatas = ncDatabase().objects(NextcloudItemMetadataTable.self).filter("account == %@ AND serverUrl == %@", account, serverUrl)
-        return sortedItemMetadatas(metadatas)
-    }
-
-    func itemMetadatas(account: String, serverUrl: String, status: NextcloudItemMetadataTable.Status) -> [NextcloudItemMetadataTable] {
-        let metadatas = ncDatabase().objects(NextcloudItemMetadataTable.self).filter("account == %@ AND serverUrl == %@ AND status == %@", account, serverUrl, status.rawValue)
-        return sortedItemMetadatas(metadatas)
-    }
-
-    func itemMetadataFromFileProviderItemIdentifier(_ identifier: NSFileProviderItemIdentifier) -> NextcloudItemMetadataTable? {
-        let ocId = identifier.rawValue
-        return itemMetadataFromOcId(ocId)
-    }
-
-    private func processItemMetadatasToDelete(databaseToWriteTo: Realm,
-                                              existingMetadatas: Results<NextcloudItemMetadataTable>,
-                                              updatedMetadatas: [NextcloudItemMetadataTable]) -> [NextcloudItemMetadataTable] {
-
-        assert(databaseToWriteTo.isInWriteTransaction)
-
-        var deletedMetadatas: [NextcloudItemMetadataTable] = []
-
-        for existingMetadata in existingMetadatas {
-            guard !updatedMetadatas.contains(where: { $0.ocId == existingMetadata.ocId }),
-                    let metadataToDelete = itemMetadataFromOcId(existingMetadata.ocId) else { continue }
-
-            deletedMetadatas.append(metadataToDelete)
-
-            NSLog("""
-                    Deleting metadata.
-                    ocID: %@,
-                    fileName: %@,
-                    etag: %@
-                  """
-                  , metadataToDelete.ocId, metadataToDelete.fileName, metadataToDelete.etag)
-
-            // 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>,
-                                              updatedMetadatas: [NextcloudItemMetadataTable]) -> (newMetadatas: [NextcloudItemMetadataTable], updatedMetadatas: [NextcloudItemMetadataTable]) {
-
-        assert(databaseToWriteTo.isInWriteTransaction)
-
-        var returningNewMetadatas: [NextcloudItemMetadataTable] = []
-        var returningUpdatedMetadatas: [NextcloudItemMetadataTable] = []
-
-        for updatedMetadata in updatedMetadatas {
-            if let existingMetadata = existingMetadatas.first(where: { $0.ocId == updatedMetadata.ocId }) {
-
-                if existingMetadata.status == NextcloudItemMetadataTable.Status.normal.rawValue &&
-                    !existingMetadata.isInSameDatabaseStoreableRemoteState(updatedMetadata) {
-
-                    returningUpdatedMetadatas.append(NextcloudItemMetadataTable(value: updatedMetadata))
-                    databaseToWriteTo.add(updatedMetadata, update: .all)
-                    
-                    NSLog("""
-                            Updated existing metadata.
-                            ocID: %@,
-                            fileName: %@,
-                            etag: %@
-                          """
-                          , updatedMetadata.ocId, updatedMetadata.fileName, updatedMetadata.etag)
-                } else {
-                    NSLog("""
-                              Skipping metadata update as received metadata status is same as existing,
-                              or metadata is currently being downloaded/uploaded:
-
-                              ocID: %@,
-                              fileName: %@,
-                              etag: %@
-                          """
-                          , updatedMetadata.ocId, updatedMetadata.fileName, updatedMetadata.etag)
-                }
-
-            } else { // This is a new metadata
-                returningNewMetadatas.append(NextcloudItemMetadataTable(value: updatedMetadata))
-                databaseToWriteTo.add(updatedMetadata, update: .all)
-
-                NSLog("""
-                        Created new metadata.
-                        ocID: %@,
-                        fileName: %@,
-                        etag: %@
-                      """
-                      , updatedMetadata.ocId, updatedMetadata.fileName, updatedMetadata.etag)
-            }
-        }
-
-        return (returningNewMetadatas, returningUpdatedMetadatas)
-    }
-
-    func updateItemMetadatas(account: String, serverUrl: String, updatedMetadatas: [NextcloudItemMetadataTable], completionHandler: @escaping(_ newMetadatas: [NextcloudItemMetadataTable]?, _ updatedMetadatas: [NextcloudItemMetadataTable]?, _ deletedMetadatas: [NextcloudItemMetadataTable]?) -> Void) {
-        let database = ncDatabase()
-
-        do {
-            try database.write {
-                let existingMetadatas = database.objects(NextcloudItemMetadataTable.self).filter("account == %@ AND serverUrl == %@ AND status == %@", account, serverUrl, NextcloudItemMetadataTable.Status.normal.rawValue)
-
-                let deletedMetadatas = processItemMetadatasToDelete(databaseToWriteTo: database,
-                                                                    existingMetadatas: existingMetadatas,
-                                                                    updatedMetadatas: updatedMetadatas)
-
-                let metadatasFromUpdate = processItemMetadatasToUpdate(databaseToWriteTo: database,
-                                                                       existingMetadatas: existingMetadatas,
-                                                                       updatedMetadatas: updatedMetadatas)
-
-                completionHandler(metadatasFromUpdate.newMetadatas, metadatasFromUpdate.updatedMetadatas, deletedMetadatas)
-            }
-        } catch let error {
-            NSLog("Could not update any metadatas, received error: %@", error.localizedDescription)
-            completionHandler(nil, nil, nil)
-        }
-    }
-
-    func setStatusForItemMetadata(_ metadata: NextcloudItemMetadataTable, status: NextcloudItemMetadataTable.Status) -> NextcloudItemMetadataTable? {
-        let database = ncDatabase()
-        var result: NextcloudItemMetadataTable?
-
-        do {
-            try database.write {
-                result = database.objects(NextcloudItemMetadataTable.self).filter("ocId == %@", metadata.ocId).first
-                result?.status = status.rawValue
-            }
-        } catch let error {
-            NSLog("Could not update status for item metadata with ocID: %@ and filename: %@, received error: %@", metadata.ocId, metadata.fileNameView, error.localizedDescription)
-        }
-
-        if result != nil {
-            return NextcloudItemMetadataTable(value: result!)
-        }
-
-        return nil
-    }
-
-    func addItemMetadata(_ metadata: NextcloudItemMetadataTable) {
-        let database = ncDatabase()
-
-        do {
-            try database.write {
-                database.add(metadata, update: .all)
-                NSLog("""
-                        Created new metadata (addItemMetadata).
-                        ocID: %@,
-                        fileName: %@,
-                        etag: %@
-                      """
-                      , metadata.ocId, metadata.fileName, metadata.etag)
-            }
-        } catch let error {
-            NSLog("Could not add item metadata with ocID: %@ and filename: %@, received error: %@", metadata.ocId, metadata.fileNameView, error.localizedDescription)
-        }
-    }
-
-    func deleteItemMetadata(ocId: String) {
-        let database = ncDatabase()
-
-        do {
-            try database.write {
-                let results = database.objects(NextcloudItemMetadataTable.self).filter("ocId == %@", ocId)
-                database.delete(results)
-            }
-        } catch let error {
-            NSLog("Could not delete item metadata with ocId: %@, received error: %@", ocId, error.localizedDescription)
-        }
-    }
-
-    func renameItemMetadata(ocId: String, newServerUrl: String, newFileName: String) {
-        let database = ncDatabase()
-
-        do {
-            try database.write {
-                guard let itemMetadata = database.objects(NextcloudItemMetadataTable.self).filter("ocId == %@", ocId).first else {
-                    NSLog("Could not find an item with ocID %@ to rename to %@", ocId, newFileName)
-                    return
-                }
-
-                let oldFileName = itemMetadata.fileName
-                let oldServerUrl = itemMetadata.serverUrl
-
-                itemMetadata.fileName = newFileName
-                itemMetadata.fileNameView = newFileName
-                itemMetadata.serverUrl = newServerUrl
-
-                database.add(itemMetadata, update: .all)
-
-                NSLog("Renamed item %@ to %@, moved from serverUrl: %@ to serverUrl: %@", oldFileName, newFileName, oldServerUrl, newServerUrl)
-            }
-        } catch let error {
-            NSLog("Could not rename filename of item metadata with ocID: %@ to proposed name %@, received error: %@", ocId, newFileName, error.localizedDescription)
-        }
-    }
-
-    func directoryMetadata(account: String, serverUrl: String) -> NextcloudDirectoryMetadataTable? {
-        if let metadata = ncDatabase().objects(NextcloudDirectoryMetadataTable.self).filter("account == %@ AND serverUrl == %@", account, serverUrl).first {
-            return NextcloudDirectoryMetadataTable(value: metadata)
-        }
-
-        return nil
-    }
-
-    func directoryMetadata(ocId: String) -> NextcloudDirectoryMetadataTable? {
-        if let metadata = ncDatabase().objects(NextcloudDirectoryMetadataTable.self).filter("ocId == %@", ocId).first {
-            return NextcloudDirectoryMetadataTable(value: metadata)
-        }
-
-        return nil
-    }
-
-    private func sortedDirectoryMetadatas(_ metadatas: Results<NextcloudDirectoryMetadataTable>) -> [NextcloudDirectoryMetadataTable] {
-        let sortedMetadatas = metadatas.sorted(byKeyPath: "serverUrl", ascending: true)
-        return Array(sortedMetadatas.map { NextcloudDirectoryMetadataTable(value: $0) })
-    }
-
-    func childDirectoriesForDirectory(_ directoryMetadata: NextcloudDirectoryMetadataTable) -> [NextcloudDirectoryMetadataTable] {
-        let metadatas = ncDatabase().objects(NextcloudDirectoryMetadataTable.self).filter("serverUrl BEGINSWITH %@ AND ocId != %@", directoryMetadata.serverUrl, directoryMetadata.account)
-        return sortedDirectoryMetadatas(metadatas)
-    }
-
-    func parentDirectoryMetadataForItem(_ itemMetadata: NextcloudItemMetadataTable) -> NextcloudDirectoryMetadataTable? {
-        return directoryMetadata(account: itemMetadata.account, serverUrl: itemMetadata.serverUrl)
-    }
-
-    func directoryMetadatas(account: String) -> [NextcloudDirectoryMetadataTable] {
-        let metadatas = ncDatabase().objects(NextcloudDirectoryMetadataTable.self).filter("account == %@", account)
-        return sortedDirectoryMetadatas(metadatas)
-    }
-
-    func directoryMetadatas(account: String, parentDirectoryServerUrl: String) -> [NextcloudDirectoryMetadataTable] {
-        let metadatas = ncDatabase().objects(NextcloudDirectoryMetadataTable.self).filter("account == %@ AND parentDirectoryServerUrl == %@", account, parentDirectoryServerUrl)
-        return sortedDirectoryMetadatas(metadatas)
-    }
-
-    private func processDirectoryMetadatasToDelete(databaseToWriteTo: Realm,
-                                           existingDirectoryMetadatas: Results<NextcloudDirectoryMetadataTable>,
-                                           updatedDirectoryMetadatas: [NextcloudDirectoryMetadataTable]) {
-
-        for existingMetadata in existingDirectoryMetadatas {
-            guard !updatedDirectoryMetadatas.contains(where: { $0.ocId == existingMetadata.ocId }),
-                  let metadataToDelete = directoryMetadata(ocId: existingMetadata.ocId) else { continue }
-
-            NSLog("""
-                    Deleting directory metadata.
-                    ocID: %@,
-                    serverUrl: %@,
-                    etag: %@
-                  """
-                  , metadataToDelete.ocId, metadataToDelete.serverUrl, metadataToDelete.etag)
-
-            self.deleteDirectoryAndSubdirectoriesMetadata(ocId: metadataToDelete.ocId)
-        }
-    }
-
-    private func processDirectoryMetadatasToUpdate(databaseToWriteTo: Realm,
-                                           existingDirectoryMetadatas: Results<NextcloudDirectoryMetadataTable>,
-                                           updatedDirectoryMetadatas: [NextcloudDirectoryMetadataTable]) {
-
-        assert(databaseToWriteTo.isInWriteTransaction)
-
-        for updatedMetadata in updatedDirectoryMetadatas {
-            if let existingMetadata = existingDirectoryMetadatas.first(where: { $0.ocId == updatedMetadata.ocId }) {
-
-                if !existingMetadata.isInSameRemoteState(updatedMetadata) {
-
-                    databaseToWriteTo.add(NextcloudDirectoryMetadataTable(value: updatedMetadata), update: .all)
-                    NSLog("""
-                            Updated existing directory metadata.
-                            ocID: %@,
-                            serverUrl: %@,
-                            etag: %@
-                          """
-                          , updatedMetadata.ocId, updatedMetadata.serverUrl, updatedMetadata.etag)
-                }
-                // Don't update under other circumstances in which the metadata already exists
-
-            } else { // This is a new metadata
-                databaseToWriteTo.add(NextcloudDirectoryMetadataTable(value: updatedMetadata), update: .all)
-                NSLog("""
-                        Created new metadata.
-                        ocID: %@,
-                        serverUrl: %@,
-                        etag: %@
-                      """
-                      , updatedMetadata.ocId, updatedMetadata.serverUrl, updatedMetadata.etag)
-            }
-        }
-    }
-
-    func updateDirectoryMetadatas(account: String, parentDirectoryServerUrl: String, updatedDirectoryMetadatas: [NextcloudDirectoryMetadataTable]) {
-        let database = ncDatabase()
-
-        let existingDirectoryMetadatas = ncDatabase().objects(NextcloudDirectoryMetadataTable.self).filter("account == %@ AND parentDirectoryServerUrl == %@", account, parentDirectoryServerUrl)
-
-        // Actual db writing handled internally
-        processDirectoryMetadatasToDelete(databaseToWriteTo: database,
-                                          existingDirectoryMetadatas: existingDirectoryMetadatas,
-                                          updatedDirectoryMetadatas: updatedDirectoryMetadatas)
-
-        do {
-            try database.write {
-
-                processDirectoryMetadatasToUpdate(databaseToWriteTo: database,
-                                                  existingDirectoryMetadatas: existingDirectoryMetadatas,
-                                                  updatedDirectoryMetadatas: updatedDirectoryMetadatas)
-            }
-        } catch let error {
-            NSLog("Could not update directory metadatas, received error: %@", error.localizedDescription)
-        }
-    }
-
-    func directoryMetadataFromItemMetadata(directoryItemMetadata: NextcloudItemMetadataTable, recordEtag: Bool = false) -> NextcloudDirectoryMetadataTable {
-        var newDirectoryMetadata = NextcloudDirectoryMetadataTable()
-        let directoryOcId = directoryItemMetadata.ocId
-
-        if let existingDirectoryMetadata = directoryMetadata(ocId: directoryOcId) {
-            newDirectoryMetadata = existingDirectoryMetadata
-        }
-
-        if recordEtag {
-            newDirectoryMetadata.etag = directoryItemMetadata.etag
-        }
-
-        newDirectoryMetadata.ocId = directoryOcId
-        newDirectoryMetadata.fileId = directoryItemMetadata.fileId
-        newDirectoryMetadata.parentDirectoryServerUrl = directoryItemMetadata.serverUrl
-        newDirectoryMetadata.serverUrl = directoryItemMetadata.serverUrl + "/" + directoryItemMetadata.fileNameView
-        newDirectoryMetadata.account = directoryItemMetadata.account
-        newDirectoryMetadata.e2eEncrypted = directoryItemMetadata.e2eEncrypted
-        newDirectoryMetadata.favorite = directoryItemMetadata.favorite
-        newDirectoryMetadata.permissions = directoryItemMetadata.permissions
-
-        return newDirectoryMetadata
-    }
-
-    func updateDirectoryMetadatasFromItemMetadatas(account: String, parentDirectoryServerUrl: String, updatedDirectoryItemMetadatas: [NextcloudItemMetadataTable], recordEtag: Bool = false) {
-
-        var updatedDirMetadatas: [NextcloudDirectoryMetadataTable] = []
-
-        for directoryItemMetadata in updatedDirectoryItemMetadatas {
-            let newDirectoryMetadata = directoryMetadataFromItemMetadata(directoryItemMetadata: directoryItemMetadata, recordEtag: recordEtag)
-            updatedDirMetadatas.append(newDirectoryMetadata)
-        }
-
-        updateDirectoryMetadatas(account: account, parentDirectoryServerUrl: parentDirectoryServerUrl, updatedDirectoryMetadatas: updatedDirMetadatas)
-    }
-
-    func addDirectoryMetadata(_ metadata: NextcloudDirectoryMetadataTable) {
-        let database = ncDatabase()
-
-        do {
-            try database.write {
-                database.add(metadata, update: .all)
-                NSLog("""
-                        Created new metadata (addDirectoryMetadata).
-                        ocID: %@,
-                        serverUrl: %@,
-                        etag: %@
-                      """
-                      , metadata.ocId, metadata.serverUrl, metadata.etag)
-            }
-        } catch let error {
-            NSLog("Could not add item metadata with ocID: %@ and serverUrl: %@, received error: %@", metadata.ocId, metadata.serverUrl, error.localizedDescription)
-        }
-    }
-
-    // Deletes all metadatas related to the info of the directory provided
-    func deleteDirectoryAndSubdirectoriesMetadata(ocId: String) {
-        let database = ncDatabase()
-        guard let directoryMetadata = database.objects(NextcloudDirectoryMetadataTable.self).filter("ocId == %@", ocId).first else {
-            NSLog("Could not find directory metadata for ocId %@. Not proceeding with deletion", ocId)
-            return
-        }
-
-        let results = database.objects(NextcloudDirectoryMetadataTable.self).filter("account == %@ AND serverUrl BEGINSWITH %@", directoryMetadata.account, directoryMetadata.serverUrl)
-
-        for result in results {
-            deleteItemMetadata(ocId: result.ocId)
-            deleteLocalFileMetadata(ocId: result.ocId)
-        }
-
-        do {
-            try database.write {
-                database.delete(results)
-            }
-        } catch let error {
-            NSLog("Could not delete directory metadata with ocId: %@ and serverUrl: %@, received error: %@", directoryMetadata.ocId, directoryMetadata.serverUrl, error.localizedDescription)
-        }
-    }
-
-    func renameDirectoryAndPropagateToChildren(ocId: String, newServerUrl: String, newFileName: String) {
-
-        let database = ncDatabase()
-
-        do {
-            try database.write {
-                guard let directoryTableResult = database.objects(NextcloudDirectoryMetadataTable.self).filter("ocId == %@", ocId).first,
-                      let directoryItemResult = database.objects(NextcloudItemMetadataTable.self).filter("ocId == %@", ocId).first else {
-                    NSLog("Could not find a directory with ocID %@", ocId)
-                    return
-                }
-
-                let oldServerUrl = directoryTableResult.serverUrl
-
-                let childItemResults = database.objects(NextcloudItemMetadataTable.self).filter("account == %@ AND serverUrl BEGINSWITH %@", directoryTableResult.account, oldServerUrl)
-                let childDirectoryResults = database.objects(NextcloudDirectoryMetadataTable.self).filter("account == %@ AND serverUrl BEGINSWITH %@", directoryTableResult.account, oldServerUrl)
-
-                directoryTableResult.serverUrl = newServerUrl
-                database.add(directoryTableResult, update: .all)
-                directoryItemResult.fileName = newFileName
-                directoryItemResult.fileNameView = newFileName
-                database.add(directoryItemResult, update: .all)
-                NSLog("Renamed directory at %@ to %@", oldServerUrl, newServerUrl)
-
-                for childItem in childItemResults {
-                    let oldServerUrl = childItem.serverUrl
-                    let movedServerUrl = oldServerUrl.replacingOccurrences(of: oldServerUrl, with: newServerUrl)
-                    childItem.serverUrl = movedServerUrl
-                    database.add(childItem, update: .all)
-                    NSLog("Moved childItem at %@ to %@", oldServerUrl, movedServerUrl)
-                }
-
-                for childDirectory in childDirectoryResults {
-                    let oldServerUrl = childDirectory.serverUrl
-                    let oldParentServerUrl = childDirectory.parentDirectoryServerUrl
-                    let movedServerUrl = oldServerUrl.replacingOccurrences(of: oldServerUrl, with: newServerUrl)
-                    let movedParentServerUrl = oldServerUrl.replacingOccurrences(of: oldParentServerUrl, with: newServerUrl)
-                    childDirectory.serverUrl = movedServerUrl
-                    childDirectory.parentDirectoryServerUrl = movedParentServerUrl
-                    database.add(childDirectory, update: .all)
-                    NSLog("Moved childDirectory at %@ to %@", oldServerUrl, movedServerUrl)
-                }
-            }
-        } catch let error {
-            NSLog("Could not rename directory metadata with ocId: %@ serverUrl: %@, received error: %@", ocId, newServerUrl, error.localizedDescription)
-        }
-    }
-
-    func localFileMetadataFromOcId(_ ocId: String) -> NextcloudLocalFileMetadataTable? {
-        if let metadata = ncDatabase().objects(NextcloudLocalFileMetadataTable.self).filter("ocId == %@", ocId).first {
-            return NextcloudLocalFileMetadataTable(value: metadata)
-        }
-
-        return nil
-    }
-
-    func addLocalFileMetadataFromItemMetadata(_ itemMetadata: NextcloudItemMetadataTable) {
-        let database = ncDatabase()
-
-        do {
-            try database.write {
-                let newLocalFileMetadata = NextcloudLocalFileMetadataTable()
-
-                newLocalFileMetadata.ocId = itemMetadata.ocId
-                newLocalFileMetadata.fileName = itemMetadata.fileName
-                newLocalFileMetadata.account = itemMetadata.account
-                newLocalFileMetadata.etag = itemMetadata.etag
-                newLocalFileMetadata.exifDate = Date()
-                newLocalFileMetadata.exifLatitude = "-1"
-                newLocalFileMetadata.exifLongitude = "-1"
-
-                database.add(newLocalFileMetadata, update: .all)
-            }
-        } catch let error {
-            NSLog("Could not add local file metadata from item metadata with ocID: %@ and filename: %@, received error: %@", itemMetadata.ocId, itemMetadata.fileNameView, error.localizedDescription)
-        }
-    }
-
-    func deleteLocalFileMetadata(ocId: String) {
-        let database = ncDatabase()
-
-        do {
-            try database.write {
-                let results = database.objects(NextcloudLocalFileMetadataTable.self).filter("ocId == %@", ocId)
-                database.delete(results)
-            }
-        } catch let error {
-            NSLog("Could not delete local file metadata with ocId: %@, received error: %@", ocId, error.localizedDescription)
-        }
-    }
-
-    private func sortedLocalFileMetadatas(_ metadatas: Results<NextcloudLocalFileMetadataTable>) -> [NextcloudLocalFileMetadataTable] {
-        let sortedMetadatas = metadatas.sorted(byKeyPath: "fileName", ascending: true)
-        return Array(sortedMetadatas.map { NextcloudLocalFileMetadataTable(value: $0) })
-    }
-
-    func localFileMetadatas(account: String) -> [NextcloudLocalFileMetadataTable] {
-        let results = ncDatabase().objects(NextcloudLocalFileMetadataTable.self).filter("account == %@", account)
-        return sortedLocalFileMetadatas(results)
-    }
-
-    func localFileItemMetadatas(account: String) -> [NextcloudItemMetadataTable] {
-        let localFileMetadatas = localFileMetadatas(account: account)
-        let localFileMetadatasOcIds = Array(localFileMetadatas.map { $0.ocId })
-
-        var itemMetadatas: [NextcloudItemMetadataTable] = []
-
-        for ocId in localFileMetadatasOcIds {
-            guard let itemMetadata = itemMetadataFromOcId(ocId) else {
-                NSLog("Could not find matching item metadata for local file metadata with ocId: %@ with request from account: %@", ocId, account)
-                continue;
-            }
-
-            itemMetadatas.append(NextcloudItemMetadataTable(value: itemMetadata))
-        }
-
-        return itemMetadatas
-    }
-
-    func convertNKFileToItemMetadata(_ file: NKFile, account: String) -> NextcloudItemMetadataTable {
-
-        let metadata = NextcloudItemMetadataTable()
-
-        metadata.account = account
-        metadata.checksums = file.checksums
-        metadata.commentsUnread = file.commentsUnread
-        metadata.contentType = file.contentType
-        if let date = file.creationDate {
-            metadata.creationDate = date as Date
-        } else {
-            metadata.creationDate = file.date as Date
-        }
-        metadata.dataFingerprint = file.dataFingerprint
-        metadata.date = file.date as Date
-        metadata.directory = file.directory
-        metadata.downloadURL = file.downloadURL
-        metadata.e2eEncrypted = file.e2eEncrypted
-        metadata.etag = file.etag
-        metadata.favorite = file.favorite
-        metadata.fileId = file.fileId
-        metadata.fileName = file.fileName
-        metadata.fileNameView = file.fileName
-        metadata.hasPreview = file.hasPreview
-        metadata.iconName = file.iconName
-        metadata.mountType = file.mountType
-        metadata.name = file.name
-        metadata.note = file.note
-        metadata.ocId = file.ocId
-        metadata.ownerId = file.ownerId
-        metadata.ownerDisplayName = file.ownerDisplayName
-        metadata.lock = file.lock
-        metadata.lockOwner = file.lockOwner
-        metadata.lockOwnerEditor = file.lockOwnerEditor
-        metadata.lockOwnerType = file.lockOwnerType
-        metadata.lockOwnerDisplayName = file.lockOwnerDisplayName
-        metadata.lockTime = file.lockTime
-        metadata.lockTimeOut = file.lockTimeOut
-        metadata.path = file.path
-        metadata.permissions = file.permissions
-        metadata.quotaUsedBytes = file.quotaUsedBytes
-        metadata.quotaAvailableBytes = file.quotaAvailableBytes
-        metadata.richWorkspace = file.richWorkspace
-        metadata.resourceType = file.resourceType
-        metadata.serverUrl = file.serverUrl
-        metadata.sharePermissionsCollaborationServices = file.sharePermissionsCollaborationServices
-        for element in file.sharePermissionsCloudMesh {
-            metadata.sharePermissionsCloudMesh.append(element)
-        }
-        for element in file.shareType {
-            metadata.shareType.append(element)
-        }
-        metadata.size = file.size
-        metadata.classFile = file.classFile
-        //FIXME: iOS 12.0,* don't detect UTI text/markdown, text/x-markdown
-        if (metadata.contentType == "text/markdown" || metadata.contentType == "text/x-markdown") && metadata.classFile == NKCommon.TypeClassFile.unknow.rawValue {
-            metadata.classFile = NKCommon.TypeClassFile.document.rawValue
-        }
-        if let date = file.uploadDate {
-            metadata.uploadDate = date as Date
-        } else {
-            metadata.uploadDate = file.date as Date
-        }
-        metadata.urlBase = file.urlBase
-        metadata.user = file.user
-        metadata.userId = file.userId
-
-        // Support for finding the correct filename for e2ee files should go here
-
-        return metadata
-    }
-
-    func convertNKFilesFromDirectoryReadToItemMetadatas(_ files: [NKFile], account: String, completionHandler: @escaping (_ directoryMetadata: NextcloudItemMetadataTable, _ childDirectoriesMetadatas: [NextcloudItemMetadataTable], _ metadatas: [NextcloudItemMetadataTable]) -> Void) {
-
-        var directoryMetadataSet = false
-        var directoryMetadata = NextcloudItemMetadataTable()
-        var childDirectoriesMetadatas: [NextcloudItemMetadataTable] = []
-        var metadatas: [NextcloudItemMetadataTable] = []
-
-        for file in files {
-            let metadata = convertNKFileToItemMetadata(file, account: account)
-
-            if metadatas.isEmpty && !directoryMetadataSet {
-                directoryMetadata = metadata;
-                directoryMetadataSet = true;
-            } else {
-                metadatas.append(metadata)
-                if metadata.directory {
-                    childDirectoriesMetadatas.append(metadata)
-                }
-            }
-        }
-
-        completionHandler(directoryMetadata, childDirectoriesMetadatas, metadatas)
-    }
-}
diff --git a/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/NextcloudFilesDatabaseTables.swift b/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/NextcloudFilesDatabaseTables.swift
deleted file mode 100644 (file)
index 59d759d..0000000
+++ /dev/null
@@ -1,234 +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 Foundation
-import RealmSwift
-import FileProvider
-import NextcloudKit
-
-class NextcloudItemMetadataTable: Object {
-    enum Status: Int {
-        case downloadError = -4
-        case downloading = -3
-        case inDownload = -2
-        case waitDownload = -1
-
-        case normal = 0
-
-        case waitUpload = 1
-        case inUpload = 2
-        case uploading = 3
-        case uploadError = 4
-    }
-
-    enum SharePermissions: Int {
-        case readShare = 1
-        case updateShare = 2
-        case createShare = 4
-        case deleteShare = 8
-        case shareShare = 16
-
-        case maxFileShare = 19
-        case maxFolderShare = 31
-    }
-
-    override func isEqual(_ object: Any?) -> Bool {
-        if let object = object as? NextcloudItemMetadataTable {
-            return self.fileId == object.fileId &&
-                   self.account == object.account &&
-                   self.path == object.path &&
-                   self.fileName == object.fileName
-        }
-
-        return false
-    }
-
-    func isInSameDatabaseStoreableRemoteState(_ comparingMetadata: NextcloudItemMetadataTable) -> Bool {
-        return comparingMetadata.etag == self.etag &&
-            comparingMetadata.fileNameView == self.fileNameView &&
-            comparingMetadata.date == self.date &&
-            comparingMetadata.permissions == self.permissions &&
-            comparingMetadata.hasPreview == self.hasPreview &&
-            comparingMetadata.note == self.note &&
-            comparingMetadata.lock == self.lock &&
-            comparingMetadata.sharePermissionsCollaborationServices == self.sharePermissionsCollaborationServices &&
-            comparingMetadata.favorite == self.favorite
-    }
-
-    @Persisted(primaryKey: true) var ocId: String
-    @Persisted var account = ""
-    @Persisted var assetLocalIdentifier = ""
-    @Persisted var checksums = ""
-    @Persisted var chunk: Bool = false
-    @Persisted var classFile = ""
-    @Persisted var commentsUnread: Bool = false
-    @Persisted var contentType = ""
-    @Persisted var creationDate = Date()
-    @Persisted var dataFingerprint = ""
-    @Persisted var date = Date()
-    @Persisted var directory: Bool = false
-    @Persisted var deleteAssetLocalIdentifier: Bool = false
-    @Persisted var downloadURL = ""
-    @Persisted var e2eEncrypted: Bool = false
-    @Persisted var edited: Bool = false
-    @Persisted var etag = ""
-    @Persisted var etagResource = ""
-    @Persisted var favorite: Bool = false
-    @Persisted var fileId = ""
-    @Persisted var fileName = ""
-    @Persisted var fileNameView = ""
-    @Persisted var hasPreview: Bool = false
-    @Persisted var iconName = ""
-    @Persisted var iconUrl = ""
-    @Persisted var isExtractFile: Bool = false
-    @Persisted var livePhoto: Bool = false
-    @Persisted var mountType = ""
-    @Persisted var name = "" // for unifiedSearch is the provider.id
-    @Persisted var note = ""
-    @Persisted var ownerId = ""
-    @Persisted var ownerDisplayName = ""
-    @Persisted var lock = false
-    @Persisted var lockOwner = ""
-    @Persisted var lockOwnerEditor = ""
-    @Persisted var lockOwnerType = 0
-    @Persisted var lockOwnerDisplayName = ""
-    @Persisted var lockTime: Date?
-    @Persisted var lockTimeOut: Date?
-    @Persisted var path = ""
-    @Persisted var permissions = ""
-    @Persisted var quotaUsedBytes: Int64 = 0
-    @Persisted var quotaAvailableBytes: Int64 = 0
-    @Persisted var resourceType = ""
-    @Persisted var richWorkspace: String?
-    @Persisted var serverUrl = "" // For parent directory!!
-    @Persisted var session = ""
-    @Persisted var sessionError = ""
-    @Persisted var sessionSelector = ""
-    @Persisted var sessionTaskIdentifier: Int = 0
-    @Persisted var sharePermissionsCollaborationServices: Int = 0
-    let sharePermissionsCloudMesh = List<String>() // TODO: Find a way to compare these in remote state check
-    let shareType = List<Int>()
-    @Persisted var size: Int64 = 0
-    @Persisted var status: Int = 0
-    @Persisted var subline: String?
-    @Persisted var trashbinFileName = ""
-    @Persisted var trashbinOriginalLocation = ""
-    @Persisted var trashbinDeletionTime = Date()
-    @Persisted var uploadDate = Date()
-    @Persisted var url = ""
-    @Persisted var urlBase = ""
-    @Persisted var user = ""
-    @Persisted var userId = ""
-
-    var fileExtension: String {
-        (fileNameView as NSString).pathExtension
-    }
-
-    var fileNoExtension: String {
-        (fileNameView as NSString).deletingPathExtension
-    }
-
-    var isRenameable: Bool {
-        return lock
-    }
-
-    var isPrintable: Bool {
-        if isDocumentViewableOnly {
-            return false
-        }
-        if ["application/pdf", "com.adobe.pdf"].contains(contentType) || contentType.hasPrefix("text/") || classFile == NKCommon.TypeClassFile.image.rawValue {
-            return true
-        }
-        return false
-    }
-
-    var isDocumentViewableOnly: Bool {
-        return sharePermissionsCollaborationServices == SharePermissions.readShare.rawValue &&
-            classFile == NKCommon.TypeClassFile.document.rawValue
-    }
-
-    var isCopyableInPasteboard: Bool {
-        !isDocumentViewableOnly && !directory
-    }
-
-    var isModifiableWithQuickLook: Bool {
-        if directory || isDocumentViewableOnly {
-            return false
-        }
-        return contentType == "com.adobe.pdf" || contentType == "application/pdf" || classFile == NKCommon.TypeClassFile.image.rawValue
-    }
-
-    var isSettableOnOffline: Bool {
-        return session.isEmpty && !isDocumentViewableOnly
-    }
-
-    var canOpenIn: Bool {
-        return session.isEmpty && !isDocumentViewableOnly && !directory
-    }
-
-    var isDownloadUpload: Bool {
-        return status == Status.inDownload.rawValue ||
-            status == Status.downloading.rawValue ||
-            status == Status.inUpload.rawValue ||
-            status == Status.uploading.rawValue
-    }
-
-    var isDownload: Bool {
-        status == Status.inDownload.rawValue || status == Status.downloading.rawValue
-    }
-
-    var isUpload: Bool {
-        status == Status.inUpload.rawValue || status == Status.uploading.rawValue
-    }
-
-    /// Returns false if the user is lokced out of the file. I.e. The file is locked but by somone else
-    func canUnlock(as user: String) -> Bool {
-        return !lock || (lockOwner == user && lockOwnerType == 0)
-    }
-}
-
-class NextcloudDirectoryMetadataTable: Object {
-    func isInSameRemoteState(_ comparingMetadata: NextcloudDirectoryMetadataTable) -> Bool {
-        return comparingMetadata.etag == self.etag &&
-            comparingMetadata.e2eEncrypted == self.e2eEncrypted &&
-            comparingMetadata.favorite == self.favorite &&
-            comparingMetadata.permissions == self.permissions
-    }
-
-    @Persisted(primaryKey: true) var ocId: String
-    @Persisted var account = ""
-    @Persisted var colorFolder: String?
-    @Persisted var e2eEncrypted: Bool = false
-    @Persisted var etag = ""
-    @Persisted var favorite: Bool = false
-    @Persisted var fileId = ""
-    @Persisted var offline: Bool = false
-    @Persisted var permissions = ""
-    @Persisted var richWorkspace: String?
-    @Persisted var serverUrl = ""
-    @Persisted var parentDirectoryServerUrl = ""
-}
-
-class NextcloudLocalFileMetadataTable: Object {
-    @Persisted(primaryKey: true) var ocId: String
-    @Persisted var account = ""
-    @Persisted var etag = ""
-    @Persisted var exifDate: Date?
-    @Persisted var exifLatitude = ""
-    @Persisted var exifLongitude = ""
-    @Persisted var exifLensModel: String?
-    @Persisted var favorite: Bool = false
-    @Persisted var fileName = ""
-    @Persisted var offline: Bool = false
-}