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 returningNewMetadatas.append(updatedMetadata)
171 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))")
175 return (returningNewMetadatas, returningUpdatedMetadatas, directoriesNeedingRename)
178 func updateItemMetadatas(account: String, serverUrl: String, updatedMetadatas: [NextcloudItemMetadataTable], updateDirectoryEtags: Bool) -> (newMetadatas: [NextcloudItemMetadataTable]?, updatedMetadatas: [NextcloudItemMetadataTable]?, deletedMetadatas: [NextcloudItemMetadataTable]?) {
179 let database = ncDatabase()
182 let existingMetadatas = database.objects(NextcloudItemMetadataTable.self).filter("account == %@ AND serverUrl == %@ AND status == %@", account, serverUrl, NextcloudItemMetadataTable.Status.normal.rawValue)
184 let metadatasToDelete = processItemMetadatasToDelete(existingMetadatas: existingMetadatas,
185 updatedMetadatas: updatedMetadatas)
187 let metadatasToChange = processItemMetadatasToUpdate(existingMetadatas: existingMetadatas,
188 updatedMetadatas: updatedMetadatas,
189 updateDirectoryEtags: updateDirectoryEtags)
191 var metadatasToUpdate = metadatasToChange.updatedMetadatas
192 let metadatasToCreate = metadatasToChange.newMetadatas
193 let directoriesNeedingRename = metadatasToChange.directoriesNeedingRename
195 let metadatasToAdd = Array(metadatasToUpdate.map { NextcloudItemMetadataTable(value: $0) }) +
196 Array(metadatasToCreate.map { NextcloudItemMetadataTable(value: $0) })
199 for metadata in metadatasToDelete {
200 // Can't pass copies, we need the originals from the database
201 database.delete(ncDatabase().objects(NextcloudItemMetadataTable.self).filter("ocId == %@", metadata.ocId))
204 for metadata in metadatasToAdd {
205 database.add(metadata, update: .all)
210 for metadata in directoriesNeedingRename {
211 if let updatedDirectoryChildren = renameDirectoryAndPropagateToChildren(ocId: metadata.ocId, newServerUrl: metadata.serverUrl, newFileName: metadata.fileName) {
212 metadatasToUpdate += updatedDirectoryChildren
216 return (newMetadatas: metadatasToCreate, updatedMetadatas: metadatasToUpdate, deletedMetadatas: metadatasToDelete)
218 Logger.ncFilesDatabase.error("Could not update any item metadatas, received error: \(error.localizedDescription, privacy: .public)")
219 return (nil, nil, nil)
223 func setStatusForItemMetadata(_ metadata: NextcloudItemMetadataTable, status: NextcloudItemMetadataTable.Status, completionHandler: @escaping(_ updatedMetadata: NextcloudItemMetadataTable?) -> Void) {
224 let database = ncDatabase()
228 guard let result = database.objects(NextcloudItemMetadataTable.self).filter("ocId == %@", metadata.ocId).first else {
229 Logger.ncFilesDatabase.debug("Did not update status for item metadata as it was not found. ocID: \(metadata.ocId, privacy: .public)")
233 result.status = status.rawValue
234 database.add(result, update: .all)
235 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))")
237 completionHandler(NextcloudItemMetadataTable(value: result))
240 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)")
241 completionHandler(nil)
245 func addItemMetadata(_ metadata: NextcloudItemMetadataTable) {
246 let database = ncDatabase()
250 database.add(metadata, update: .all)
251 Logger.ncFilesDatabase.debug("Added item metadata. ocID: \(metadata.ocId, privacy: .public), etag: \(metadata.etag, privacy: .public), fileName: \(metadata.fileName, privacy: OSLogPrivacy.auto(mask: .hash))")
254 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)")
258 func deleteItemMetadata(ocId: String) {
259 let database = ncDatabase()
263 let results = database.objects(NextcloudItemMetadataTable.self).filter("ocId == %@", ocId)
265 Logger.ncFilesDatabase.debug("Deleting item metadata. \(ocId, privacy: .public)")
266 database.delete(results)
269 Logger.ncFilesDatabase.error("Could not delete item metadata with ocId: \(ocId, privacy: .public), received error: \(error.localizedDescription, privacy: .public)")
273 func renameItemMetadata(ocId: String, newServerUrl: String, newFileName: String) {
274 let database = ncDatabase()
278 guard let itemMetadata = database.objects(NextcloudItemMetadataTable.self).filter("ocId == %@", ocId).first else {
279 Logger.ncFilesDatabase.debug("Could not find an item with ocID \(ocId, privacy: .public) to rename to \(newFileName, privacy: OSLogPrivacy.auto(mask: .hash))")
283 let oldFileName = itemMetadata.fileName
284 let oldServerUrl = itemMetadata.serverUrl
286 itemMetadata.fileName = newFileName
287 itemMetadata.fileNameView = newFileName
288 itemMetadata.serverUrl = newServerUrl
290 database.add(itemMetadata, update: .all)
292 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))")
295 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)")
299 func parentItemIdentifierFromMetadata(_ metadata: NextcloudItemMetadataTable) -> NSFileProviderItemIdentifier? {
300 let homeServerFilesUrl = metadata.urlBase + "/remote.php/dav/files/" + metadata.userId
302 if metadata.serverUrl == homeServerFilesUrl {
303 return .rootContainer
306 guard let itemParentDirectory = parentDirectoryMetadataForItem(metadata) else {
307 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))")
311 if let parentDirectoryMetadata = itemMetadataFromOcId(itemParentDirectory.ocId) {
312 return NSFileProviderItemIdentifier(parentDirectoryMetadata.ocId)
315 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))")
319 func directoryMetadata(account: String, serverUrl: String) -> NextcloudItemMetadataTable? {
320 // We want to split by "/" (e.g. cloud.nc.com/files/a/b) but we need to be mindful of "https://c.nc.com"
321 let problematicSeparator = "://"
322 let placeholderSeparator = "__TEMP_REPLACE__"
323 let serverUrlWithoutPrefix = serverUrl.replacingOccurrences(of: problematicSeparator, with: placeholderSeparator)
324 var splitServerUrl = serverUrlWithoutPrefix.split(separator: "/")
325 let directoryItemFileName = String(splitServerUrl.removeLast())
326 let directoryItemServerUrl = splitServerUrl.joined(separator: "/").replacingOccurrences(of: placeholderSeparator, with: problematicSeparator)
328 if let metadata = ncDatabase().objects(NextcloudItemMetadataTable.self).filter("account == %@ AND serverUrl == %@ AND fileName == %@ AND directory == true", account, directoryItemServerUrl, directoryItemFileName).first {
329 return NextcloudItemMetadataTable(value: metadata)
335 func childDirectoriesForDirectory(_ directoryMetadata: NextcloudItemMetadataTable) -> [NextcloudItemMetadataTable] {
336 let directoryServerUrl = directoryMetadata.serverUrl + "/" + directoryMetadata.fileName
337 let metadatas = ncDatabase().objects(NextcloudItemMetadataTable.self).filter("serverUrl BEGINSWITH %@ AND ocId != %@ AND directory == true", directoryServerUrl, directoryMetadata.account)
338 return sortedItemMetadatas(metadatas)
341 func parentDirectoryMetadataForItem(_ itemMetadata: NextcloudItemMetadataTable) -> NextcloudItemMetadataTable? {
342 return directoryMetadata(account: itemMetadata.account, serverUrl: itemMetadata.serverUrl)
345 func directoryMetadata(ocId: String) -> NextcloudItemMetadataTable? {
346 if let metadata = ncDatabase().objects(NextcloudItemMetadataTable.self).filter("ocId == %@ AND directory == true", ocId).first {
347 return NextcloudItemMetadataTable(value: metadata)
353 func directoryMetadatas(account: String) -> [NextcloudItemMetadataTable] {
354 let metadatas = ncDatabase().objects(NextcloudItemMetadataTable.self).filter("account == %@ AND directory == true", account)
355 return sortedItemMetadatas(metadatas)
358 func directoryMetadatas(account: String, parentDirectoryServerUrl: String) -> [NextcloudItemMetadataTable] {
359 let metadatas = ncDatabase().objects(NextcloudItemMetadataTable.self).filter("account == %@ AND parentDirectoryServerUrl == %@ AND directory == true", account, parentDirectoryServerUrl)
360 return sortedItemMetadatas(metadatas)
363 // Deletes all metadatas related to the info of the directory provided
364 func deleteDirectoryAndSubdirectoriesMetadata(ocId: String) {
365 let database = ncDatabase()
366 guard let directoryMetadata = database.objects(NextcloudItemMetadataTable.self).filter("ocId == %@ AND directory == true", ocId).first else {
367 Logger.ncFilesDatabase.error("Could not find directory metadata for ocId \(ocId, privacy: .public). Not proceeding with deletion")
371 let directoryUrlPath = directoryMetadata.serverUrl + "/" + directoryMetadata.fileName
372 let results = database.objects(NextcloudItemMetadataTable.self).filter("account == %@ AND serverUrl BEGINSWITH %@", directoryMetadata.account, directoryUrlPath)
374 for result in results {
375 deleteItemMetadata(ocId: result.ocId)
376 deleteLocalFileMetadata(ocId: result.ocId)
381 Logger.ncFilesDatabase.debug("Deleting root directory metadata in recursive delete. ocID: \(directoryMetadata.ocId, privacy: .public), etag: \(directoryMetadata.etag, privacy: .public), serverUrl: \(directoryUrlPath)")
382 database.delete(results)
385 Logger.ncFilesDatabase.error("Could not delete root directory metadata in recursive delete. ocID: \(directoryMetadata.ocId, privacy: .public), etag: \(directoryMetadata.etag, privacy: .public), serverUrl: \(directoryUrlPath), received error: \(error.localizedDescription, privacy: .public)")
389 func renameDirectoryAndPropagateToChildren(ocId: String, newServerUrl: String, newFileName: String) -> [NextcloudItemMetadataTable]? {
391 let database = ncDatabase()
393 guard let directoryMetadata = database.objects(NextcloudItemMetadataTable.self).filter("ocId == %@ AND directory == true", ocId).first else {
394 Logger.ncFilesDatabase.error("Could not find a directory with ocID \(ocId, privacy: .public), cannot proceed with recursive renaming")
398 let oldServerUrl = directoryMetadata.serverUrl + "/" + directoryMetadata.fileName
399 let childItemResults = database.objects(NextcloudItemMetadataTable.self).filter("account == %@ AND serverUrl BEGINSWITH %@", directoryMetadata.account, oldServerUrl)
401 renameItemMetadata(ocId: ocId, newServerUrl: newServerUrl, newFileName: newFileName)
402 Logger.ncFilesDatabase.debug("Renamed root renaming directory")
406 for childItem in childItemResults {
407 let oldServerUrl = childItem.serverUrl
408 let movedServerUrl = oldServerUrl.replacingOccurrences(of: oldServerUrl, with: newServerUrl)
409 childItem.serverUrl = movedServerUrl
410 database.add(childItem, update: .all)
411 Logger.ncFilesDatabase.debug("Moved childItem at \(oldServerUrl) to \(movedServerUrl)")
415 Logger.ncFilesDatabase.error("Could not rename directory metadata with ocId: \(ocId, privacy: .public) to new serverUrl: \(newServerUrl), received error: \(error.localizedDescription, privacy: .public)")
420 let updatedChildItemResults = database.objects(NextcloudItemMetadataTable.self).filter("account == %@ AND serverUrl BEGINSWITH %@", directoryMetadata.account, newServerUrl)
421 return sortedItemMetadatas(updatedChildItemResults)
424 func localFileMetadataFromOcId(_ ocId: String) -> NextcloudLocalFileMetadataTable? {
425 if let metadata = ncDatabase().objects(NextcloudLocalFileMetadataTable.self).filter("ocId == %@", ocId).first {
426 return NextcloudLocalFileMetadataTable(value: metadata)
432 func addLocalFileMetadataFromItemMetadata(_ itemMetadata: NextcloudItemMetadataTable) {
433 let database = ncDatabase()
437 let newLocalFileMetadata = NextcloudLocalFileMetadataTable()
439 newLocalFileMetadata.ocId = itemMetadata.ocId
440 newLocalFileMetadata.fileName = itemMetadata.fileName
441 newLocalFileMetadata.account = itemMetadata.account
442 newLocalFileMetadata.etag = itemMetadata.etag
443 newLocalFileMetadata.exifDate = Date()
444 newLocalFileMetadata.exifLatitude = "-1"
445 newLocalFileMetadata.exifLongitude = "-1"
447 database.add(newLocalFileMetadata, update: .all)
448 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))")
451 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)")
455 func deleteLocalFileMetadata(ocId: String) {
456 let database = ncDatabase()
460 let results = database.objects(NextcloudLocalFileMetadataTable.self).filter("ocId == %@", ocId)
461 database.delete(results)
464 Logger.ncFilesDatabase.error("Could not delete local file metadata with ocId: \(ocId, privacy: .public), received error: \(error.localizedDescription, privacy: .public)")
468 private func sortedLocalFileMetadatas(_ metadatas: Results<NextcloudLocalFileMetadataTable>) -> [NextcloudLocalFileMetadataTable] {
469 let sortedMetadatas = metadatas.sorted(byKeyPath: "fileName", ascending: true)
470 return Array(sortedMetadatas.map { NextcloudLocalFileMetadataTable(value: $0) })
473 func localFileMetadatas(account: String) -> [NextcloudLocalFileMetadataTable] {
474 let results = ncDatabase().objects(NextcloudLocalFileMetadataTable.self).filter("account == %@", account)
475 return sortedLocalFileMetadatas(results)
478 func localFileItemMetadatas(account: String) -> [NextcloudItemMetadataTable] {
479 let localFileMetadatas = localFileMetadatas(account: account)
480 let localFileMetadatasOcIds = Array(localFileMetadatas.map { $0.ocId })
482 var itemMetadatas: [NextcloudItemMetadataTable] = []
484 for ocId in localFileMetadatasOcIds {
485 guard let itemMetadata = itemMetadataFromOcId(ocId) else {
486 Logger.ncFilesDatabase.error("Could not find matching item metadata for local file metadata with ocId: \(ocId, privacy: .public) with request from account: \(account)")
490 itemMetadatas.append(NextcloudItemMetadataTable(value: itemMetadata))
496 func convertNKFileToItemMetadata(_ file: NKFile, account: String) -> NextcloudItemMetadataTable {
498 let metadata = NextcloudItemMetadataTable()
500 metadata.account = account
501 metadata.checksums = file.checksums
502 metadata.commentsUnread = file.commentsUnread
503 metadata.contentType = file.contentType
504 if let date = file.creationDate {
505 metadata.creationDate = date as Date
507 metadata.creationDate = file.date as Date
509 metadata.dataFingerprint = file.dataFingerprint
510 metadata.date = file.date as Date
511 metadata.directory = file.directory
512 metadata.downloadURL = file.downloadURL
513 metadata.e2eEncrypted = file.e2eEncrypted
514 metadata.etag = file.etag
515 metadata.favorite = file.favorite
516 metadata.fileId = file.fileId
517 metadata.fileName = file.fileName
518 metadata.fileNameView = file.fileName
519 metadata.hasPreview = file.hasPreview
520 metadata.iconName = file.iconName
521 metadata.mountType = file.mountType
522 metadata.name = file.name
523 metadata.note = file.note
524 metadata.ocId = file.ocId
525 metadata.ownerId = file.ownerId
526 metadata.ownerDisplayName = file.ownerDisplayName
527 metadata.lock = file.lock
528 metadata.lockOwner = file.lockOwner
529 metadata.lockOwnerEditor = file.lockOwnerEditor
530 metadata.lockOwnerType = file.lockOwnerType
531 metadata.lockOwnerDisplayName = file.lockOwnerDisplayName
532 metadata.lockTime = file.lockTime
533 metadata.lockTimeOut = file.lockTimeOut
534 metadata.path = file.path
535 metadata.permissions = file.permissions
536 metadata.quotaUsedBytes = file.quotaUsedBytes
537 metadata.quotaAvailableBytes = file.quotaAvailableBytes
538 metadata.richWorkspace = file.richWorkspace
539 metadata.resourceType = file.resourceType
540 metadata.serverUrl = file.serverUrl
541 metadata.sharePermissionsCollaborationServices = file.sharePermissionsCollaborationServices
542 for element in file.sharePermissionsCloudMesh {
543 metadata.sharePermissionsCloudMesh.append(element)
545 for element in file.shareType {
546 metadata.shareType.append(element)
548 metadata.size = file.size
549 metadata.classFile = file.classFile
550 //FIXME: iOS 12.0,* don't detect UTI text/markdown, text/x-markdown
551 if (metadata.contentType == "text/markdown" || metadata.contentType == "text/x-markdown") && metadata.classFile == NKCommon.TypeClassFile.unknow.rawValue {
552 metadata.classFile = NKCommon.TypeClassFile.document.rawValue
554 if let date = file.uploadDate {
555 metadata.uploadDate = date as Date
557 metadata.uploadDate = file.date as Date
559 metadata.urlBase = file.urlBase
560 metadata.user = file.user
561 metadata.userId = file.userId
563 // Support for finding the correct filename for e2ee files should go here
568 func convertNKFilesFromDirectoryReadToItemMetadatas(_ files: [NKFile], account: String, completionHandler: @escaping (_ directoryMetadata: NextcloudItemMetadataTable, _ childDirectoriesMetadatas: [NextcloudItemMetadataTable], _ metadatas: [NextcloudItemMetadataTable]) -> Void) {
570 var directoryMetadataSet = false
571 var directoryMetadata = NextcloudItemMetadataTable()
572 var childDirectoriesMetadatas: [NextcloudItemMetadataTable] = []
573 var metadatas: [NextcloudItemMetadataTable] = []
576 let metadata = convertNKFileToItemMetadata(file, account: account)
578 if metadatas.isEmpty && !directoryMetadataSet {
579 directoryMetadata = metadata;
580 directoryMetadataSet = true;
582 metadatas.append(metadata)
583 if metadata.directory {
584 childDirectoriesMetadatas.append(metadata)
589 completionHandler(directoryMetadata, childDirectoriesMetadatas, metadatas)