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(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],
143 updateDirectoryEtags: Bool) -> (newMetadatas: [NextcloudItemMetadataTable], updatedMetadatas: [NextcloudItemMetadataTable]) {
145 assert(databaseToWriteTo.isInWriteTransaction)
147 var returningNewMetadatas: [NextcloudItemMetadataTable] = []
148 var returningUpdatedMetadatas: [NextcloudItemMetadataTable] = []
150 for updatedMetadata in updatedMetadatas {
151 if let existingMetadata = existingMetadatas.first(where: { $0.ocId == updatedMetadata.ocId }) {
153 if existingMetadata.status == NextcloudItemMetadataTable.Status.normal.rawValue &&
154 !existingMetadata.isInSameDatabaseStoreableRemoteState(updatedMetadata) {
156 if !updateDirectoryEtags {
157 updatedMetadata.etag = existingMetadata.etag
160 returningUpdatedMetadatas.append(NextcloudItemMetadataTable(value: updatedMetadata))
161 databaseToWriteTo.add(updatedMetadata, update: .all)
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(NextcloudItemMetadataTable(value: updatedMetadata))
170 databaseToWriteTo.add(updatedMetadata, update: .all)
172 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))")
176 return (returningNewMetadatas, returningUpdatedMetadatas)
179 func updateItemMetadatas(account: String, serverUrl: String, updatedMetadatas: [NextcloudItemMetadataTable], updateDirectoryEtags: Bool, completionHandler: @escaping(_ newMetadatas: [NextcloudItemMetadataTable]?, _ updatedMetadatas: [NextcloudItemMetadataTable]?, _ deletedMetadatas: [NextcloudItemMetadataTable]?) -> Void) {
180 let database = ncDatabase()
184 let existingMetadatas = database.objects(NextcloudItemMetadataTable.self).filter("account == %@ AND serverUrl == %@ AND status == %@", account, serverUrl, NextcloudItemMetadataTable.Status.normal.rawValue)
186 let deletedMetadatas = processItemMetadatasToDelete(databaseToWriteTo: database,
187 existingMetadatas: existingMetadatas,
188 updatedMetadatas: updatedMetadatas)
190 let metadatasFromUpdate = processItemMetadatasToUpdate(databaseToWriteTo: database,
191 existingMetadatas: existingMetadatas,
192 updatedMetadatas: updatedMetadatas,
193 updateDirectoryEtags: updateDirectoryEtags)
195 completionHandler(metadatasFromUpdate.newMetadatas, metadatasFromUpdate.updatedMetadatas, deletedMetadatas)
198 Logger.ncFilesDatabase.error("Could not update any item metadatas, received error: \(error.localizedDescription, privacy: .public)")
199 completionHandler(nil, nil, nil)
203 func setStatusForItemMetadata(_ metadata: NextcloudItemMetadataTable, status: NextcloudItemMetadataTable.Status, completionHandler: @escaping(_ updatedMetadata: NextcloudItemMetadataTable?) -> Void) {
204 let database = ncDatabase()
208 guard let result = database.objects(NextcloudItemMetadataTable.self).filter("ocId == %@", metadata.ocId).first else {
209 Logger.ncFilesDatabase.debug("Did not update status for item metadata as it was not found. ocID: \(metadata.ocId, privacy: .public)")
213 result.status = status.rawValue
214 database.add(result, update: .all)
215 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))")
217 completionHandler(NextcloudItemMetadataTable(value: result))
220 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)")
221 completionHandler(nil)
225 func addItemMetadata(_ metadata: NextcloudItemMetadataTable) {
226 let database = ncDatabase()
230 database.add(metadata, update: .all)
231 Logger.ncFilesDatabase.debug("Added item metadata. ocID: \(metadata.ocId, privacy: .public), etag: \(metadata.etag, privacy: .public), fileName: \(metadata.fileName, privacy: OSLogPrivacy.auto(mask: .hash))")
234 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)")
238 func deleteItemMetadata(ocId: String) {
239 let database = ncDatabase()
243 let results = database.objects(NextcloudItemMetadataTable.self).filter("ocId == %@", ocId)
245 Logger.ncFilesDatabase.debug("Deleting item metadata. \(ocId, privacy: .public)")
246 database.delete(results)
249 Logger.ncFilesDatabase.error("Could not delete item metadata with ocId: \(ocId, privacy: .public), received error: \(error.localizedDescription, privacy: .public)")
253 func renameItemMetadata(ocId: String, newServerUrl: String, newFileName: String) {
254 let database = ncDatabase()
258 guard let itemMetadata = database.objects(NextcloudItemMetadataTable.self).filter("ocId == %@", ocId).first else {
259 Logger.ncFilesDatabase.debug("Could not find an item with ocID \(ocId, privacy: .public) to rename to \(newFileName, privacy: OSLogPrivacy.auto(mask: .hash))")
263 let oldFileName = itemMetadata.fileName
264 let oldServerUrl = itemMetadata.serverUrl
266 itemMetadata.fileName = newFileName
267 itemMetadata.fileNameView = newFileName
268 itemMetadata.serverUrl = newServerUrl
270 database.add(itemMetadata, update: .all)
272 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))")
275 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)")
279 func parentItemIdentifierFromMetadata(_ metadata: NextcloudItemMetadataTable) -> NSFileProviderItemIdentifier? {
280 let homeServerFilesUrl = metadata.urlBase + "/remote.php/dav/files/" + metadata.userId
282 if metadata.serverUrl == homeServerFilesUrl {
283 return .rootContainer
286 guard let itemParentDirectory = parentDirectoryMetadataForItem(metadata) else {
287 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))")
291 if let parentDirectoryMetadata = itemMetadataFromOcId(itemParentDirectory.ocId) {
292 return NSFileProviderItemIdentifier(parentDirectoryMetadata.ocId)
295 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))")
299 func directoryMetadata(account: String, serverUrl: String) -> NextcloudItemMetadataTable? {
300 // We want to split by "/" (e.g. cloud.nc.com/files/a/b) but we need to be mindful of "https://c.nc.com"
301 let problematicSeparator = "://"
302 let placeholderSeparator = "__TEMP_REPLACE__"
303 let serverUrlWithoutPrefix = serverUrl.replacingOccurrences(of: problematicSeparator, with: placeholderSeparator)
304 var splitServerUrl = serverUrlWithoutPrefix.split(separator: "/")
305 let directoryItemFileName = String(splitServerUrl.removeLast())
306 let directoryItemServerUrl = splitServerUrl.joined(separator: "/").replacingOccurrences(of: placeholderSeparator, with: problematicSeparator)
308 if let metadata = ncDatabase().objects(NextcloudItemMetadataTable.self).filter("account == %@ AND serverUrl == %@ AND fileName == %@ AND directory == true", account, directoryItemServerUrl, directoryItemFileName).first {
309 return NextcloudItemMetadataTable(value: metadata)
315 func childDirectoriesForDirectory(_ directoryMetadata: NextcloudItemMetadataTable) -> [NextcloudItemMetadataTable] {
316 let directoryServerUrl = directoryMetadata.serverUrl + "/" + directoryMetadata.fileName
317 let metadatas = ncDatabase().objects(NextcloudItemMetadataTable.self).filter("serverUrl BEGINSWITH %@ AND ocId != %@ AND directory == true", directoryServerUrl, directoryMetadata.account)
318 return sortedItemMetadatas(metadatas)
321 func parentDirectoryMetadataForItem(_ itemMetadata: NextcloudItemMetadataTable) -> NextcloudItemMetadataTable? {
322 return directoryMetadata(account: itemMetadata.account, serverUrl: itemMetadata.serverUrl)
325 func directoryMetadata(ocId: String) -> NextcloudItemMetadataTable? {
326 if let metadata = ncDatabase().objects(NextcloudItemMetadataTable.self).filter("ocId == %@ AND directory == true", ocId).first {
327 return NextcloudItemMetadataTable(value: metadata)
333 func directoryMetadatas(account: String) -> [NextcloudItemMetadataTable] {
334 let metadatas = ncDatabase().objects(NextcloudItemMetadataTable.self).filter("account == %@ AND directory == true", account)
335 return sortedItemMetadatas(metadatas)
338 func directoryMetadatas(account: String, parentDirectoryServerUrl: String) -> [NextcloudItemMetadataTable] {
339 let metadatas = ncDatabase().objects(NextcloudItemMetadataTable.self).filter("account == %@ AND parentDirectoryServerUrl == %@ AND directory == true", account, parentDirectoryServerUrl)
340 return sortedItemMetadatas(metadatas)
343 // Deletes all metadatas related to the info of the directory provided
344 func deleteDirectoryAndSubdirectoriesMetadata(ocId: String) {
345 let database = ncDatabase()
346 guard let directoryMetadata = database.objects(NextcloudItemMetadataTable.self).filter("ocId == %@ AND directory == true", ocId).first else {
347 Logger.ncFilesDatabase.error("Could not find directory metadata for ocId \(ocId, privacy: .public). Not proceeding with deletion")
351 let directoryUrlPath = directoryMetadata.serverUrl + "/" + directoryMetadata.fileName
352 let results = database.objects(NextcloudItemMetadataTable.self).filter("account == %@ AND serverUrl BEGINSWITH %@", directoryMetadata.account, directoryUrlPath)
354 for result in results {
355 deleteItemMetadata(ocId: result.ocId)
356 deleteLocalFileMetadata(ocId: result.ocId)
361 Logger.ncFilesDatabase.debug("Deleting root directory metadata in recursive delete. ocID: \(directoryMetadata.ocId, privacy: .public), etag: \(directoryMetadata.etag, privacy: .public), serverUrl: \(directoryUrlPath)")
362 database.delete(results)
365 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)")
369 func renameDirectoryAndPropagateToChildren(ocId: String, newServerUrl: String, newFileName: String) {
371 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 a directory with ocID \(ocId, privacy: .public), cannot proceed with recursive renaming")
379 let oldServerUrl = directoryMetadata.serverUrl + "/" + directoryMetadata.fileName
381 let childItemResults = database.objects(NextcloudItemMetadataTable.self).filter("account == %@ AND serverUrl BEGINSWITH %@", directoryMetadata.account, oldServerUrl)
383 renameItemMetadata(ocId: ocId, newServerUrl: newServerUrl, newFileName: newFileName)
384 Logger.ncFilesDatabase.debug("Renamed root renaming directory")
387 for childItem in childItemResults {
388 let oldServerUrl = childItem.serverUrl
389 let movedServerUrl = oldServerUrl.replacingOccurrences(of: oldServerUrl, with: newServerUrl)
390 childItem.serverUrl = movedServerUrl
391 database.add(childItem, update: .all)
392 Logger.ncFilesDatabase.debug("Moved childItem at \(oldServerUrl) to \(movedServerUrl)")
396 Logger.ncFilesDatabase.error("Could not rename directory metadata with ocId: \(ocId, privacy: .public) to new serverUrl: \(newServerUrl), received error: \(error.localizedDescription, privacy: .public)")
400 func localFileMetadataFromOcId(_ ocId: String) -> NextcloudLocalFileMetadataTable? {
401 if let metadata = ncDatabase().objects(NextcloudLocalFileMetadataTable.self).filter("ocId == %@", ocId).first {
402 return NextcloudLocalFileMetadataTable(value: metadata)
408 func addLocalFileMetadataFromItemMetadata(_ itemMetadata: NextcloudItemMetadataTable) {
409 let database = ncDatabase()
413 let newLocalFileMetadata = NextcloudLocalFileMetadataTable()
415 newLocalFileMetadata.ocId = itemMetadata.ocId
416 newLocalFileMetadata.fileName = itemMetadata.fileName
417 newLocalFileMetadata.account = itemMetadata.account
418 newLocalFileMetadata.etag = itemMetadata.etag
419 newLocalFileMetadata.exifDate = Date()
420 newLocalFileMetadata.exifLatitude = "-1"
421 newLocalFileMetadata.exifLongitude = "-1"
423 database.add(newLocalFileMetadata, update: .all)
424 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))")
427 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)")
431 func deleteLocalFileMetadata(ocId: String) {
432 let database = ncDatabase()
436 let results = database.objects(NextcloudLocalFileMetadataTable.self).filter("ocId == %@", ocId)
437 database.delete(results)
440 Logger.ncFilesDatabase.error("Could not delete local file metadata with ocId: \(ocId, privacy: .public), received error: \(error.localizedDescription, privacy: .public)")
444 private func sortedLocalFileMetadatas(_ metadatas: Results<NextcloudLocalFileMetadataTable>) -> [NextcloudLocalFileMetadataTable] {
445 let sortedMetadatas = metadatas.sorted(byKeyPath: "fileName", ascending: true)
446 return Array(sortedMetadatas.map { NextcloudLocalFileMetadataTable(value: $0) })
449 func localFileMetadatas(account: String) -> [NextcloudLocalFileMetadataTable] {
450 let results = ncDatabase().objects(NextcloudLocalFileMetadataTable.self).filter("account == %@", account)
451 return sortedLocalFileMetadatas(results)
454 func localFileItemMetadatas(account: String) -> [NextcloudItemMetadataTable] {
455 let localFileMetadatas = localFileMetadatas(account: account)
456 let localFileMetadatasOcIds = Array(localFileMetadatas.map { $0.ocId })
458 var itemMetadatas: [NextcloudItemMetadataTable] = []
460 for ocId in localFileMetadatasOcIds {
461 guard let itemMetadata = itemMetadataFromOcId(ocId) else {
462 Logger.ncFilesDatabase.error("Could not find matching item metadata for local file metadata with ocId: \(ocId, privacy: .public) with request from account: \(account)")
466 itemMetadatas.append(NextcloudItemMetadataTable(value: itemMetadata))
472 func convertNKFileToItemMetadata(_ file: NKFile, account: String) -> NextcloudItemMetadataTable {
474 let metadata = NextcloudItemMetadataTable()
476 metadata.account = account
477 metadata.checksums = file.checksums
478 metadata.commentsUnread = file.commentsUnread
479 metadata.contentType = file.contentType
480 if let date = file.creationDate {
481 metadata.creationDate = date as Date
483 metadata.creationDate = file.date as Date
485 metadata.dataFingerprint = file.dataFingerprint
486 metadata.date = file.date as Date
487 metadata.directory = file.directory
488 metadata.downloadURL = file.downloadURL
489 metadata.e2eEncrypted = file.e2eEncrypted
490 metadata.etag = file.etag
491 metadata.favorite = file.favorite
492 metadata.fileId = file.fileId
493 metadata.fileName = file.fileName
494 metadata.fileNameView = file.fileName
495 metadata.hasPreview = file.hasPreview
496 metadata.iconName = file.iconName
497 metadata.mountType = file.mountType
498 metadata.name = file.name
499 metadata.note = file.note
500 metadata.ocId = file.ocId
501 metadata.ownerId = file.ownerId
502 metadata.ownerDisplayName = file.ownerDisplayName
503 metadata.lock = file.lock
504 metadata.lockOwner = file.lockOwner
505 metadata.lockOwnerEditor = file.lockOwnerEditor
506 metadata.lockOwnerType = file.lockOwnerType
507 metadata.lockOwnerDisplayName = file.lockOwnerDisplayName
508 metadata.lockTime = file.lockTime
509 metadata.lockTimeOut = file.lockTimeOut
510 metadata.path = file.path
511 metadata.permissions = file.permissions
512 metadata.quotaUsedBytes = file.quotaUsedBytes
513 metadata.quotaAvailableBytes = file.quotaAvailableBytes
514 metadata.richWorkspace = file.richWorkspace
515 metadata.resourceType = file.resourceType
516 metadata.serverUrl = file.serverUrl
517 metadata.sharePermissionsCollaborationServices = file.sharePermissionsCollaborationServices
518 for element in file.sharePermissionsCloudMesh {
519 metadata.sharePermissionsCloudMesh.append(element)
521 for element in file.shareType {
522 metadata.shareType.append(element)
524 metadata.size = file.size
525 metadata.classFile = file.classFile
526 //FIXME: iOS 12.0,* don't detect UTI text/markdown, text/x-markdown
527 if (metadata.contentType == "text/markdown" || metadata.contentType == "text/x-markdown") && metadata.classFile == NKCommon.TypeClassFile.unknow.rawValue {
528 metadata.classFile = NKCommon.TypeClassFile.document.rawValue
530 if let date = file.uploadDate {
531 metadata.uploadDate = date as Date
533 metadata.uploadDate = file.date as Date
535 metadata.urlBase = file.urlBase
536 metadata.user = file.user
537 metadata.userId = file.userId
539 // Support for finding the correct filename for e2ee files should go here
544 func convertNKFilesFromDirectoryReadToItemMetadatas(_ files: [NKFile], account: String, completionHandler: @escaping (_ directoryMetadata: NextcloudItemMetadataTable, _ childDirectoriesMetadatas: [NextcloudItemMetadataTable], _ metadatas: [NextcloudItemMetadataTable]) -> Void) {
546 var directoryMetadataSet = false
547 var directoryMetadata = NextcloudItemMetadataTable()
548 var childDirectoriesMetadatas: [NextcloudItemMetadataTable] = []
549 var metadatas: [NextcloudItemMetadataTable] = []
552 let metadata = convertNKFileToItemMetadata(file, account: account)
554 if metadatas.isEmpty && !directoryMetadataSet {
555 directoryMetadata = metadata;
556 directoryMetadataSet = true;
558 metadatas.append(metadata)
559 if metadata.directory {
560 childDirectoriesMetadatas.append(metadata)
565 completionHandler(directoryMetadata, childDirectoriesMetadatas, metadatas)