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, 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(existingMetadatas: Results<NextcloudItemMetadataTable>,
118 updatedMetadatas: [NextcloudItemMetadataTable]) -> [NextcloudItemMetadataTable] {
120 var deletedMetadatas: [NextcloudItemMetadataTable] = []
122 for existingMetadata in existingMetadatas {
123 guard !updatedMetadatas.contains(where: { $0.ocId == existingMetadata.ocId }),
124 let metadataToDelete = itemMetadataFromOcId(existingMetadata.ocId) else { continue }
126 deletedMetadatas.append(metadataToDelete)
128 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))")
131 return deletedMetadatas
134 private func processItemMetadatasToUpdate(existingMetadatas: Results<NextcloudItemMetadataTable>,
135 updatedMetadatas: [NextcloudItemMetadataTable],
136 updateDirectoryEtags: Bool) -> (newMetadatas: [NextcloudItemMetadataTable], updatedMetadatas: [NextcloudItemMetadataTable], directoriesNeedingRename: [NextcloudItemMetadataTable]) {
138 var returningNewMetadatas: [NextcloudItemMetadataTable] = []
139 var returningUpdatedMetadatas: [NextcloudItemMetadataTable] = []
140 var directoriesNeedingRename: [NextcloudItemMetadataTable] = []
142 for updatedMetadata in updatedMetadatas {
143 if let existingMetadata = existingMetadatas.first(where: { $0.ocId == updatedMetadata.ocId }) {
145 if existingMetadata.status == NextcloudItemMetadataTable.Status.normal.rawValue &&
146 !existingMetadata.isInSameDatabaseStoreableRemoteState(updatedMetadata) {
148 if updatedMetadata.directory {
150 if updatedMetadata.serverUrl != existingMetadata.serverUrl || updatedMetadata.fileName != existingMetadata.fileName {
152 directoriesNeedingRename.append(NextcloudItemMetadataTable(value: updatedMetadata))
153 updatedMetadata.etag = "" // Renaming doesn't change the etag so reset manually
155 } else if !updateDirectoryEtags {
156 updatedMetadata.etag = existingMetadata.etag
160 returningUpdatedMetadatas.append(updatedMetadata)
163 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))")
165 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))")
168 } else { // This is a new metadata
169 if !updateDirectoryEtags && updatedMetadata.directory {
170 updatedMetadata.etag = ""
173 returningNewMetadatas.append(updatedMetadata)
175 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))")
179 return (returningNewMetadatas, returningUpdatedMetadatas, directoriesNeedingRename)
182 func updateItemMetadatas(account: String, serverUrl: String, updatedMetadatas: [NextcloudItemMetadataTable], updateDirectoryEtags: Bool) -> (newMetadatas: [NextcloudItemMetadataTable]?, updatedMetadatas: [NextcloudItemMetadataTable]?, deletedMetadatas: [NextcloudItemMetadataTable]?) {
183 let database = ncDatabase()
186 let existingMetadatas = database.objects(NextcloudItemMetadataTable.self).filter("account == %@ AND serverUrl == %@ AND status == %@", account, serverUrl, NextcloudItemMetadataTable.Status.normal.rawValue)
188 let metadatasToDelete = processItemMetadatasToDelete(existingMetadatas: existingMetadatas,
189 updatedMetadatas: updatedMetadatas)
191 let metadatasToChange = processItemMetadatasToUpdate(existingMetadatas: existingMetadatas,
192 updatedMetadatas: updatedMetadatas,
193 updateDirectoryEtags: updateDirectoryEtags)
195 var metadatasToUpdate = metadatasToChange.updatedMetadatas
196 let metadatasToCreate = metadatasToChange.newMetadatas
197 let directoriesNeedingRename = metadatasToChange.directoriesNeedingRename
199 let metadatasToAdd = Array(metadatasToUpdate.map { NextcloudItemMetadataTable(value: $0) }) +
200 Array(metadatasToCreate.map { NextcloudItemMetadataTable(value: $0) })
202 for metadata in directoriesNeedingRename {
204 if let updatedDirectoryChildren = renameDirectoryAndPropagateToChildren(ocId: metadata.ocId, newServerUrl: metadata.serverUrl, newFileName: metadata.fileName) {
205 metadatasToUpdate += updatedDirectoryChildren
210 for metadata in metadatasToDelete {
211 // Can't pass copies, we need the originals from the database
212 database.delete(ncDatabase().objects(NextcloudItemMetadataTable.self).filter("ocId == %@", metadata.ocId))
215 for metadata in metadatasToAdd {
216 database.add(metadata, update: .all)
221 return (newMetadatas: metadatasToCreate, updatedMetadatas: metadatasToUpdate, deletedMetadatas: metadatasToDelete)
223 Logger.ncFilesDatabase.error("Could not update any item metadatas, received error: \(error.localizedDescription, privacy: .public)")
224 return (nil, nil, nil)
228 func setStatusForItemMetadata(_ metadata: NextcloudItemMetadataTable, status: NextcloudItemMetadataTable.Status, completionHandler: @escaping(_ updatedMetadata: NextcloudItemMetadataTable?) -> Void) {
229 let database = ncDatabase()
233 guard let result = database.objects(NextcloudItemMetadataTable.self).filter("ocId == %@", metadata.ocId).first else {
234 Logger.ncFilesDatabase.debug("Did not update status for item metadata as it was not found. ocID: \(metadata.ocId, privacy: .public)")
238 result.status = status.rawValue
239 database.add(result, update: .all)
240 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))")
242 completionHandler(NextcloudItemMetadataTable(value: result))
245 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)")
246 completionHandler(nil)
250 func addItemMetadata(_ metadata: NextcloudItemMetadataTable) {
251 let database = ncDatabase()
255 database.add(metadata, update: .all)
256 Logger.ncFilesDatabase.debug("Added item metadata. ocID: \(metadata.ocId, privacy: .public), etag: \(metadata.etag, privacy: .public), fileName: \(metadata.fileName, privacy: OSLogPrivacy.auto(mask: .hash))")
259 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)")
263 @discardableResult func deleteItemMetadata(ocId: String) -> Bool {
264 let database = ncDatabase()
268 let results = database.objects(NextcloudItemMetadataTable.self).filter("ocId == %@", ocId)
270 Logger.ncFilesDatabase.debug("Deleting item metadata. \(ocId, privacy: .public)")
271 database.delete(results)
276 Logger.ncFilesDatabase.error("Could not delete item metadata with ocId: \(ocId, privacy: .public), received error: \(error.localizedDescription, privacy: .public)")
281 func renameItemMetadata(ocId: String, newServerUrl: String, newFileName: String) {
282 let database = ncDatabase()
286 guard let itemMetadata = database.objects(NextcloudItemMetadataTable.self).filter("ocId == %@", ocId).first else {
287 Logger.ncFilesDatabase.debug("Could not find an item with ocID \(ocId, privacy: .public) to rename to \(newFileName, privacy: OSLogPrivacy.auto(mask: .hash))")
291 let oldFileName = itemMetadata.fileName
292 let oldServerUrl = itemMetadata.serverUrl
294 itemMetadata.fileName = newFileName
295 itemMetadata.fileNameView = newFileName
296 itemMetadata.serverUrl = newServerUrl
298 database.add(itemMetadata, update: .all)
300 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))")
303 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)")
307 func parentItemIdentifierFromMetadata(_ metadata: NextcloudItemMetadataTable) -> NSFileProviderItemIdentifier? {
308 let homeServerFilesUrl = metadata.urlBase + "/remote.php/dav/files/" + metadata.userId
310 if metadata.serverUrl == homeServerFilesUrl {
311 return .rootContainer
314 guard let itemParentDirectory = parentDirectoryMetadataForItem(metadata) else {
315 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))")
319 if let parentDirectoryMetadata = itemMetadataFromOcId(itemParentDirectory.ocId) {
320 return NSFileProviderItemIdentifier(parentDirectoryMetadata.ocId)
323 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))")
327 func directoryMetadata(account: String, serverUrl: String) -> NextcloudItemMetadataTable? {
328 // We want to split by "/" (e.g. cloud.nc.com/files/a/b) but we need to be mindful of "https://c.nc.com"
329 let problematicSeparator = "://"
330 let placeholderSeparator = "__TEMP_REPLACE__"
331 let serverUrlWithoutPrefix = serverUrl.replacingOccurrences(of: problematicSeparator, with: placeholderSeparator)
332 var splitServerUrl = serverUrlWithoutPrefix.split(separator: "/")
333 let directoryItemFileName = String(splitServerUrl.removeLast())
334 let directoryItemServerUrl = splitServerUrl.joined(separator: "/").replacingOccurrences(of: placeholderSeparator, with: problematicSeparator)
336 if let metadata = ncDatabase().objects(NextcloudItemMetadataTable.self).filter("account == %@ AND serverUrl == %@ AND fileName == %@ AND directory == true", account, directoryItemServerUrl, directoryItemFileName).first {
337 return NextcloudItemMetadataTable(value: metadata)
343 func childDirectoriesForDirectory(_ directoryMetadata: NextcloudItemMetadataTable) -> [NextcloudItemMetadataTable] {
344 let directoryServerUrl = directoryMetadata.serverUrl + "/" + directoryMetadata.fileName
345 let metadatas = ncDatabase().objects(NextcloudItemMetadataTable.self).filter("serverUrl BEGINSWITH %@ AND ocId != %@ AND directory == true", directoryServerUrl, directoryMetadata.account)
346 return sortedItemMetadatas(metadatas)
349 func parentDirectoryMetadataForItem(_ itemMetadata: NextcloudItemMetadataTable) -> NextcloudItemMetadataTable? {
350 return directoryMetadata(account: itemMetadata.account, serverUrl: itemMetadata.serverUrl)
353 func directoryMetadata(ocId: String) -> NextcloudItemMetadataTable? {
354 if let metadata = ncDatabase().objects(NextcloudItemMetadataTable.self).filter("ocId == %@ AND directory == true", ocId).first {
355 return NextcloudItemMetadataTable(value: metadata)
361 func directoryMetadatas(account: String) -> [NextcloudItemMetadataTable] {
362 let metadatas = ncDatabase().objects(NextcloudItemMetadataTable.self).filter("account == %@ AND directory == true", account)
363 return sortedItemMetadatas(metadatas)
366 func directoryMetadatas(account: String, parentDirectoryServerUrl: String) -> [NextcloudItemMetadataTable] {
367 let metadatas = ncDatabase().objects(NextcloudItemMetadataTable.self).filter("account == %@ AND parentDirectoryServerUrl == %@ AND directory == true", account, parentDirectoryServerUrl)
368 return sortedItemMetadatas(metadatas)
371 // Deletes all metadatas related to the info of the directory provided
372 func deleteDirectoryAndSubdirectoriesMetadata(ocId: String) -> [NextcloudItemMetadataTable]? {
373 let database = ncDatabase()
374 guard let directoryMetadata = database.objects(NextcloudItemMetadataTable.self).filter("ocId == %@ AND directory == true", ocId).first else {
375 Logger.ncFilesDatabase.error("Could not find directory metadata for ocId \(ocId, privacy: .public). Not proceeding with deletion")
379 let directoryMetadataCopy = NextcloudItemMetadataTable(value: directoryMetadata)
380 let directoryUrlPath = directoryMetadata.serverUrl + "/" + directoryMetadata.fileName
381 let directoryAccount = directoryMetadata.account
382 let directoryEtag = directoryMetadata.etag
384 Logger.ncFilesDatabase.debug("Deleting root directory metadata in recursive delete. ocID: \(directoryMetadata.ocId, privacy: .public), etag: \(directoryEtag, privacy: .public), serverUrl: \(directoryUrlPath, privacy: OSLogPrivacy.auto(mask: .hash))")
386 guard deleteItemMetadata(ocId: directoryMetadata.ocId) else {
387 Logger.ncFilesDatabase.debug("Failure to delete root directory metadata in recursive delete. ocID: \(directoryMetadata.ocId, privacy: .public), etag: \(directoryEtag, privacy: .public), serverUrl: \(directoryUrlPath, privacy: OSLogPrivacy.auto(mask: .hash))")
391 var deletedMetadatas: [NextcloudItemMetadataTable] = [directoryMetadata]
393 let results = database.objects(NextcloudItemMetadataTable.self).filter("account == %@ AND serverUrl BEGINSWITH %@", directoryAccount, directoryUrlPath)
395 for result in results {
396 let successfulItemMetadataDelete = deleteItemMetadata(ocId: result.ocId)
397 if (successfulItemMetadataDelete) {
398 deletedMetadatas.append(NextcloudItemMetadataTable(value: result))
401 if localFileMetadataFromOcId(result.ocId) != nil {
402 deleteLocalFileMetadata(ocId: result.ocId)
406 Logger.ncFilesDatabase.debug("Completed deletions in directory recursive delete. ocID: \(directoryMetadata.ocId, privacy: .public), etag: \(directoryEtag, privacy: .public), serverUrl: \(directoryUrlPath, privacy: OSLogPrivacy.auto(mask: .hash))")
408 return deletedMetadatas
411 func renameDirectoryAndPropagateToChildren(ocId: String, newServerUrl: String, newFileName: String) -> [NextcloudItemMetadataTable]? {
413 let database = ncDatabase()
415 guard let directoryMetadata = database.objects(NextcloudItemMetadataTable.self).filter("ocId == %@ AND directory == true", ocId).first else {
416 Logger.ncFilesDatabase.error("Could not find a directory with ocID \(ocId, privacy: .public), cannot proceed with recursive renaming")
420 let oldItemServerUrl = directoryMetadata.serverUrl
421 let oldDirectoryServerUrl = oldItemServerUrl + "/" + directoryMetadata.fileName
422 let newDirectoryServerUrl = newServerUrl + "/" + newFileName
423 let childItemResults = database.objects(NextcloudItemMetadataTable.self).filter("account == %@ AND serverUrl BEGINSWITH %@", directoryMetadata.account, oldDirectoryServerUrl)
425 renameItemMetadata(ocId: ocId, newServerUrl: newServerUrl, newFileName: newFileName)
426 Logger.ncFilesDatabase.debug("Renamed root renaming directory")
430 for childItem in childItemResults {
431 let oldServerUrl = childItem.serverUrl
432 let movedServerUrl = oldServerUrl.replacingOccurrences(of: oldDirectoryServerUrl, with: newDirectoryServerUrl)
433 childItem.serverUrl = movedServerUrl
434 database.add(childItem, update: .all)
435 Logger.ncFilesDatabase.debug("Moved childItem at \(oldServerUrl) to \(movedServerUrl)")
439 Logger.ncFilesDatabase.error("Could not rename directory metadata with ocId: \(ocId, privacy: .public) to new serverUrl: \(newServerUrl), received error: \(error.localizedDescription, privacy: .public)")
444 let updatedChildItemResults = database.objects(NextcloudItemMetadataTable.self).filter("account == %@ AND serverUrl BEGINSWITH %@", directoryMetadata.account, newDirectoryServerUrl)
445 return sortedItemMetadatas(updatedChildItemResults)
448 func localFileMetadataFromOcId(_ ocId: String) -> NextcloudLocalFileMetadataTable? {
449 if let metadata = ncDatabase().objects(NextcloudLocalFileMetadataTable.self).filter("ocId == %@", ocId).first {
450 return NextcloudLocalFileMetadataTable(value: metadata)
456 func addLocalFileMetadataFromItemMetadata(_ itemMetadata: NextcloudItemMetadataTable) {
457 let database = ncDatabase()
461 let newLocalFileMetadata = NextcloudLocalFileMetadataTable()
463 newLocalFileMetadata.ocId = itemMetadata.ocId
464 newLocalFileMetadata.fileName = itemMetadata.fileName
465 newLocalFileMetadata.account = itemMetadata.account
466 newLocalFileMetadata.etag = itemMetadata.etag
467 newLocalFileMetadata.exifDate = Date()
468 newLocalFileMetadata.exifLatitude = "-1"
469 newLocalFileMetadata.exifLongitude = "-1"
471 database.add(newLocalFileMetadata, update: .all)
472 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))")
475 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)")
479 func deleteLocalFileMetadata(ocId: String) {
480 let database = ncDatabase()
484 let results = database.objects(NextcloudLocalFileMetadataTable.self).filter("ocId == %@", ocId)
485 database.delete(results)
488 Logger.ncFilesDatabase.error("Could not delete local file metadata with ocId: \(ocId, privacy: .public), received error: \(error.localizedDescription, privacy: .public)")
492 private func sortedLocalFileMetadatas(_ metadatas: Results<NextcloudLocalFileMetadataTable>) -> [NextcloudLocalFileMetadataTable] {
493 let sortedMetadatas = metadatas.sorted(byKeyPath: "fileName", ascending: true)
494 return Array(sortedMetadatas.map { NextcloudLocalFileMetadataTable(value: $0) })
497 func localFileMetadatas(account: String) -> [NextcloudLocalFileMetadataTable] {
498 let results = ncDatabase().objects(NextcloudLocalFileMetadataTable.self).filter("account == %@", account)
499 return sortedLocalFileMetadatas(results)
502 func localFileItemMetadatas(account: String) -> [NextcloudItemMetadataTable] {
503 let localFileMetadatas = localFileMetadatas(account: account)
504 let localFileMetadatasOcIds = Array(localFileMetadatas.map { $0.ocId })
506 var itemMetadatas: [NextcloudItemMetadataTable] = []
508 for ocId in localFileMetadatasOcIds {
509 guard let itemMetadata = itemMetadataFromOcId(ocId) else {
510 Logger.ncFilesDatabase.error("Could not find matching item metadata for local file metadata with ocId: \(ocId, privacy: .public) with request from account: \(account)")
514 itemMetadatas.append(NextcloudItemMetadataTable(value: itemMetadata))