2 * Copyright (C) 2022 by Claudio Cambra <claudio.cambra@nextcloud.com>
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
9 * This program is distributed in the hope that it will be useful, but
10 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
11 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
21 class NextcloudFilesDatabaseManager : NSObject {
23 return NextcloudFilesDatabaseManager();
26 let relativeDatabaseFolderPath = "Database/"
27 let databaseFilename = "fileproviderextdatabase.realm"
28 let relativeDatabaseFilePath: String
29 var databasePath: URL?
31 let schemaVersion: UInt64 = 100
34 self.relativeDatabaseFilePath = self.relativeDatabaseFolderPath + self.databaseFilename
36 guard let fileProviderDataDirUrl = pathForFileProviderExtData() else {
41 self.databasePath = fileProviderDataDirUrl.appendingPathComponent(self.relativeDatabaseFilePath)
43 // Disable file protection for directory DB
44 // https://docs.mongodb.com/realm/sdk/ios/examples/configure-and-open-a-realm/#std-label-ios-open-a-local-realm
45 let dbFolder = fileProviderDataDirUrl.appendingPathComponent(self.relativeDatabaseFolderPath)
46 let dbFolderPath = dbFolder.path
48 try FileManager.default.createDirectory(at: dbFolder, withIntermediateDirectories: true)
49 try FileManager.default.setAttributes([FileAttributeKey.protectionKey: FileProtectionType.completeUntilFirstUserAuthentication], ofItemAtPath: dbFolderPath)
51 Logger.ncFilesDatabase.error("Could not set permission level for File Provider database folder, received error: \(error.localizedDescription, privacy: .public)")
54 let config = Realm.Configuration(
55 fileURL: self.databasePath,
56 schemaVersion: self.schemaVersion,
57 objectTypes: [NextcloudItemMetadataTable.self, NextcloudDirectoryMetadataTable.self, NextcloudLocalFileMetadataTable.self]
60 Realm.Configuration.defaultConfiguration = config
64 Logger.ncFilesDatabase.info("Successfully started Realm db for FileProviderExt")
65 } catch let error as NSError {
66 Logger.ncFilesDatabase.error("Error opening Realm db: \(error.localizedDescription, privacy: .public)")
72 private func ncDatabase() -> Realm {
73 let realm = try! Realm()
78 func anyItemMetadatasForAccount(_ account: String) -> Bool {
79 return !ncDatabase().objects(NextcloudItemMetadataTable.self).filter("account == %@", account).isEmpty
82 func itemMetadataFromOcId(_ ocId: String) -> NextcloudItemMetadataTable? {
83 // Realm objects are live-fire, i.e. they will be changed and invalidated according to changes in the db
84 // Let's therefore create a copy
85 if let itemMetadata = ncDatabase().objects(NextcloudItemMetadataTable.self).filter("ocId == %@", ocId).first {
86 return NextcloudItemMetadataTable(value: itemMetadata)
92 private func sortedItemMetadatas(_ metadatas: Results<NextcloudItemMetadataTable>) -> [NextcloudItemMetadataTable] {
93 let sortedMetadatas = metadatas.sorted(byKeyPath: "fileName", ascending: true)
94 return Array(sortedMetadatas.map { NextcloudItemMetadataTable(value: $0) })
97 func itemMetadatas(account: String) -> [NextcloudItemMetadataTable] {
98 let metadatas = ncDatabase().objects(NextcloudItemMetadataTable.self).filter("account == %@", account)
99 return sortedItemMetadatas(metadatas)
102 func itemMetadatas(account: String, serverUrl: String) -> [NextcloudItemMetadataTable] {
103 let metadatas = ncDatabase().objects(NextcloudItemMetadataTable.self).filter("account == %@ AND serverUrl == %@", account, serverUrl)
104 return sortedItemMetadatas(metadatas)
107 func itemMetadatas(account: String, serverUrl: String, status: NextcloudItemMetadataTable.Status) -> [NextcloudItemMetadataTable] {
108 let metadatas = ncDatabase().objects(NextcloudItemMetadataTable.self).filter("account == %@ AND serverUrl == %@ AND status == %@", account, serverUrl, status.rawValue)
109 return sortedItemMetadatas(metadatas)
112 func itemMetadataFromFileProviderItemIdentifier(_ identifier: NSFileProviderItemIdentifier) -> NextcloudItemMetadataTable? {
113 let ocId = identifier.rawValue
114 return itemMetadataFromOcId(ocId)
117 private func processItemMetadatasToDelete(databaseToWriteTo: Realm,
118 existingMetadatas: Results<NextcloudItemMetadataTable>,
119 updatedMetadatas: [NextcloudItemMetadataTable]) -> [NextcloudItemMetadataTable] {
121 assert(databaseToWriteTo.isInWriteTransaction)
123 var deletedMetadatas: [NextcloudItemMetadataTable] = []
125 for existingMetadata in existingMetadatas {
126 guard !updatedMetadatas.contains(where: { $0.ocId == existingMetadata.ocId }),
127 let metadataToDelete = itemMetadataFromOcId(existingMetadata.ocId) else { continue }
129 deletedMetadatas.append(metadataToDelete)
131 Logger.ncFilesDatabase.debug("Deleting item metadata during update. ocID: \(existingMetadata.ocId, privacy: .public), etag: \(existingMetadata.etag, privacy: .public), fileName: \(existingMetadata.fileName, privacy: OSLogPrivacy.auto(mask: .hash))")
133 // Can't pass copies, we need the originals from the database
134 databaseToWriteTo.delete(ncDatabase().objects(NextcloudItemMetadataTable.self).filter("ocId == %@", metadataToDelete.ocId))
137 return deletedMetadatas
140 private func processItemMetadatasToUpdate(databaseToWriteTo: Realm,
141 existingMetadatas: Results<NextcloudItemMetadataTable>,
142 updatedMetadatas: [NextcloudItemMetadataTable]) -> (newMetadatas: [NextcloudItemMetadataTable], updatedMetadatas: [NextcloudItemMetadataTable]) {
144 assert(databaseToWriteTo.isInWriteTransaction)
146 var returningNewMetadatas: [NextcloudItemMetadataTable] = []
147 var returningUpdatedMetadatas: [NextcloudItemMetadataTable] = []
149 for updatedMetadata in updatedMetadatas {
150 if let existingMetadata = existingMetadatas.first(where: { $0.ocId == updatedMetadata.ocId }) {
152 if existingMetadata.status == NextcloudItemMetadataTable.Status.normal.rawValue &&
153 !existingMetadata.isInSameDatabaseStoreableRemoteState(updatedMetadata) {
155 returningUpdatedMetadatas.append(NextcloudItemMetadataTable(value: updatedMetadata))
156 databaseToWriteTo.add(updatedMetadata, update: .all)
158 Logger.ncFilesDatabase.debug("Updated existing item metadata. ocID: \(updatedMetadata.ocId, privacy: .public), etag: \(updatedMetadata.etag, privacy: .public), fileName: \(updatedMetadata.fileName, privacy: OSLogPrivacy.auto(mask: .hash))")
160 Logger.ncFilesDatabase.debug("Skipping item metadata update; same as existing, or still downloading/uploading. ocID: \(updatedMetadata.ocId, privacy: .public), etag: \(updatedMetadata.etag, privacy: .public), fileName: \(updatedMetadata.fileName, privacy: OSLogPrivacy.auto(mask: .hash))")
163 } else { // This is a new metadata
164 returningNewMetadatas.append(NextcloudItemMetadataTable(value: updatedMetadata))
165 databaseToWriteTo.add(updatedMetadata, update: .all)
167 Logger.ncFilesDatabase.debug("Created new item metadata during update. ocID: \(updatedMetadata.ocId, privacy: .public), etag: \(updatedMetadata.etag, privacy: .public), fileName: \(updatedMetadata.fileName, privacy: OSLogPrivacy.auto(mask: .hash))")
171 return (returningNewMetadatas, returningUpdatedMetadatas)
174 func updateItemMetadatas(account: String, serverUrl: String, updatedMetadatas: [NextcloudItemMetadataTable], completionHandler: @escaping(_ newMetadatas: [NextcloudItemMetadataTable]?, _ updatedMetadatas: [NextcloudItemMetadataTable]?, _ deletedMetadatas: [NextcloudItemMetadataTable]?) -> Void) {
175 let database = ncDatabase()
179 let existingMetadatas = database.objects(NextcloudItemMetadataTable.self).filter("account == %@ AND serverUrl == %@ AND status == %@", account, serverUrl, NextcloudItemMetadataTable.Status.normal.rawValue)
181 let deletedMetadatas = processItemMetadatasToDelete(databaseToWriteTo: database,
182 existingMetadatas: existingMetadatas,
183 updatedMetadatas: updatedMetadatas)
185 let metadatasFromUpdate = processItemMetadatasToUpdate(databaseToWriteTo: database,
186 existingMetadatas: existingMetadatas,
187 updatedMetadatas: updatedMetadatas)
189 completionHandler(metadatasFromUpdate.newMetadatas, metadatasFromUpdate.updatedMetadatas, deletedMetadatas)
192 Logger.ncFilesDatabase.error("Could not update any item metadatas, received error: \(error.localizedDescription, privacy: .public)")
193 completionHandler(nil, nil, nil)
197 func setStatusForItemMetadata(_ metadata: NextcloudItemMetadataTable, status: NextcloudItemMetadataTable.Status, completionHandler: @escaping(_ updatedMetadata: NextcloudItemMetadataTable?) -> Void) {
198 let database = ncDatabase()
202 guard let result = database.objects(NextcloudItemMetadataTable.self).filter("ocId == %@", metadata.ocId).first else {
203 Logger.ncFilesDatabase.debug("Did not update status for item metadata as it was not found. ocID: \(metadata.ocId, privacy: .public)")
207 result.status = status.rawValue
208 database.add(result, update: .all)
209 Logger.ncFilesDatabase.debug("Updated status for item metadata. ocID: \(metadata.ocId, privacy: .public), etag: \(metadata.etag, privacy: .public), fileName: \(metadata.fileName, privacy: OSLogPrivacy.auto(mask: .hash))")
211 completionHandler(NextcloudItemMetadataTable(value: result))
214 Logger.ncFilesDatabase.error("Could not update status for item metadata with ocID: \(metadata.ocId, privacy: .public), etag: \(metadata.etag, privacy: .public), fileName: \(metadata.fileName, privacy: OSLogPrivacy.auto(mask: .hash)), received error: \(error.localizedDescription, privacy: .public)")
215 completionHandler(nil)
219 func addItemMetadata(_ metadata: NextcloudItemMetadataTable) {
220 let database = ncDatabase()
224 database.add(metadata, update: .all)
225 Logger.ncFilesDatabase.debug("Added item metadata. ocID: \(metadata.ocId, privacy: .public), etag: \(metadata.etag, privacy: .public), fileName: \(metadata.fileName, privacy: OSLogPrivacy.auto(mask: .hash))")
228 Logger.ncFilesDatabase.error("Could not add item metadata. ocID: \(metadata.ocId, privacy: .public), etag: \(metadata.etag, privacy: .public), fileName: \(metadata.fileName, privacy: OSLogPrivacy.auto(mask: .hash)), received error: \(error.localizedDescription, privacy: .public)")
232 func deleteItemMetadata(ocId: String) {
233 let database = ncDatabase()
237 let results = database.objects(NextcloudItemMetadataTable.self).filter("ocId == %@", ocId)
239 Logger.ncFilesDatabase.debug("Deleting item metadata. \(ocId, privacy: .public)")
240 database.delete(results)
243 Logger.ncFilesDatabase.error("Could not delete item metadata with ocId: \(ocId, privacy: .public), received error: \(error.localizedDescription, privacy: .public)")
247 func renameItemMetadata(ocId: String, newServerUrl: String, newFileName: String) {
248 let database = ncDatabase()
252 guard let itemMetadata = database.objects(NextcloudItemMetadataTable.self).filter("ocId == %@", ocId).first else {
253 Logger.ncFilesDatabase.debug("Could not find an item with ocID \(ocId, privacy: .public) to rename to \(newFileName, privacy: OSLogPrivacy.auto(mask: .hash))")
257 let oldFileName = itemMetadata.fileName
258 let oldServerUrl = itemMetadata.serverUrl
260 itemMetadata.fileName = newFileName
261 itemMetadata.fileNameView = newFileName
262 itemMetadata.serverUrl = newServerUrl
264 database.add(itemMetadata, update: .all)
266 Logger.ncFilesDatabase.debug("Renamed item \(oldFileName, privacy: OSLogPrivacy.auto(mask: .hash)) to \(newFileName, privacy: OSLogPrivacy.auto(mask: .hash)), moved from serverUrl: \(oldServerUrl, privacy: OSLogPrivacy.auto(mask: .hash)) to serverUrl: \(newServerUrl, privacy: OSLogPrivacy.auto(mask: .hash))")
269 Logger.ncFilesDatabase.error("Could not rename filename of item metadata with ocID: \(ocId, privacy: .public) to proposed name \(newFileName, privacy: OSLogPrivacy.auto(mask: .hash)) at proposed serverUrl \(newServerUrl, privacy: OSLogPrivacy.auto(mask: .hash)), received error: \(error.localizedDescription, privacy: .public)")
273 func parentItemIdentifierFromMetadata(_ metadata: NextcloudItemMetadataTable) -> NSFileProviderItemIdentifier? {
274 let homeServerFilesUrl = metadata.urlBase + "/remote.php/dav/files/" + metadata.userId
276 if metadata.serverUrl == homeServerFilesUrl {
277 return .rootContainer
280 guard let itemParentDirectory = parentDirectoryMetadataForItem(metadata) else {
281 Logger.ncFilesDatabase.error("Could not get item parent directory metadata for metadata. ocID: \(metadata.ocId, privacy: .public), etag: \(metadata.etag, privacy: .public), fileName: \(metadata.fileName, privacy: OSLogPrivacy.auto(mask: .hash))")
285 if let parentDirectoryMetadata = itemMetadataFromOcId(itemParentDirectory.ocId) {
286 return NSFileProviderItemIdentifier(parentDirectoryMetadata.ocId)
289 Logger.ncFilesDatabase.error("Could not get item parent directory item metadata for metadata. ocID: \(metadata.ocId, privacy: .public), etag: \(metadata.etag, privacy: .public), fileName: \(metadata.fileName, privacy: OSLogPrivacy.auto(mask: .hash))")
293 func directoryMetadata(account: String, serverUrl: String) -> NextcloudDirectoryMetadataTable? {
294 if let metadata = ncDatabase().objects(NextcloudDirectoryMetadataTable.self).filter("account == %@ AND serverUrl == %@", account, serverUrl).first {
295 return NextcloudDirectoryMetadataTable(value: metadata)
301 func directoryMetadata(ocId: String) -> NextcloudDirectoryMetadataTable? {
302 if let metadata = ncDatabase().objects(NextcloudDirectoryMetadataTable.self).filter("ocId == %@", ocId).first {
303 return NextcloudDirectoryMetadataTable(value: metadata)
309 private func sortedDirectoryMetadatas(_ metadatas: Results<NextcloudDirectoryMetadataTable>) -> [NextcloudDirectoryMetadataTable] {
310 let sortedMetadatas = metadatas.sorted(byKeyPath: "serverUrl", ascending: true)
311 return Array(sortedMetadatas.map { NextcloudDirectoryMetadataTable(value: $0) })
314 func childDirectoriesForDirectory(_ directoryMetadata: NextcloudDirectoryMetadataTable) -> [NextcloudDirectoryMetadataTable] {
315 let metadatas = ncDatabase().objects(NextcloudDirectoryMetadataTable.self).filter("serverUrl BEGINSWITH %@ AND ocId != %@", directoryMetadata.serverUrl, directoryMetadata.account)
316 return sortedDirectoryMetadatas(metadatas)
319 func parentDirectoryMetadataForItem(_ itemMetadata: NextcloudItemMetadataTable) -> NextcloudDirectoryMetadataTable? {
320 return directoryMetadata(account: itemMetadata.account, serverUrl: itemMetadata.serverUrl)
323 func directoryMetadatas(account: String) -> [NextcloudDirectoryMetadataTable] {
324 let metadatas = ncDatabase().objects(NextcloudDirectoryMetadataTable.self).filter("account == %@", account)
325 return sortedDirectoryMetadatas(metadatas)
328 func directoryMetadatas(account: String, parentDirectoryServerUrl: String) -> [NextcloudDirectoryMetadataTable] {
329 let metadatas = ncDatabase().objects(NextcloudDirectoryMetadataTable.self).filter("account == %@ AND parentDirectoryServerUrl == %@", account, parentDirectoryServerUrl)
330 return sortedDirectoryMetadatas(metadatas)
333 private func processDirectoryMetadatasToDelete(databaseToWriteTo: Realm,
334 existingDirectoryMetadatas: Results<NextcloudDirectoryMetadataTable>,
335 updatedDirectoryMetadatas: [NextcloudDirectoryMetadataTable]) {
337 for existingMetadata in existingDirectoryMetadatas {
338 guard !updatedDirectoryMetadatas.contains(where: { $0.ocId == existingMetadata.ocId }),
339 let metadataToDelete = directoryMetadata(ocId: existingMetadata.ocId) else { continue }
341 Logger.ncFilesDatabase.debug("Deleting directory metadata during update. ocID: \(existingMetadata.ocId, privacy: .public), etag: \(existingMetadata.etag, privacy: .public), serverUrl: \(existingMetadata.serverUrl)")
343 self.deleteDirectoryAndSubdirectoriesMetadata(ocId: metadataToDelete.ocId)
347 private func processDirectoryMetadatasToUpdate(databaseToWriteTo: Realm,
348 existingDirectoryMetadatas: Results<NextcloudDirectoryMetadataTable>,
349 updatedDirectoryMetadatas: [NextcloudDirectoryMetadataTable]) {
351 assert(databaseToWriteTo.isInWriteTransaction)
353 for updatedMetadata in updatedDirectoryMetadatas {
354 if let existingMetadata = existingDirectoryMetadatas.first(where: { $0.ocId == updatedMetadata.ocId }) {
356 if !existingMetadata.isInSameRemoteState(updatedMetadata) {
358 databaseToWriteTo.add(NextcloudDirectoryMetadataTable(value: updatedMetadata), update: .all)
359 Logger.ncFilesDatabase.debug("Updated existing directory metadata. ocID: \(updatedMetadata.ocId, privacy: .public), etag: \(updatedMetadata.etag, privacy: .public), serverUrl: \(updatedMetadata.serverUrl)")
361 // Don't update under other circumstances in which the metadata already exists
363 } else { // This is a new metadata
364 databaseToWriteTo.add(NextcloudDirectoryMetadataTable(value: updatedMetadata), update: .all)
365 Logger.ncFilesDatabase.debug("Created new directory metadata during update. ocID: \(updatedMetadata.ocId, privacy: .public), etag: \(updatedMetadata.etag, privacy: .public), serverUrl: \(updatedMetadata.serverUrl)")
370 func updateDirectoryMetadatas(account: String, parentDirectoryServerUrl: String, updatedDirectoryMetadatas: [NextcloudDirectoryMetadataTable]) {
371 let database = ncDatabase()
373 let existingDirectoryMetadatas = ncDatabase().objects(NextcloudDirectoryMetadataTable.self).filter("account == %@ AND parentDirectoryServerUrl == %@", account, parentDirectoryServerUrl)
375 // Actual db writing handled internally
376 processDirectoryMetadatasToDelete(databaseToWriteTo: database,
377 existingDirectoryMetadatas: existingDirectoryMetadatas,
378 updatedDirectoryMetadatas: updatedDirectoryMetadatas)
383 processDirectoryMetadatasToUpdate(databaseToWriteTo: database,
384 existingDirectoryMetadatas: existingDirectoryMetadatas,
385 updatedDirectoryMetadatas: updatedDirectoryMetadatas)
388 Logger.ncFilesDatabase.error("Could not update directory metadatas, received error: \(error.localizedDescription)")
392 func directoryMetadataFromItemMetadata(directoryItemMetadata: NextcloudItemMetadataTable, recordEtag: Bool = false) -> NextcloudDirectoryMetadataTable {
393 var newDirectoryMetadata = NextcloudDirectoryMetadataTable()
394 let directoryOcId = directoryItemMetadata.ocId
396 if let existingDirectoryMetadata = directoryMetadata(ocId: directoryOcId) {
397 newDirectoryMetadata = existingDirectoryMetadata
401 newDirectoryMetadata.etag = directoryItemMetadata.etag
404 newDirectoryMetadata.ocId = directoryOcId
405 newDirectoryMetadata.fileId = directoryItemMetadata.fileId
406 newDirectoryMetadata.parentDirectoryServerUrl = directoryItemMetadata.serverUrl
407 newDirectoryMetadata.serverUrl = directoryItemMetadata.serverUrl + "/" + directoryItemMetadata.fileNameView
408 newDirectoryMetadata.account = directoryItemMetadata.account
409 newDirectoryMetadata.e2eEncrypted = directoryItemMetadata.e2eEncrypted
410 newDirectoryMetadata.favorite = directoryItemMetadata.favorite
411 newDirectoryMetadata.permissions = directoryItemMetadata.permissions
413 return newDirectoryMetadata
416 func updateDirectoryMetadatasFromItemMetadatas(account: String, parentDirectoryServerUrl: String, updatedDirectoryItemMetadatas: [NextcloudItemMetadataTable], recordEtag: Bool = false) {
418 var updatedDirMetadatas: [NextcloudDirectoryMetadataTable] = []
420 for directoryItemMetadata in updatedDirectoryItemMetadatas {
421 let newDirectoryMetadata = directoryMetadataFromItemMetadata(directoryItemMetadata: directoryItemMetadata, recordEtag: recordEtag)
422 updatedDirMetadatas.append(newDirectoryMetadata)
425 updateDirectoryMetadatas(account: account, parentDirectoryServerUrl: parentDirectoryServerUrl, updatedDirectoryMetadatas: updatedDirMetadatas)
428 func addDirectoryMetadata(_ metadata: NextcloudDirectoryMetadataTable) {
429 let database = ncDatabase()
433 database.add(metadata, update: .all)
434 Logger.ncFilesDatabase.debug("Added new directory metadata. ocId: \(metadata.ocId, privacy: .public), etag: \(metadata.etag, privacy: .public), serverUrl: \(metadata.serverUrl)")
437 Logger.ncFilesDatabase.error("Could not add new directory metadata. ocId: \(metadata.ocId, privacy: .public), etag: \(metadata.etag, privacy: .public), serverUrl: \(metadata.serverUrl), received error: \(error.localizedDescription, privacy: .public)")
441 // Deletes all metadatas related to the info of the directory provided
442 func deleteDirectoryAndSubdirectoriesMetadata(ocId: String) {
443 let database = ncDatabase()
444 guard let directoryMetadata = database.objects(NextcloudDirectoryMetadataTable.self).filter("ocId == %@", ocId).first else {
445 Logger.ncFilesDatabase.error("Could not find directory metadata for ocId \(ocId, privacy: .public). Not proceeding with deletion")
449 let results = database.objects(NextcloudDirectoryMetadataTable.self).filter("account == %@ AND serverUrl BEGINSWITH %@", directoryMetadata.account, directoryMetadata.serverUrl)
451 for result in results {
452 deleteItemMetadata(ocId: result.ocId)
453 deleteLocalFileMetadata(ocId: result.ocId)
458 Logger.ncFilesDatabase.debug("Deleting root directory metadata in recursive delete. ocID: \(directoryMetadata.ocId, privacy: .public), etag: \(directoryMetadata.etag, privacy: .public), serverUrl: \(directoryMetadata.serverUrl)")
459 database.delete(results)
462 Logger.ncFilesDatabase.error("Could not delete root directory metadata in recursive delete. ocID: \(directoryMetadata.ocId, privacy: .public), etag: \(directoryMetadata.etag, privacy: .public), serverUrl: \(directoryMetadata.serverUrl), received error: \(error.localizedDescription, privacy: .public)")
466 func renameDirectoryAndPropagateToChildren(ocId: String, newServerUrl: String, newFileName: String) {
468 let database = ncDatabase()
472 guard let directoryTableResult = database.objects(NextcloudDirectoryMetadataTable.self).filter("ocId == %@", ocId).first,
473 let directoryItemResult = database.objects(NextcloudItemMetadataTable.self).filter("ocId == %@", ocId).first else {
474 Logger.ncFilesDatabase.error("Could not find a directory with ocID \(ocId, privacy: .public), cannot proceed with recursive renaming")
478 let oldServerUrl = directoryTableResult.serverUrl
480 let childItemResults = database.objects(NextcloudItemMetadataTable.self).filter("account == %@ AND serverUrl BEGINSWITH %@", directoryTableResult.account, oldServerUrl)
481 let childDirectoryResults = database.objects(NextcloudDirectoryMetadataTable.self).filter("account == %@ AND serverUrl BEGINSWITH %@", directoryTableResult.account, oldServerUrl)
483 directoryTableResult.serverUrl = newServerUrl
484 database.add(directoryTableResult, update: .all)
485 directoryItemResult.fileName = newFileName
486 directoryItemResult.fileNameView = newFileName
487 database.add(directoryItemResult, update: .all)
488 Logger.ncFilesDatabase.debug("Renamed root renaming directory at \(oldServerUrl) to \(newServerUrl)")
490 for childItem in childItemResults {
491 let oldServerUrl = childItem.serverUrl
492 let movedServerUrl = oldServerUrl.replacingOccurrences(of: oldServerUrl, with: newServerUrl)
493 childItem.serverUrl = movedServerUrl
494 database.add(childItem, update: .all)
495 Logger.ncFilesDatabase.debug("Moved childItem at \(oldServerUrl) to \(movedServerUrl)")
498 for childDirectory in childDirectoryResults {
499 let oldServerUrl = childDirectory.serverUrl
500 let oldParentServerUrl = childDirectory.parentDirectoryServerUrl
501 let movedServerUrl = oldServerUrl.replacingOccurrences(of: oldServerUrl, with: newServerUrl)
502 let movedParentServerUrl = oldServerUrl.replacingOccurrences(of: oldParentServerUrl, with: newServerUrl)
503 childDirectory.serverUrl = movedServerUrl
504 childDirectory.parentDirectoryServerUrl = movedParentServerUrl
505 database.add(childDirectory, update: .all)
506 Logger.ncFilesDatabase.debug("Moved childDirectory at \(oldServerUrl) to \(movedServerUrl)")
510 Logger.ncFilesDatabase.error("Could not rename directory metadata with ocId: \(ocId, privacy: .public) to new serverUrl: \(newServerUrl), received error: \(error.localizedDescription, privacy: .public)")
514 func localFileMetadataFromOcId(_ ocId: String) -> NextcloudLocalFileMetadataTable? {
515 if let metadata = ncDatabase().objects(NextcloudLocalFileMetadataTable.self).filter("ocId == %@", ocId).first {
516 return NextcloudLocalFileMetadataTable(value: metadata)
522 func addLocalFileMetadataFromItemMetadata(_ itemMetadata: NextcloudItemMetadataTable) {
523 let database = ncDatabase()
527 let newLocalFileMetadata = NextcloudLocalFileMetadataTable()
529 newLocalFileMetadata.ocId = itemMetadata.ocId
530 newLocalFileMetadata.fileName = itemMetadata.fileName
531 newLocalFileMetadata.account = itemMetadata.account
532 newLocalFileMetadata.etag = itemMetadata.etag
533 newLocalFileMetadata.exifDate = Date()
534 newLocalFileMetadata.exifLatitude = "-1"
535 newLocalFileMetadata.exifLongitude = "-1"
537 database.add(newLocalFileMetadata, update: .all)
538 Logger.ncFilesDatabase.debug("Added local file metadata from item metadata. ocID: \(itemMetadata.ocId, privacy: .public), etag: \(itemMetadata.etag, privacy: .public), fileName: \(itemMetadata.fileName, privacy: OSLogPrivacy.auto(mask: .hash))")
541 Logger.ncFilesDatabase.error("Could not add local file metadata from item metadata. ocID: \(itemMetadata.ocId, privacy: .public), etag: \(itemMetadata.etag, privacy: .public), fileName: \(itemMetadata.fileName, privacy: OSLogPrivacy.auto(mask: .hash)), received error: \(error.localizedDescription, privacy: .public)")
545 func deleteLocalFileMetadata(ocId: String) {
546 let database = ncDatabase()
550 let results = database.objects(NextcloudLocalFileMetadataTable.self).filter("ocId == %@", ocId)
551 database.delete(results)
554 Logger.ncFilesDatabase.error("Could not delete local file metadata with ocId: \(ocId, privacy: .public), received error: \(error.localizedDescription, privacy: .public)")
558 private func sortedLocalFileMetadatas(_ metadatas: Results<NextcloudLocalFileMetadataTable>) -> [NextcloudLocalFileMetadataTable] {
559 let sortedMetadatas = metadatas.sorted(byKeyPath: "fileName", ascending: true)
560 return Array(sortedMetadatas.map { NextcloudLocalFileMetadataTable(value: $0) })
563 func localFileMetadatas(account: String) -> [NextcloudLocalFileMetadataTable] {
564 let results = ncDatabase().objects(NextcloudLocalFileMetadataTable.self).filter("account == %@", account)
565 return sortedLocalFileMetadatas(results)
568 func localFileItemMetadatas(account: String) -> [NextcloudItemMetadataTable] {
569 let localFileMetadatas = localFileMetadatas(account: account)
570 let localFileMetadatasOcIds = Array(localFileMetadatas.map { $0.ocId })
572 var itemMetadatas: [NextcloudItemMetadataTable] = []
574 for ocId in localFileMetadatasOcIds {
575 guard let itemMetadata = itemMetadataFromOcId(ocId) else {
576 Logger.ncFilesDatabase.error("Could not find matching item metadata for local file metadata with ocId: \(ocId, privacy: .public) with request from account: \(account)")
580 itemMetadatas.append(NextcloudItemMetadataTable(value: itemMetadata))
586 func convertNKFileToItemMetadata(_ file: NKFile, account: String) -> NextcloudItemMetadataTable {
588 let metadata = NextcloudItemMetadataTable()
590 metadata.account = account
591 metadata.checksums = file.checksums
592 metadata.commentsUnread = file.commentsUnread
593 metadata.contentType = file.contentType
594 if let date = file.creationDate {
595 metadata.creationDate = date as Date
597 metadata.creationDate = file.date as Date
599 metadata.dataFingerprint = file.dataFingerprint
600 metadata.date = file.date as Date
601 metadata.directory = file.directory
602 metadata.downloadURL = file.downloadURL
603 metadata.e2eEncrypted = file.e2eEncrypted
604 metadata.etag = file.etag
605 metadata.favorite = file.favorite
606 metadata.fileId = file.fileId
607 metadata.fileName = file.fileName
608 metadata.fileNameView = file.fileName
609 metadata.hasPreview = file.hasPreview
610 metadata.iconName = file.iconName
611 metadata.mountType = file.mountType
612 metadata.name = file.name
613 metadata.note = file.note
614 metadata.ocId = file.ocId
615 metadata.ownerId = file.ownerId
616 metadata.ownerDisplayName = file.ownerDisplayName
617 metadata.lock = file.lock
618 metadata.lockOwner = file.lockOwner
619 metadata.lockOwnerEditor = file.lockOwnerEditor
620 metadata.lockOwnerType = file.lockOwnerType
621 metadata.lockOwnerDisplayName = file.lockOwnerDisplayName
622 metadata.lockTime = file.lockTime
623 metadata.lockTimeOut = file.lockTimeOut
624 metadata.path = file.path
625 metadata.permissions = file.permissions
626 metadata.quotaUsedBytes = file.quotaUsedBytes
627 metadata.quotaAvailableBytes = file.quotaAvailableBytes
628 metadata.richWorkspace = file.richWorkspace
629 metadata.resourceType = file.resourceType
630 metadata.serverUrl = file.serverUrl
631 metadata.sharePermissionsCollaborationServices = file.sharePermissionsCollaborationServices
632 for element in file.sharePermissionsCloudMesh {
633 metadata.sharePermissionsCloudMesh.append(element)
635 for element in file.shareType {
636 metadata.shareType.append(element)
638 metadata.size = file.size
639 metadata.classFile = file.classFile
640 //FIXME: iOS 12.0,* don't detect UTI text/markdown, text/x-markdown
641 if (metadata.contentType == "text/markdown" || metadata.contentType == "text/x-markdown") && metadata.classFile == NKCommon.TypeClassFile.unknow.rawValue {
642 metadata.classFile = NKCommon.TypeClassFile.document.rawValue
644 if let date = file.uploadDate {
645 metadata.uploadDate = date as Date
647 metadata.uploadDate = file.date as Date
649 metadata.urlBase = file.urlBase
650 metadata.user = file.user
651 metadata.userId = file.userId
653 // Support for finding the correct filename for e2ee files should go here
658 func convertNKFilesFromDirectoryReadToItemMetadatas(_ files: [NKFile], account: String, completionHandler: @escaping (_ directoryMetadata: NextcloudItemMetadataTable, _ childDirectoriesMetadatas: [NextcloudItemMetadataTable], _ metadatas: [NextcloudItemMetadataTable]) -> Void) {
660 var directoryMetadataSet = false
661 var directoryMetadata = NextcloudItemMetadataTable()
662 var childDirectoriesMetadatas: [NextcloudItemMetadataTable] = []
663 var metadatas: [NextcloudItemMetadataTable] = []
666 let metadata = convertNKFileToItemMetadata(file, account: account)
668 if metadatas.isEmpty && !directoryMetadataSet {
669 directoryMetadata = metadata;
670 directoryMetadataSet = true;
672 metadatas.append(metadata)
673 if metadata.directory {
674 childDirectoriesMetadatas.append(metadata)
679 completionHandler(directoryMetadata, childDirectoriesMetadatas, metadatas)