From: Claudio Cambra Date: Mon, 13 Mar 2023 11:30:27 +0000 (+0100) Subject: Move file provider database files into own folder X-Git-Tag: archive/raspbian/3.16.7-1_deb13u1+rpi1~1^2~12^2~10^2~55^2~139 X-Git-Url: https://dgit.raspbian.org/?a=commitdiff_plain;h=3ec9968fc216c92be2eb7f3450da55f6f747b40a;p=nextcloud-desktop.git Move file provider database files into own folder Signed-off-by: Claudio Cambra --- diff --git a/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/Database/NextcloudFilesDatabaseManager.swift b/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/Database/NextcloudFilesDatabaseManager.swift new file mode 100644 index 000000000..dac9ec41c --- /dev/null +++ b/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/Database/NextcloudFilesDatabaseManager.swift @@ -0,0 +1,710 @@ +/* + * Copyright (C) 2022 by Claudio Cambra + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +import 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] { + 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, + 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, + 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] { + 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, + 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, + 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] { + 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 index 000000000..59d759d0b --- /dev/null +++ b/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/Database/NextcloudFilesDatabaseTables.swift @@ -0,0 +1,234 @@ +/* + * Copyright (C) 2022 by Claudio Cambra + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +import 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() // TODO: Find a way to compare these in remote state check + let shareType = List() + @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 index dac9ec41c..000000000 --- a/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/NextcloudFilesDatabaseManager.swift +++ /dev/null @@ -1,710 +0,0 @@ -/* - * Copyright (C) 2022 by Claudio Cambra - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY - * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * for more details. - */ - -import 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] { - 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, - 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, - 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] { - 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, - 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, - 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] { - 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 index 59d759d0b..000000000 --- a/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/NextcloudFilesDatabaseTables.swift +++ /dev/null @@ -1,234 +0,0 @@ -/* - * Copyright (C) 2022 by Claudio Cambra - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY - * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * for more details. - */ - -import 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() // TODO: Find a way to compare these in remote state check - let shareType = List() - @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 -}