From 77621beed8b2879902dd7184562b2ea0d4b6c7e8 Mon Sep 17 00:00:00 2001 From: TSI-amrutwaghmare <96108296+TSI-amrutwaghmare@users.noreply.github.com> Date: Wed, 15 Nov 2023 14:49:41 +0530 Subject: [PATCH 1/4] NMC 1933 - Collabora customisation changes --- .../CollaboraTestCase.swift | 142 +++++ iOSClient/BrowserWeb/NCBrowserWeb.swift | 2 +- .../Data/NCManageDatabase+Metadata.swift | 13 + .../NCCreateFormUploadDocuments.storyboard | 149 +++++ .../Create/NCCreateFormUploadDocuments.swift | 556 ++++++++++++++++++ .../FolderPathCustomCell.swift | 33 ++ .../NMC Custom Views/FolderPathCustomCell.xib | 74 +++ .../NCCreateDocumentCustomTextField.swift | 70 +++ .../NCCreateDocumentCustomTextField.xib | 80 +++ 9 files changed, 1118 insertions(+), 1 deletion(-) create mode 100644 Tests/NextcloudUnitTests/CollaboraTestCase.swift create mode 100644 iOSClient/Main/Create/NCCreateFormUploadDocuments.storyboard create mode 100644 iOSClient/Main/Create/NCCreateFormUploadDocuments.swift create mode 100644 iOSClient/NMC Custom Views/FolderPathCustomCell.swift create mode 100644 iOSClient/NMC Custom Views/FolderPathCustomCell.xib create mode 100644 iOSClient/NMC Custom Views/NCCreateDocumentCustomTextField.swift create mode 100644 iOSClient/NMC Custom Views/NCCreateDocumentCustomTextField.xib diff --git a/Tests/NextcloudUnitTests/CollaboraTestCase.swift b/Tests/NextcloudUnitTests/CollaboraTestCase.swift new file mode 100644 index 0000000000..51b3ecce1f --- /dev/null +++ b/Tests/NextcloudUnitTests/CollaboraTestCase.swift @@ -0,0 +1,142 @@ +// +// CollaboraTestCase.swift +// NextcloudTests +// +// Created by A200073704 on 06/05/23. +// Copyright © 2023 Marino Faggiana. All rights reserved. +// + +@testable import Nextcloud +import XCTest +import NextcloudKit + +class CollaboraTestCase: XCTestCase { + + + override func setUpWithError() throws { + // Put setup code here. This method is called before the invocation of each test method in the class. + } + + override func tearDownWithError() throws { + // Put teardown code here. This method is called after the invocation of each test method in the class. + } + + func testCollaboraDocumentIsPresent() { + + var viewForDocument: NCMenuAction? + + if let image = UIImage(named: "create_file_document") { + viewForDocument = NCMenuAction(title: NSLocalizedString("_create_new_document_", comment: ""), icon: image, action: { _ in + guard let navigationController = UIStoryboard(name: "NCCreateFormUploadDocuments", bundle: nil).instantiateInitialViewController() as? UINavigationController else { + return + } + + let viewController = navigationController.topViewController as? NCCreateFormUploadDocuments + viewController?.titleForm = NSLocalizedString("_create_new_document_", comment: "") + }) + } + + XCTAssertNotNil(viewForDocument) + + } + + func testCollaboraPresentationIsPresent() { + + var viewForPresentation: NCMenuAction? + + if let image = UIImage(named: "create_file_ppt") { + viewForPresentation = NCMenuAction(title: NSLocalizedString("_create_new_presentation_", comment: ""), icon: image, action: { _ in + guard let navigationController = UIStoryboard(name: "NCCreateFormUploadDocuments", bundle: nil).instantiateInitialViewController() as? UINavigationController else { + return + } + + let viewController = navigationController.topViewController as? NCCreateFormUploadDocuments + viewController?.titleForm = NSLocalizedString("_create_new_presentation_", comment: "") + }) + } + + XCTAssertNotNil(viewForPresentation) + + } + + func testCollaboraSpreadsheetIsPresent() { + + var viewForSpreadsheet: NCMenuAction? + + if let image = UIImage(named: "create_file_xls") { + viewForSpreadsheet = NCMenuAction(title: NSLocalizedString("_create_new_spreadsheet_", comment: ""), icon: image, action: { _ in + guard let navigationController = UIStoryboard(name: "NCCreateFormUploadDocuments", bundle: nil).instantiateInitialViewController() as? UINavigationController else { + return + } + + let viewController = navigationController.topViewController as? NCCreateFormUploadDocuments + viewController?.titleForm = NSLocalizedString("_create_new_spreadsheet_", comment: "") + }) + } + + XCTAssertNotNil(viewForSpreadsheet) + + } + + func testTextDocumentIsPresent() { + + var textMenu: NCMenuAction? + + if let image = UIImage(named: "file_txt_menu") { + textMenu = NCMenuAction(title: NSLocalizedString("_create_nextcloudtext_document_", comment: ""), icon: image, action: { _ in + guard let navigationController = UIStoryboard(name: "NCCreateFormUploadDocuments", bundle: nil).instantiateInitialViewController() as? UINavigationController else { + return + } + + let viewController = navigationController.topViewController as? NCCreateFormUploadDocuments + viewController?.titleForm = NSLocalizedString("_create_nextcloudtext_document_", comment: "") + }) + } + + XCTAssertNotNil(textMenu) + + } + + func testTextDocumentAction() { + + let text = NCGlobal.shared.actionTextDocument + XCTAssertNotNil(text, "Text Editor Should be opened") + } + + func testTextFieldIsPresent() { + + let storyboard = UIStoryboard(name: "NCCreateFormUploadDocuments", bundle: nil) + guard let viewController = storyboard.instantiateInitialViewController() as? NCCreateFormUploadDocuments else { + return + } + + // Verify that a text field is present in the view controller + let textFields = viewController.view.subviews.filter { $0 is UITextField } + XCTAssertFalse(textFields.isEmpty, "No text field found in NCCreateFormUploadDocuments") + } + + func testSavePathFolder() { + + let viewController = NCCreateFormUploadDocuments() + + let form : XLFormDescriptor = XLFormDescriptor() as XLFormDescriptor + form.rowNavigationOptions = XLFormRowNavigationOptions.stopDisableRow + + var row : XLFormRowDescriptor + + // the section with the title "Folder Destination" + + row = XLFormRowDescriptor(tag: "ButtonDestinationFolder", rowType: "kNMCFolderCustomCellType", title: "") + row.action.formSelector = #selector(viewController.changeDestinationFolder(_:)) + + // Verify that section was found + XCTAssertNotNil(row, "Expected save path section to exist in form.") + + } + + + + + + +} diff --git a/iOSClient/BrowserWeb/NCBrowserWeb.swift b/iOSClient/BrowserWeb/NCBrowserWeb.swift index 206197c76c..4bf4a4f613 100644 --- a/iOSClient/BrowserWeb/NCBrowserWeb.swift +++ b/iOSClient/BrowserWeb/NCBrowserWeb.swift @@ -36,7 +36,7 @@ class NCBrowserWeb: UIViewController { buttonExit.isHidden = true } else { self.view.bringSubviewToFront(buttonExit) - let image = NCUtility().loadImage(named: "xmark", colors: [.systemBlue]) + let image = NCUtility().loadImage(named: "xmark", colors: [NCBrandColor.shared.customer]) buttonExit.setImage(image, for: .normal) } diff --git a/iOSClient/Data/NCManageDatabase+Metadata.swift b/iOSClient/Data/NCManageDatabase+Metadata.swift index f03ef6cf8d..5ce5a4250b 100644 --- a/iOSClient/Data/NCManageDatabase+Metadata.swift +++ b/iOSClient/Data/NCManageDatabase+Metadata.swift @@ -333,6 +333,18 @@ extension tableMetadata { return !lock || (lockOwner == user && lockOwnerType == 0) } + // Return if is sharable + func isSharable() -> Bool { + guard let capabilities = NCNetworking.shared.capabilities[account] else { + return false + } + if !capabilities.fileSharingApiEnabled || (capabilities.e2EEEnabled && isDirectoryE2EE) { +// if !NCCapabilities.shared.getCapabilities(account: account).capabilityFileSharingApiEnabled || (NCCapabilities.shared.getCapabilities(account: account).capabilityE2EEEnabled && isDirectoryE2EE), !e2eEncrypted { + return false + } + return true + } + /// Returns a detached (unmanaged) deep copy of the current `tableMetadata` object. /// /// - Note: The Realm `List` properties containing primitive types (e.g., `tags`, `shareType`) are copied automatically @@ -354,6 +366,7 @@ extension tableMetadata { extension NCManageDatabase { #if !EXTENSION + func isMetadataShareOrMounted(metadata: tableMetadata, metadataFolder: tableMetadata?) -> Bool { var isShare = false var isMounted = false diff --git a/iOSClient/Main/Create/NCCreateFormUploadDocuments.storyboard b/iOSClient/Main/Create/NCCreateFormUploadDocuments.storyboard new file mode 100644 index 0000000000..6995ac52d9 --- /dev/null +++ b/iOSClient/Main/Create/NCCreateFormUploadDocuments.storyboard @@ -0,0 +1,149 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/iOSClient/Main/Create/NCCreateFormUploadDocuments.swift b/iOSClient/Main/Create/NCCreateFormUploadDocuments.swift new file mode 100644 index 0000000000..61177fa174 --- /dev/null +++ b/iOSClient/Main/Create/NCCreateFormUploadDocuments.swift @@ -0,0 +1,556 @@ +// +// NCCreateFormUploadDocuments.swift +// Nextcloud +// +// Created by Marino Faggiana on 14/11/18. +// Copyright © 2018 Marino Faggiana. All rights reserved. +// +// Author Marino Faggiana +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// + +import UIKit +import NextcloudKit +import XLForm + +// MARK: - + +@objc class NCCreateFormUploadDocuments: XLFormViewController, NCSelectDelegate, UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout, NCCreateFormUploadConflictDelegate { + + @IBOutlet weak var indicator: UIActivityIndicatorView! + @IBOutlet weak var collectionView: UICollectionView! + @IBOutlet weak var collectionViewHeigth: NSLayoutConstraint! + + let appDelegate = (UIApplication.shared.delegate as? AppDelegate)! + var editorId = "" + var creatorId = "" + var typeTemplate = "" + var templateIdentifier = "" + var serverUrl = "" + var fileNameFolder = "" + var fileName = "" + var fileNameExtension = "" + var titleForm = "" + var listOfTemplate: [NKEditorTemplates] = [] + var selectTemplate: NKEditorTemplates? + let utilityFileSystem = NCUtilityFileSystem() + let utility = NCUtility() + + // Layout + let numItems = 2 + let sectionInsets: CGFloat = 10 + let highLabelName: CGFloat = 20 + + // MARK: - View Life Cycle + + override func viewDidLoad() { + super.viewDidLoad() + + if serverUrl == utilityFileSystem.getHomeServer(urlBase: appDelegate.urlBase, userId: appDelegate.userId) { + fileNameFolder = "/" + } else { + fileNameFolder = (serverUrl as NSString).lastPathComponent + } + + self.tableView.separatorStyle = UITableViewCell.SeparatorStyle.none + + view.backgroundColor = .systemGroupedBackground + collectionView.backgroundColor = .systemGroupedBackground + tableView.backgroundColor = .secondarySystemGroupedBackground + + let cancelButton: UIBarButtonItem = UIBarButtonItem(title: NSLocalizedString("_cancel_", comment: ""), style: UIBarButtonItem.Style.plain, target: self, action: #selector(cancel)) + let saveButton: UIBarButtonItem = UIBarButtonItem(title: NSLocalizedString("_save_", comment: ""), style: UIBarButtonItem.Style.plain, target: self, action: #selector(save)) + cancelButton.tintColor = NCBrandColor.shared.brand + saveButton.tintColor = NCBrandColor.shared.brand + + self.navigationItem.leftBarButtonItem = cancelButton + self.navigationItem.rightBarButtonItem = saveButton + self.navigationItem.rightBarButtonItem?.isEnabled = false + + // title + self.title = titleForm + + fileName = NCUtilityFileSystem().createFileNameDate("Text", ext: getFileExtension()) + + initializeForm() + getTemplate() + } + + // MARK: - Tableview (XLForm) + + func initializeForm() { + + let form: XLFormDescriptor = XLFormDescriptor() as XLFormDescriptor + form.rowNavigationOptions = XLFormRowNavigationOptions.stopDisableRow + + var section: XLFormSectionDescriptor + var row: XLFormRowDescriptor + + // Section: Destination Folder + + section = XLFormSectionDescriptor.formSection(withTitle: NSLocalizedString("_save_path_", comment: "").uppercased()) + section.footerTitle = " " + form.addFormSection(section) + + XLFormViewController.cellClassesForRowDescriptorTypes()["kNMCFolderCustomCellType"] = FolderPathCustomCell.self + row = XLFormRowDescriptor(tag: "ButtonDestinationFolder", rowType: "kNMCFolderCustomCellType", title: "") + row.action.formSelector = #selector(changeDestinationFolder(_:)) + row.cellConfig["folderImage.image"] = UIImage(named: "folder")!.imageColor(NCBrandColor.shared.customer) + row.cellConfig["photoLabel.textAlignment"] = NSTextAlignment.right.rawValue + row.cellConfig["photoLabel.font"] = UIFont.systemFont(ofSize: 15.0) + row.cellConfig["photoLabel.textColor"] = UIColor.label //photos + if(self.fileNameFolder == "/"){ + row.cellConfig["photoLabel.text"] = NSLocalizedString("_prefix_upload_path_", comment: "") + }else{ + row.cellConfig["photoLabel.text"] = self.fileNameFolder + } + row.cellConfig["textLabel.text"] = "" + + section.addFormRow(row) + + // Section: File Name + + section = XLFormSectionDescriptor.formSection(withTitle: NSLocalizedString("_filename_", comment: "").uppercased()) + form.addFormSection(section) + + XLFormViewController.cellClassesForRowDescriptorTypes()["kMyAppCustomCellType"] = NCCreateDocumentCustomTextField.self + + row = XLFormRowDescriptor(tag: "fileName", rowType: "kMyAppCustomCellType", title: NSLocalizedString("_filename_", comment: "")) + row.cellClass = NCCreateDocumentCustomTextField.self + + row.cellConfigAtConfigure["backgroundColor"] = UIColor.secondarySystemGroupedBackground; + row.cellConfig["fileNameTextField.textAlignment"] = NSTextAlignment.left.rawValue + row.cellConfig["fileNameTextField.font"] = UIFont.systemFont(ofSize: 15.0) + row.cellConfig["fileNameTextField.textColor"] = UIColor.label + row.cellConfig["fileNameTextField.placeholder"] = self.fileName + + section.addFormRow(row) + + self.form = form + // tableView.reloadData() + // collectionView.reloadData() + } + + override func tableView(_ tableView: UITableView, willDisplayHeaderView view: UIView, forSection section: Int) { + let header = view as? UITableViewHeaderFooterView + header?.textLabel?.font = UIFont.systemFont(ofSize: 13.0) + header?.textLabel?.textColor = .gray + header?.tintColor = tableView.backgroundColor + } + + override func tableView(_ tableView: UITableView, willDisplayFooterView view: UIView, forSection section: Int) { + let header = view as? UITableViewHeaderFooterView + header?.textLabel?.font = UIFont.systemFont(ofSize: 13.0) + header?.textLabel?.textColor = .gray + header?.tintColor = tableView.backgroundColor + } + + // MARK: - CollectionView + + func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { + return listOfTemplate.count + } + + func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { + + let itemWidth: CGFloat = (collectionView.frame.width - (sectionInsets * 4) - CGFloat(numItems)) / CGFloat(numItems) + let itemHeight: CGFloat = itemWidth + highLabelName + + collectionViewHeigth.constant = itemHeight + sectionInsets + + return CGSize(width: itemWidth, height: itemHeight) + } + + func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { + + let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) + + let template = listOfTemplate[indexPath.row] + + // image + let imagePreview = cell.viewWithTag(100) as? UIImageView + if !template.preview.isEmpty { + let fileNameLocalPath = utilityFileSystem.directoryUserData + "/" + template.name + ".png" + if FileManager.default.fileExists(atPath: fileNameLocalPath) { + let imageURL = URL(fileURLWithPath: fileNameLocalPath) + if let image = UIImage(contentsOfFile: imageURL.path) { + imagePreview?.image = image + } + } else { + getImageFromTemplate(name: template.name, preview: template.preview, indexPath: indexPath) + } + } + + // name + let name = cell.viewWithTag(200) as? UILabel + name?.text = template.name + name?.textColor = .secondarySystemGroupedBackground + + // select + let imageSelect = cell.viewWithTag(300) as? UIImageView + if selectTemplate != nil && selectTemplate?.name == template.name { + cell.backgroundColor = .label + imageSelect?.image = UIImage(named: "plus100") + imageSelect?.isHidden = false + } else { + cell.backgroundColor = .secondarySystemGroupedBackground + imageSelect?.isHidden = true + } + + return cell + } + + func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { + + let template = listOfTemplate[indexPath.row] + + selectTemplate = template + fileNameExtension = template.ext + + collectionView.reloadData() + } + + // MARK: - Action + + func dismissSelect(serverUrl: String?, metadata: tableMetadata?, type: String, items: [Any], overwrite: Bool, copy: Bool, move: Bool) { + + guard let serverUrl = serverUrl else { return } + + self.serverUrl = serverUrl + if serverUrl == utilityFileSystem.getHomeServer(urlBase: appDelegate.urlBase, userId: appDelegate.userId) { + fileNameFolder = "/" + } else { + fileNameFolder = (serverUrl as NSString).lastPathComponent + } + + let buttonDestinationFolder: XLFormRowDescriptor = self.form.formRow(withTag: "ButtonDestinationFolder")! + buttonDestinationFolder.cellConfig["photoLabel.text"] = fileNameFolder + + self.tableView.reloadData() + } + +// override func formRowDescriptorValueHasChanged(_ formRow: XLFormRowDescriptor!, oldValue: Any!, newValue: Any!) { +// super.formRowDescriptorValueHasChanged(formRow, oldValue: oldValue, newValue: newValue) +//// if formRow.tag == "fileName" { +//// self.form.delegate = nil +//// if let fileNameNew = formRow.value { +//// self.fileName = CCUtility.removeForbiddenCharactersServer(fileNameNew as? String) +//// } +//// formRow.value = self.fileName +//// self.form.delegate = self +//// } +// } + + @objc func changeDestinationFolder(_ sender: XLFormRowDescriptor) { + + self.deselectFormRow(sender) + + let storyboard = UIStoryboard(name: "NCSelect", bundle: nil) + if let navigationController = storyboard.instantiateInitialViewController() as? UINavigationController, + let viewController = navigationController.topViewController as? NCSelect { + + viewController.delegate = self + viewController.typeOfCommandView = .selectCreateFolder + + self.present(navigationController, animated: true, completion: nil) + } + } + + @objc func save() { + + guard let selectTemplate = self.selectTemplate else { return } + templateIdentifier = selectTemplate.identifier + + let rowFileName: XLFormRowDescriptor = self.form.formRow(withTag: "fileName")! + var fileName = rowFileName.value as? String + if fileName?.isEmpty ?? false || fileName == nil { + fileName = NCUtilityFileSystem().createFileNameDate("Text", ext: getFileExtension()) + } else if fileName?.trimmingCharacters(in: .whitespaces).isEmpty ?? false { + let alert = UIAlertController(title: "", message: NSLocalizedString("_please_enter_file_name_", comment: ""), preferredStyle: .alert) + alert.addAction(UIAlertAction(title: NSLocalizedString("_ok_", comment: ""), style: .cancel, handler: nil)) + self.present(alert, animated: true) + return + } + guard var fileNameForm: String = fileName, !fileNameForm.isEmpty else { return } + + // Trim whitespaces after checks above + fileNameForm = fileNameForm.trimmingCharacters(in: .whitespacesAndNewlines) + + let result = NextcloudKit.shared.nkCommonInstance.getInternalType(fileName: fileNameForm, mimeType: "", directory: false) + if utility.isDirectEditing(account: appDelegate.account, contentType: result.mimeType).isEmpty { + fileNameForm = (fileNameForm as NSString).deletingPathExtension + "." + fileNameExtension + } + + if NCManageDatabase.shared.getMetadataConflict(account: appDelegate.account, serverUrl: serverUrl, fileNameView: String(describing: fileNameForm)) != nil { + + let metadataForUpload = NCManageDatabase.shared.createMetadata(account: appDelegate.account, user: appDelegate.user, userId: appDelegate.userId, fileName: String(describing: fileNameForm), fileNameView: String(describing: fileNameForm), ocId: "", serverUrl: serverUrl, urlBase: appDelegate.urlBase, url: "", contentType: "") + + guard let conflict = UIStoryboard(name: "NCCreateFormUploadConflict", bundle: nil).instantiateInitialViewController() as? NCCreateFormUploadConflict else { return } + + conflict.textLabelDetailNewFile = NSLocalizedString("_now_", comment: "") + conflict.alwaysNewFileNameNumber = true + conflict.serverUrl = serverUrl + conflict.metadatasUploadInConflict = [metadataForUpload] + conflict.delegate = self + + self.present(conflict, animated: true, completion: nil) + + } else { + + let fileNamePath = utilityFileSystem.getFileNamePath(String(describing: fileNameForm), serverUrl: serverUrl, urlBase: appDelegate.urlBase, userId: appDelegate.userId) + createDocument(fileNamePath: fileNamePath, fileName: String(describing: fileNameForm)) + } + } + + func dismissCreateFormUploadConflict(metadatas: [tableMetadata]?) { + + if let metadatas, metadatas.count > 0 { + let fileName = metadatas[0].fileName + let fileNamePath = utilityFileSystem.getFileNamePath(fileName, serverUrl: serverUrl, urlBase: appDelegate.urlBase, userId: appDelegate.userId) + createDocument(fileNamePath: fileNamePath, fileName: fileName) + } else { + DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) { + self.cancel() + } + } + } + + func createDocument(fileNamePath: String, fileName: String) { + + self.navigationItem.rightBarButtonItem?.isEnabled = false + var UUID = NSUUID().uuidString + UUID = "TEMP" + UUID.replacingOccurrences(of: "-", with: "") + + if self.editorId == NCGlobal.shared.editorText || self.editorId == NCGlobal.shared.editorOnlyoffice { + + var options = NKRequestOptions() + if self.editorId == NCGlobal.shared.editorOnlyoffice { + options = NKRequestOptions(customUserAgent: utility.getCustomUserAgentOnlyOffice()) + } else if editorId == NCGlobal.shared.editorText { + options = NKRequestOptions(customUserAgent: utility.getCustomUserAgentNCText()) + } + + NextcloudKit.shared.NCTextCreateFile(fileNamePath: fileNamePath, editorId: editorId, creatorId: creatorId, templateId: templateIdentifier, options: options) { account, url, _, error in + guard error == .success, account == self.appDelegate.account, let url = url else { + self.navigationItem.rightBarButtonItem?.isEnabled = true + NCContentPresenter().showError(error: error) + return + } + + var results = NextcloudKit.shared.nkCommonInstance.getInternalType(fileName: fileName, mimeType: "", directory: false) + // FIXME: iOS 12.0,* don't detect UTI text/markdown, text/x-markdown + if results.mimeType.isEmpty { + results.mimeType = "text/x-markdown" + } + + self.dismiss(animated: true, completion: { + let metadata = NCManageDatabase.shared.createMetadata(account: self.appDelegate.account, user: self.appDelegate.user, userId: self.appDelegate.userId, fileName: fileName, fileNameView: fileName, ocId: UUID, serverUrl: self.serverUrl, urlBase: self.appDelegate.urlBase, url: url, contentType: results.mimeType) + if let viewController = self.appDelegate.activeViewController { + NCViewer().view(viewController: viewController, metadata: metadata, metadatas: [metadata], imageIcon: nil) + } + }) + } + } + + if self.editorId == NCGlobal.shared.editorCollabora { + + NextcloudKit.shared.createRichdocuments(path: fileNamePath, templateId: templateIdentifier) { account, url, _, error in + guard error == .success, account == self.appDelegate.account, let url = url else { + self.navigationItem.rightBarButtonItem?.isEnabled = true + NCContentPresenter().showError(error: error) + return + } + + self.dismiss(animated: true, completion: { + let createFileName = (fileName as NSString).deletingPathExtension + "." + self.fileNameExtension + let metadata = NCManageDatabase.shared.createMetadata(account: self.appDelegate.account, user: self.appDelegate.user, userId: self.appDelegate.userId, fileName: createFileName, fileNameView: createFileName, ocId: UUID, serverUrl: self.serverUrl, urlBase: self.appDelegate.urlBase, url: url, contentType: "") + if let viewController = self.appDelegate.activeViewController { + NCViewer().view(viewController: viewController, metadata: metadata, metadatas: [metadata], imageIcon: nil) + } + }) + } + } + } + + @objc func cancel() { + + self.dismiss(animated: true, completion: nil) + } + + // MARK: NC API + + func getTemplate() { + + indicator.color = NCBrandColor.shared.brandElement + indicator.startAnimating() + + if self.editorId == NCGlobal.shared.editorText || self.editorId == NCGlobal.shared.editorOnlyoffice { + + var options = NKRequestOptions() + if self.editorId == NCGlobal.shared.editorOnlyoffice { + options = NKRequestOptions(customUserAgent: utility.getCustomUserAgentOnlyOffice()) + } else if editorId == NCGlobal.shared.editorText { + options = NKRequestOptions(customUserAgent: utility.getCustomUserAgentNCText()) + } + + NextcloudKit.shared.NCTextGetListOfTemplates(options: options) { account, templates, _, error in + + self.indicator.stopAnimating() + + if error == .success && account == self.appDelegate.account { + + for template in templates { + + let temp = NKEditorTemplates() + + temp.identifier = template.identifier + temp.ext = template.ext + temp.name = template.name + temp.preview = template.preview + + self.listOfTemplate.append(temp) + + // default: template empty + if temp.preview.isEmpty { + self.selectTemplate = temp + self.fileNameExtension = template.ext + self.navigationItem.rightBarButtonItem?.isEnabled = true + } + } + } + + if self.listOfTemplate.isEmpty { + + let temp = NKEditorTemplates() + + temp.identifier = "" + if self.editorId == NCGlobal.shared.editorText { + temp.ext = "md" + } else if self.editorId == NCGlobal.shared.editorOnlyoffice && self.typeTemplate == NCGlobal.shared.templateDocument { + temp.ext = "docx" + } else if self.editorId == NCGlobal.shared.editorOnlyoffice && self.typeTemplate == NCGlobal.shared.templateSpreadsheet { + temp.ext = "xlsx" + } else if self.editorId == NCGlobal.shared.editorOnlyoffice && self.typeTemplate == NCGlobal.shared.templatePresentation { + temp.ext = "pptx" + } + temp.name = "Empty" + temp.preview = "" + + self.listOfTemplate.append(temp) + + self.selectTemplate = temp + self.fileNameExtension = temp.ext + self.navigationItem.rightBarButtonItem?.isEnabled = true + } + + self.collectionView.reloadData() + } + + } + + if self.editorId == NCGlobal.shared.editorCollabora { + + NextcloudKit.shared.getTemplatesRichdocuments(typeTemplate: typeTemplate) { account, templates, _, error in + + self.indicator.stopAnimating() + + if error == .success && account == self.appDelegate.account { + + for template in templates! { + + let temp = NKEditorTemplates() + + temp.identifier = "\(template.templateId)" + temp.delete = template.delete + temp.ext = template.ext + temp.name = template.name + temp.preview = template.preview + temp.type = template.type + + self.listOfTemplate.append(temp) + + // default: template empty + if temp.preview.isEmpty { + self.selectTemplate = temp + self.fileNameExtension = temp.ext + self.navigationItem.rightBarButtonItem?.isEnabled = true + } + } + } + + if self.listOfTemplate.isEmpty { + + let temp = NKEditorTemplates() + + temp.identifier = "" + if self.typeTemplate == NCGlobal.shared.templateDocument { + temp.ext = "docx" + } else if self.typeTemplate == NCGlobal.shared.templateSpreadsheet { + temp.ext = "xlsx" + } else if self.typeTemplate == NCGlobal.shared.templatePresentation { + temp.ext = "pptx" + } + temp.name = "Empty" + temp.preview = "" + + self.listOfTemplate.append(temp) + + self.selectTemplate = temp + self.fileNameExtension = temp.ext + self.navigationItem.rightBarButtonItem?.isEnabled = true + } + + self.collectionView.reloadData() + } + } + } + + func getImageFromTemplate(name: String, preview: String, indexPath: IndexPath) { + + let fileNameLocalPath = utilityFileSystem.directoryUserData + "/" + name + ".png" + + NextcloudKit.shared.download(serverUrlFileName: preview, fileNameLocalPath: fileNameLocalPath, requestHandler: { _ in + + }, taskHandler: { _ in + + }, progressHandler: { _ in + + }) { account, _, _, _, _, _, error in + + if error == .success && account == self.appDelegate.account { + self.collectionView.reloadItems(at: [indexPath]) + } else if error != .success { + print("\(error.errorCode)") + } else { + print("[ERROR] It has been changed user during networking process, error.") + } + } + } + + func getFileExtension() -> String { + switch typeTemplate { + case NCGlobal.shared.editorText: + return "md" + case NCGlobal.shared.templateDocument: + return "docx" + case NCGlobal.shared.templateSpreadsheet: + return "xlsx" + case NCGlobal.shared.templatePresentation: + return "pptx" + default: + return "" + } + } +} diff --git a/iOSClient/NMC Custom Views/FolderPathCustomCell.swift b/iOSClient/NMC Custom Views/FolderPathCustomCell.swift new file mode 100644 index 0000000000..901ce667ae --- /dev/null +++ b/iOSClient/NMC Custom Views/FolderPathCustomCell.swift @@ -0,0 +1,33 @@ +// +// FolderPathCustomCell.swift +// Nextcloud +// +// Created by A200073704 on 04/05/23. +// Copyright © 2023 Marino Faggiana. All rights reserved. +// + +import UIKit + +class FolderPathCustomCell: XLFormButtonCell{ + + @IBOutlet weak var photoLabel: UILabel! + @IBOutlet weak var folderImage: UIImageView! + @IBOutlet weak var bottomLineView: UIView! + + override func awakeFromNib() { + super.awakeFromNib() + } + + override func configure() { + super.configure() + } + + override func update() { + super.update() + if (rowDescriptor.tag == "PhotoButtonDestinationFolder"){ + bottomLineView.isHidden = true + }else{ + bottomLineView.isHidden = false + } + } +} diff --git a/iOSClient/NMC Custom Views/FolderPathCustomCell.xib b/iOSClient/NMC Custom Views/FolderPathCustomCell.xib new file mode 100644 index 0000000000..a231ae7c72 --- /dev/null +++ b/iOSClient/NMC Custom Views/FolderPathCustomCell.xib @@ -0,0 +1,74 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/iOSClient/NMC Custom Views/NCCreateDocumentCustomTextField.swift b/iOSClient/NMC Custom Views/NCCreateDocumentCustomTextField.swift new file mode 100644 index 0000000000..0242fa0f7a --- /dev/null +++ b/iOSClient/NMC Custom Views/NCCreateDocumentCustomTextField.swift @@ -0,0 +1,70 @@ +// +// NCCreateDocumentCustomTextField.swift +// Nextcloud +// +// Created by A200073704 on 04/05/23. +// Copyright © 2023 Marino Faggiana. All rights reserved. +// + +import UIKit + +class NCCreateDocumentCustomTextField: XLFormBaseCell,UITextFieldDelegate { + + @IBOutlet weak var fileNameTextField: UITextField! + + override func awakeFromNib() { + super.awakeFromNib() + // Initialization code + + fileNameTextField.delegate = self + + } + + override func setSelected(_ selected: Bool, animated: Bool) { + super.setSelected(selected, animated: animated) + + // Configure the view for the selected state + } + + override func configure() { + super.configure() + } + + override func update() { + super.update() + } + + func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool { + + if fileNameTextField == textField { + if let rowDescriptor = rowDescriptor { + if let text = textField.text{ + if (text + " ").isEmpty == false { + rowDescriptor.value = self.fileNameTextField.text! + string + } else { + rowDescriptor.value = nil + } + } + } + } + + self.formViewController().textField(textField, shouldChangeCharactersIn: range, replacementString: string) + + + return true + } + + func textFieldShouldReturn(_ textField: UITextField) -> Bool { + self.formViewController()?.textFieldShouldReturn(fileNameTextField) + return true + } + + func textFieldShouldClear(_ textField: UITextField) -> Bool { + self.formViewController()?.textFieldShouldClear(fileNameTextField) + return true + } + + override class func formDescriptorCellHeight(for rowDescriptor: XLFormRowDescriptor!) -> CGFloat { + return 45 + } +} diff --git a/iOSClient/NMC Custom Views/NCCreateDocumentCustomTextField.xib b/iOSClient/NMC Custom Views/NCCreateDocumentCustomTextField.xib new file mode 100644 index 0000000000..3fd86bb2d6 --- /dev/null +++ b/iOSClient/NMC Custom Views/NCCreateDocumentCustomTextField.xib @@ -0,0 +1,80 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 1edebbad60b77bfa6be69b61d6c8e61035f43a09 Mon Sep 17 00:00:00 2001 From: harshada-15-tsys Date: Fri, 11 Apr 2025 10:54:41 +0530 Subject: [PATCH 2/4] NMC 1933 - Collabora customisations changes --- Nextcloud.xcodeproj/project.pbxproj | 41 +++++ .../Data/NCManageDatabase+Metadata.swift | 159 +++++++++++++++++- .../Create/NCCreateFormUploadDocuments.swift | 55 +++--- 3 files changed, 226 insertions(+), 29 deletions(-) diff --git a/Nextcloud.xcodeproj/project.pbxproj b/Nextcloud.xcodeproj/project.pbxproj index 8dd4c5545f..f3d77d9c6e 100644 --- a/Nextcloud.xcodeproj/project.pbxproj +++ b/Nextcloud.xcodeproj/project.pbxproj @@ -85,6 +85,16 @@ AFCE353527E4ED5900FEA6C2 /* DateFormatter+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = AFCE353427E4ED5900FEA6C2 /* DateFormatter+Extension.swift */; }; AFCE353727E4ED7B00FEA6C2 /* NCShareCells.swift in Sources */ = {isa = PBXBuildFile; fileRef = AFCE353627E4ED7B00FEA6C2 /* NCShareCells.swift */; }; AFCE353927E5DE0500FEA6C2 /* Shareable.swift in Sources */ = {isa = PBXBuildFile; fileRef = AFCE353827E5DE0400FEA6C2 /* Shareable.swift */; }; + AFCE353927E5DE0500FEA6C2 /* NCShare+Helper.swift in Sources */ = {isa = PBXBuildFile; fileRef = AFCE353827E5DE0400FEA6C2 /* NCShare+Helper.swift */; }; + B52FAE902DA8D9E1001AB1BD /* CollaboraTestCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = B52FAE8F2DA8D9E1001AB1BD /* CollaboraTestCase.swift */; }; + B52FAE932DA8DCB2001AB1BD /* NCCreateFormUploadDocuments.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = B52FAE912DA8DCB2001AB1BD /* NCCreateFormUploadDocuments.storyboard */; }; + B52FAE942DA8DCB2001AB1BD /* NCCreateFormUploadDocuments.swift in Sources */ = {isa = PBXBuildFile; fileRef = B52FAE922DA8DCB2001AB1BD /* NCCreateFormUploadDocuments.swift */; }; + B52FAE9C2DA8DED9001AB1BD /* NCCreateDocumentCustomTextField.xib in Resources */ = {isa = PBXBuildFile; fileRef = B52FAE9A2DA8DED9001AB1BD /* NCCreateDocumentCustomTextField.xib */; }; + B52FAE9D2DA8DED9001AB1BD /* FolderPathCustomCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = B52FAE982DA8DED9001AB1BD /* FolderPathCustomCell.xib */; }; + B52FAE9E2DA8DED9001AB1BD /* NCCreateDocumentCustomTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = B52FAE992DA8DED9001AB1BD /* NCCreateDocumentCustomTextField.swift */; }; + B52FAE9F2DA8DED9001AB1BD /* FolderPathCustomCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = B52FAE972DA8DED9001AB1BD /* FolderPathCustomCell.swift */; }; + C04E2F232A17BB4D001BAD85 /* FilesIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C04E2F222A17BB4D001BAD85 /* FilesIntegrationTests.swift */; }; + D575039F27146F93008DC9DC /* String+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7A0D1342591FBC5008F8A13 /* String+Extension.swift */; }; D5B6AA7827200C7200D49C24 /* NCActivityTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5B6AA7727200C7200D49C24 /* NCActivityTableViewCell.swift */; }; F310B1EF2BA862F1001C42F5 /* NCViewerMedia+VisionKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = F310B1EE2BA862F1001C42F5 /* NCViewerMedia+VisionKit.swift */; }; F321DA8A2B71205A00DDA0E6 /* NCTrashSelectTabBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = F321DA892B71205A00DDA0E6 /* NCTrashSelectTabBar.swift */; }; @@ -1221,6 +1231,14 @@ AFCE353427E4ED5900FEA6C2 /* DateFormatter+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DateFormatter+Extension.swift"; sourceTree = ""; }; AFCE353627E4ED7B00FEA6C2 /* NCShareCells.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCShareCells.swift; sourceTree = ""; }; AFCE353827E5DE0400FEA6C2 /* Shareable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Shareable.swift; sourceTree = ""; }; + AFCE353827E5DE0400FEA6C2 /* NCShare+Helper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NCShare+Helper.swift"; sourceTree = ""; }; + B52FAE8F2DA8D9E1001AB1BD /* CollaboraTestCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CollaboraTestCase.swift; sourceTree = ""; }; + B52FAE912DA8DCB2001AB1BD /* NCCreateFormUploadDocuments.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = NCCreateFormUploadDocuments.storyboard; sourceTree = ""; }; + B52FAE922DA8DCB2001AB1BD /* NCCreateFormUploadDocuments.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCCreateFormUploadDocuments.swift; sourceTree = ""; }; + B52FAE972DA8DED9001AB1BD /* FolderPathCustomCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FolderPathCustomCell.swift; sourceTree = ""; }; + B52FAE982DA8DED9001AB1BD /* FolderPathCustomCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = FolderPathCustomCell.xib; sourceTree = ""; }; + B52FAE992DA8DED9001AB1BD /* NCCreateDocumentCustomTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCCreateDocumentCustomTextField.swift; sourceTree = ""; }; + B52FAE9A2DA8DED9001AB1BD /* NCCreateDocumentCustomTextField.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = NCCreateDocumentCustomTextField.xib; sourceTree = ""; }; C0046CDA2A17B98400D87C9D /* NextcloudUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = NextcloudUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; C04E2F202A17BB4D001BAD85 /* NextcloudIntegrationTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = NextcloudIntegrationTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; D5B6AA7727200C7200D49C24 /* NCActivityTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCActivityTableViewCell.swift; sourceTree = ""; }; @@ -2024,6 +2042,8 @@ isa = PBXGroup; children = ( AA52EB452D42AC5A0089C348 /* Placeholder.swift */, + B52FAE8F2DA8D9E1001AB1BD /* CollaboraTestCase.swift */, + AF8ED1FB2757821000B8DBC4 /* NextcloudUnitTests.swift */, ); path = NextcloudUnitTests; sourceTree = ""; @@ -2062,6 +2082,17 @@ path = Advanced; sourceTree = ""; }; + B52FAE9B2DA8DED9001AB1BD /* NMC Custom Views */ = { + isa = PBXGroup; + children = ( + B52FAE972DA8DED9001AB1BD /* FolderPathCustomCell.swift */, + B52FAE982DA8DED9001AB1BD /* FolderPathCustomCell.xib */, + B52FAE992DA8DED9001AB1BD /* NCCreateDocumentCustomTextField.swift */, + B52FAE9A2DA8DED9001AB1BD /* NCCreateDocumentCustomTextField.xib */, + ); + path = "NMC Custom Views"; + sourceTree = ""; + }; C0046CDB2A17B98400D87C9D /* NextcloudUITests */ = { isa = PBXGroup; children = ( @@ -3103,6 +3134,8 @@ F7DFB7E9219C5A0500680748 /* Create */ = { isa = PBXGroup; children = ( + B52FAE912DA8DCB2001AB1BD /* NCCreateFormUploadDocuments.storyboard */, + B52FAE922DA8DCB2001AB1BD /* NCCreateFormUploadDocuments.swift */, F7FA7FFD2C0F4F3B0072FC60 /* Upload Assets */, F7A509242C26BD5D00326106 /* NCCreate.swift */, F704B5E22430AA6F00632F5F /* NCCreateFormUploadConflict.storyboard */, @@ -3226,6 +3259,7 @@ isa = PBXGroup; children = ( AA517BB42D66149900F8D37C /* .tx */, + B52FAE9B2DA8DED9001AB1BD /* NMC Custom Views */, F702F2CC25EE5B4F008F8E80 /* AppDelegate.swift */, F794E13E2BBC0F70003693D7 /* SceneDelegate.swift */, F7CF067A2E0FF38F0063AD04 /* NCAppStateManager.swift */, @@ -3952,6 +3986,8 @@ F717402D24F699A5000C87D5 /* NCFavorite.storyboard in Resources */, F723B3DD22FC6D1D00301EFE /* NCShareCommentsCell.xib in Resources */, F78ACD4B21903F850088454D /* NCTrashListCell.xib in Resources */, + B52FAE9C2DA8DED9001AB1BD /* NCCreateDocumentCustomTextField.xib in Resources */, + B52FAE9D2DA8DED9001AB1BD /* FolderPathCustomCell.xib in Resources */, AF93471927E2361E002537EE /* NCShareAdvancePermissionFooter.xib in Resources */, F7725A61251F33BB00D125E0 /* NCFiles.storyboard in Resources */, F7CBC1232BAC8B0000EC1D55 /* NCSectionFirstHeaderEmptyData.xib in Resources */, @@ -3971,6 +4007,7 @@ F78ACD54219047D40088454D /* NCSectionFooter.xib in Resources */, F751247E2C42919C00E63DB8 /* NCPhotoCell.xib in Resources */, F704B5E32430AA6F00632F5F /* NCCreateFormUploadConflict.storyboard in Resources */, + B52FAE932DA8DCB2001AB1BD /* NCCreateFormUploadDocuments.storyboard in Resources */, F7EDE509262DA9D600414FE6 /* NCSelectCommandViewSelect.xib in Resources */, F732D23327CF8AED000B0F1B /* NCPlayerToolBar.xib in Resources */, F73D11FA253C5F4800DF9BEC /* NCViewerNextcloudText.storyboard in Resources */, @@ -4392,6 +4429,7 @@ F7E7AEA72BA32D0000512E52 /* NCCollectionViewUnifiedSearch.swift in Sources */, F73EF7A72B0223900087E6E9 /* NCManageDatabase+Comments.swift in Sources */, F33918C42C7CD8F2002D9AA1 /* FileNameValidator+Extensions.swift in Sources */, + B52FAE942DA8DCB2001AB1BD /* NCCreateFormUploadDocuments.swift in Sources */, F39170AD2CB82024006127BC /* FileAutoRenamer+Extensions.swift in Sources */, F799DF882C4B83CC003410B5 /* NCCollectionViewCommon+EasyTipView.swift in Sources */, F7AE00F8230E81CB007ACF8A /* NCBrowserWeb.swift in Sources */, @@ -4525,6 +4563,9 @@ F76882352C0DD1E7001CF441 /* NCWebBrowserView.swift in Sources */, F3A047972BD2668800658E7B /* NCAssistantEmptyView.swift in Sources */, F757CC8D29E82D0500F31428 /* NCGroupfolders.swift in Sources */, + F760329F252F0F8E0015A421 /* NCTransferCell.swift in Sources */, + B52FAE9E2DA8DED9001AB1BD /* NCCreateDocumentCustomTextField.swift in Sources */, + B52FAE9F2DA8DED9001AB1BD /* FolderPathCustomCell.swift in Sources */, F3BB46542A3A1E9D00461F6E /* CCCellMore.swift in Sources */, AF68326A27BE65A90010BF0B /* NCMenuAction.swift in Sources */, F7F3E58E2D3BB65600A32B14 /* NCNetworking+Recommendations.swift in Sources */, diff --git a/iOSClient/Data/NCManageDatabase+Metadata.swift b/iOSClient/Data/NCManageDatabase+Metadata.swift index 5ce5a4250b..8d93019f18 100644 --- a/iOSClient/Data/NCManageDatabase+Metadata.swift +++ b/iOSClient/Data/NCManageDatabase+Metadata.swift @@ -139,10 +139,37 @@ extension tableMetadata { (fileNameView as NSString).deletingPathExtension } + var isRenameable: Bool { + if !NCMetadataPermissions.canRename(self) { + return false + } + if lock { + return false + } + if !isDirectoryE2EE && e2eEncrypted { + return false + } + return true + } + + var isPrintable: Bool { + if isDocumentViewableOnly { + return false + } + if ["application/pdf", "com.adobe.pdf"].contains(contentType) || contentType.hasPrefix("text/") || classFile == NKCommon.TypeClassFile.image.rawValue { + return true + } + return false + } + var isSavebleInCameraRoll: Bool { return (classFile == NKTypeClassFile.image.rawValue && contentType != "image/svg+xml") || classFile == NKTypeClassFile.video.rawValue } + var isDocumentViewableOnly: Bool { + sharePermissionsCollaborationServices == NCPermissions().permissionReadShare && classFile == NKCommon.TypeClassFile.document.rawValue + } + var isAudioOrVideo: Bool { return classFile == NKTypeClassFile.audio.rawValue || classFile == NKTypeClassFile.video.rawValue } @@ -168,7 +195,7 @@ extension tableMetadata { } var isCopyableInPasteboard: Bool { - !directory + !isDocumentViewableOnly && !directory } #if !EXTENSION_FILE_PROVIDER_EXTENSION @@ -177,11 +204,11 @@ extension tableMetadata { } var isCopyableMovable: Bool { - !isDirectoryE2EE && !e2eEncrypted + !isDocumentViewableOnly && !isDirectoryE2EE && !e2eEncrypted } var isModifiableWithQuickLook: Bool { - if directory || isDirectoryE2EE { + if directory || isDocumentViewableOnly || isDirectoryE2EE { return false } return isPDF || isImage @@ -212,7 +239,7 @@ extension tableMetadata { } var canSetAsAvailableOffline: Bool { - return session.isEmpty && !isDirectoryE2EE && !e2eEncrypted + return session.isEmpty && !isDocumentViewableOnly //!isDirectoryE2EE && !e2eEncrypted } // Return if is sharable @@ -225,6 +252,68 @@ extension tableMetadata { } return true } + + var canShare: Bool { + return session.isEmpty && !isDocumentViewableOnly && !directory && !NCBrandOptions.shared.disable_openin_file + } + + var canUnsetDirectoryAsE2EE: Bool { + return !isDirectoryE2EE && directory && size == 0 && e2eEncrypted && NCPreferences().isEndToEndEnabled(account: account) + } + + var canOpenExternalEditor: Bool { + if isDocumentViewableOnly { + return false + } + let utility = NCUtility() + let editors = utility.editorsDirectEditing(account: account, contentType: contentType) + let isRichDocument = utility.isTypeFileRichDocument(self) + return classFile == NKCommon.TypeClassFile.document.rawValue && editors.contains(NCGlobal.shared.editorText) && ((editors.contains(NCGlobal.shared.editorOnlyoffice) || isRichDocument)) + } + + var isWaitingTransfer: Bool { + status == NCGlobal.shared.metadataStatusWaitDownload || status == NCGlobal.shared.metadataStatusWaitUpload || status == NCGlobal.shared.metadataStatusUploadError + } + + var isInTransfer: Bool { + status == NCGlobal.shared.metadataStatusDownloading || status == NCGlobal.shared.metadataStatusUploading + } + + var isTransferInForeground: Bool { + (status > 0 && (chunk > 0 || e2eEncrypted)) + } + + var isDownloadUpload: Bool { + status == NCGlobal.shared.metadataStatusDownloading || status == NCGlobal.shared.metadataStatusUploading + } + + var isDownload: Bool { + status == NCGlobal.shared.metadataStatusWaitDownload || status == NCGlobal.shared.metadataStatusDownloading + } + + var isUpload: Bool { + status == NCGlobal.shared.metadataStatusWaitUpload || status == NCGlobal.shared.metadataStatusUploading + } + + var isDirectory: Bool { + directory + } + + @objc var isDirectoryE2EE: Bool { + return NCUtilityFileSystem().isDirectoryE2EE(serverUrl: serverUrl, urlBase: urlBase, userId: userId, account: account) + } + + var isLivePhoto: Bool { + !livePhotoFile.isEmpty + } + + var isNotFlaggedAsLivePhotoByServer: Bool { + !isFlaggedAsLivePhotoByServer + } + + var imageSize: CGSize { + CGSize(width: width, height: height) + } var hasPreviewBorder: Bool { !isImage && !isAudioOrVideo && hasPreview && NCUtilityFileSystem().fileProviderStorageImageExists(ocId, etag: etag, ext: NCGlobal.shared.previewExt1024, userId: userId, urlBase: urlBase) @@ -342,7 +431,7 @@ extension tableMetadata { // if !NCCapabilities.shared.getCapabilities(account: account).capabilityFileSharingApiEnabled || (NCCapabilities.shared.getCapabilities(account: account).capabilityE2EEEnabled && isDirectoryE2EE), !e2eEncrypted { return false } - return true + return !e2eEncrypted } /// Returns a detached (unmanaged) deep copy of the current `tableMetadata` object. @@ -1118,6 +1207,25 @@ extension NCManageDatabase { } ?? [] } + func getTableMetadatasDirectoryFavoriteIdentifierRankAsync(account: String) async -> [String: NSNumber] { + let result = await performRealmReadAsync { realm in + var listIdentifierRank: [String: NSNumber] = [:] + var counter = Int64(10) + + let results = realm.objects(tableMetadata.self) + .filter("account == %@ AND directory == true AND favorite == true", account) + .sorted(byKeyPath: "fileNameView", ascending: true) + + results.forEach { item in + counter += 1 + listIdentifierRank[item.ocId] = NSNumber(value: counter) + } + + return listIdentifierRank + } + return result ?? [:] + } + func getAssetLocalIdentifiersUploadedAsync() async -> [String]? { return await core.performRealmReadAsync { realm in let results = realm.objects(tableMetadata.self).filter("assetLocalIdentifier != ''") @@ -1125,6 +1233,18 @@ extension NCManageDatabase { } } + @objc func clearMetadatasUpload(account: String) { + do { + let realm = try Realm() + try realm.write { + let results = realm.objects(tableMetadata.self).filter("account == %@ AND (status == %d OR status == %d)", account, NCGlobal.shared.metadataStatusWaitUpload, NCGlobal.shared.metadataStatusUploadError) + realm.delete(results) + } + } catch let error { + NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] Could not write to database: \(error)") + } + } + func getMetadataFromFileId(_ fileId: String?) -> tableMetadata? { guard let fileId else { return nil @@ -1302,4 +1422,33 @@ extension NCManageDatabase { .first != nil } ?? false } + + func getAdvancedMetadatas(predicate: NSPredicate, page: Int = 0, limit: Int = 0, sorted: String, ascending: Bool) -> [tableMetadata] { + + var metadatas: [tableMetadata] = [] + + do { + let realm = try Realm() + realm.refresh() + let results = realm.objects(tableMetadata.self).filter(predicate).sorted(byKeyPath: sorted, ascending: ascending) + if !results.isEmpty { + if page == 0 || limit == 0 { + return Array(results.map { tableMetadata.init(value: $0) }) + } else { + let nFrom = (page - 1) * limit + let nTo = nFrom + (limit - 1) + for n in nFrom...nTo { + if n == results.count { + break + } + metadatas.append(tableMetadata.init(value: results[n])) + } + } + } + } catch let error as NSError { + NextcloudKit.shared.nkCommonInstance.writeLog("Could not access database: \(error)") + } + + return metadatas + } } diff --git a/iOSClient/Main/Create/NCCreateFormUploadDocuments.swift b/iOSClient/Main/Create/NCCreateFormUploadDocuments.swift index 61177fa174..ec489d893a 100644 --- a/iOSClient/Main/Create/NCCreateFormUploadDocuments.swift +++ b/iOSClient/Main/Create/NCCreateFormUploadDocuments.swift @@ -53,15 +53,20 @@ import XLForm let sectionInsets: CGFloat = 10 let highLabelName: CGFloat = 20 + var controller: NCMainTabBarController! + var session: NCSession.Session { + NCSession.shared.getSession(controller: controller) + } + // MARK: - View Life Cycle override func viewDidLoad() { super.viewDidLoad() - if serverUrl == utilityFileSystem.getHomeServer(urlBase: appDelegate.urlBase, userId: appDelegate.userId) { + if serverUrl == utilityFileSystem.getHomeServer(session: session) { fileNameFolder = "/" } else { - fileNameFolder = (serverUrl as NSString).lastPathComponent + fileNameFolder = utilityFileSystem.getTextServerUrl(session: session, serverUrl: serverUrl)//(serverUrl as NSString).lastPathComponent } self.tableView.separatorStyle = UITableViewCell.SeparatorStyle.none @@ -229,7 +234,7 @@ import XLForm guard let serverUrl = serverUrl else { return } self.serverUrl = serverUrl - if serverUrl == utilityFileSystem.getHomeServer(urlBase: appDelegate.urlBase, userId: appDelegate.userId) { + if serverUrl == utilityFileSystem.getHomeServer(session: session) { fileNameFolder = "/" } else { fileNameFolder = (serverUrl as NSString).lastPathComponent @@ -288,14 +293,15 @@ import XLForm // Trim whitespaces after checks above fileNameForm = fileNameForm.trimmingCharacters(in: .whitespacesAndNewlines) - let result = NextcloudKit.shared.nkCommonInstance.getInternalType(fileName: fileNameForm, mimeType: "", directory: false) - if utility.isDirectEditing(account: appDelegate.account, contentType: result.mimeType).isEmpty { + let result = NextcloudKit.shared.nkCommonInstance.getInternalType(fileName: fileNameForm, mimeType: "", directory: false, account: session.account + ) + if utility.editorsDirectEditing(account: session.account, contentType: result.mimeType).isEmpty { fileNameForm = (fileNameForm as NSString).deletingPathExtension + "." + fileNameExtension } - if NCManageDatabase.shared.getMetadataConflict(account: appDelegate.account, serverUrl: serverUrl, fileNameView: String(describing: fileNameForm)) != nil { + if NCManageDatabase.shared.getMetadataConflict(account: session.account, serverUrl: serverUrl, fileNameView: String(describing: fileNameForm), nativeFormat: false) != nil { - let metadataForUpload = NCManageDatabase.shared.createMetadata(account: appDelegate.account, user: appDelegate.user, userId: appDelegate.userId, fileName: String(describing: fileNameForm), fileNameView: String(describing: fileNameForm), ocId: "", serverUrl: serverUrl, urlBase: appDelegate.urlBase, url: "", contentType: "") + let metadataForUpload = NCManageDatabase.shared.createMetadata(fileName: String(describing: fileNameForm), fileNameView: String(describing: fileNameForm), ocId: UUID().uuidString, serverUrl: serverUrl, url: "", contentType: "", session: session, sceneIdentifier: self.appDelegate.sceneIdentifier) guard let conflict = UIStoryboard(name: "NCCreateFormUploadConflict", bundle: nil).instantiateInitialViewController() as? NCCreateFormUploadConflict else { return } @@ -309,7 +315,7 @@ import XLForm } else { - let fileNamePath = utilityFileSystem.getFileNamePath(String(describing: fileNameForm), serverUrl: serverUrl, urlBase: appDelegate.urlBase, userId: appDelegate.userId) + let fileNamePath = utilityFileSystem.getFileNamePath(String(describing: fileNameForm), serverUrl: serverUrl, session: session) createDocument(fileNamePath: fileNamePath, fileName: String(describing: fileNameForm)) } } @@ -318,7 +324,7 @@ import XLForm if let metadatas, metadatas.count > 0 { let fileName = metadatas[0].fileName - let fileNamePath = utilityFileSystem.getFileNamePath(fileName, serverUrl: serverUrl, urlBase: appDelegate.urlBase, userId: appDelegate.userId) + let fileNamePath = utilityFileSystem.getFileNamePath(fileName, serverUrl: serverUrl, session: session) createDocument(fileNamePath: fileNamePath, fileName: fileName) } else { DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) { @@ -342,23 +348,23 @@ import XLForm options = NKRequestOptions(customUserAgent: utility.getCustomUserAgentNCText()) } - NextcloudKit.shared.NCTextCreateFile(fileNamePath: fileNamePath, editorId: editorId, creatorId: creatorId, templateId: templateIdentifier, options: options) { account, url, _, error in - guard error == .success, account == self.appDelegate.account, let url = url else { + NextcloudKit.shared.NCTextCreateFile(fileNamePath: fileNamePath, editorId: editorId, creatorId: creatorId, templateId: templateIdentifier, account: session.account, options: options) { account, url, _, error in + guard error == .success, account == self.session.account, let url = url else { self.navigationItem.rightBarButtonItem?.isEnabled = true NCContentPresenter().showError(error: error) return } - var results = NextcloudKit.shared.nkCommonInstance.getInternalType(fileName: fileName, mimeType: "", directory: false) + var results = NextcloudKit.shared.nkCommonInstance.getInternalType(fileName: fileName, mimeType: "", directory: false, account: self.session.account) // FIXME: iOS 12.0,* don't detect UTI text/markdown, text/x-markdown if results.mimeType.isEmpty { results.mimeType = "text/x-markdown" } self.dismiss(animated: true, completion: { - let metadata = NCManageDatabase.shared.createMetadata(account: self.appDelegate.account, user: self.appDelegate.user, userId: self.appDelegate.userId, fileName: fileName, fileNameView: fileName, ocId: UUID, serverUrl: self.serverUrl, urlBase: self.appDelegate.urlBase, url: url, contentType: results.mimeType) + let metadata = NCManageDatabase.shared.createMetadata(fileName: fileName, fileNameView: fileName, ocId: UUID, serverUrl: self.serverUrl, url: url, contentType: results.mimeType, session: self.session, sceneIdentifier: self.appDelegate.sceneIdentifier) if let viewController = self.appDelegate.activeViewController { - NCViewer().view(viewController: viewController, metadata: metadata, metadatas: [metadata], imageIcon: nil) + NCViewer().view(viewController: viewController, metadata: metadata, metadatas: [metadata]) } }) } @@ -366,8 +372,8 @@ import XLForm if self.editorId == NCGlobal.shared.editorCollabora { - NextcloudKit.shared.createRichdocuments(path: fileNamePath, templateId: templateIdentifier) { account, url, _, error in - guard error == .success, account == self.appDelegate.account, let url = url else { + NextcloudKit.shared.createRichdocuments(path: fileNamePath, templateId: templateIdentifier, account: session.account) { account, url, _, error in + guard error == .success, account == self.session.account, let url = url else { self.navigationItem.rightBarButtonItem?.isEnabled = true NCContentPresenter().showError(error: error) return @@ -375,9 +381,10 @@ import XLForm self.dismiss(animated: true, completion: { let createFileName = (fileName as NSString).deletingPathExtension + "." + self.fileNameExtension - let metadata = NCManageDatabase.shared.createMetadata(account: self.appDelegate.account, user: self.appDelegate.user, userId: self.appDelegate.userId, fileName: createFileName, fileNameView: createFileName, ocId: UUID, serverUrl: self.serverUrl, urlBase: self.appDelegate.urlBase, url: url, contentType: "") + let metadata = NCManageDatabase.shared.createMetadata(fileName: createFileName, fileNameView: createFileName, ocId: UUID, serverUrl: self.serverUrl, url: url, contentType: "", session: self.session, sceneIdentifier: self.appDelegate.sceneIdentifier) + AnalyticsHelper.shared.trackCreateFile(metadata: metadata) if let viewController = self.appDelegate.activeViewController { - NCViewer().view(viewController: viewController, metadata: metadata, metadatas: [metadata], imageIcon: nil) + NCViewer().view(viewController: viewController, metadata: metadata, metadatas: [metadata]) } }) } @@ -405,11 +412,11 @@ import XLForm options = NKRequestOptions(customUserAgent: utility.getCustomUserAgentNCText()) } - NextcloudKit.shared.NCTextGetListOfTemplates(options: options) { account, templates, _, error in + NextcloudKit.shared.NCTextGetListOfTemplates(account: session.account, options: options) { account, templates, _, error in self.indicator.stopAnimating() - if error == .success && account == self.appDelegate.account { + if error == .success && account == self.session.account, let templates = templates { for template in templates { @@ -462,11 +469,11 @@ import XLForm if self.editorId == NCGlobal.shared.editorCollabora { - NextcloudKit.shared.getTemplatesRichdocuments(typeTemplate: typeTemplate) { account, templates, _, error in + NextcloudKit.shared.getTemplatesRichdocuments(typeTemplate: typeTemplate, account: session.account) { account, templates, _, error in self.indicator.stopAnimating() - if error == .success && account == self.appDelegate.account { + if error == .success && account == self.session.account { for template in templates! { @@ -521,7 +528,7 @@ import XLForm let fileNameLocalPath = utilityFileSystem.directoryUserData + "/" + name + ".png" - NextcloudKit.shared.download(serverUrlFileName: preview, fileNameLocalPath: fileNameLocalPath, requestHandler: { _ in + NextcloudKit.shared.download(serverUrlFileName: preview, fileNameLocalPath: fileNameLocalPath, account: session.account, requestHandler: { _ in }, taskHandler: { _ in @@ -529,7 +536,7 @@ import XLForm }) { account, _, _, _, _, _, error in - if error == .success && account == self.appDelegate.account { + if error == .success && account == self.session.account { self.collectionView.reloadItems(at: [indexPath]) } else if error != .success { print("\(error.errorCode)") From 565958d8a51e1467efd0577766927beee07af53d Mon Sep 17 00:00:00 2001 From: harshada-15-tsys Date: Wed, 1 Oct 2025 14:13:06 +0530 Subject: [PATCH 3/4] NMC 1933 - Collabora customisation changes --- iOSClient/BrowserWeb/NCBrowserWeb.swift | 25 +- .../Data/NCManageDatabase+Metadata.swift | 1846 +++++++++-------- .../Create/NCCreateFormUploadDocuments.swift | 48 +- .../FolderPathCustomCell.swift | 1 + .../NMC Custom Views/FolderPathCustomCell.xib | 11 +- .../NCCreateDocumentCustomTextField.swift | 1 + 6 files changed, 1059 insertions(+), 873 deletions(-) diff --git a/iOSClient/BrowserWeb/NCBrowserWeb.swift b/iOSClient/BrowserWeb/NCBrowserWeb.swift index 4bf4a4f613..10ee6d71ca 100644 --- a/iOSClient/BrowserWeb/NCBrowserWeb.swift +++ b/iOSClient/BrowserWeb/NCBrowserWeb.swift @@ -1,6 +1,25 @@ -// SPDX-FileCopyrightText: Nextcloud GmbH -// SPDX-FileCopyrightText: 2019 Marino Faggiana -// SPDX-License-Identifier: GPL-3.0-or-later +// +// NCBrowserWeb.swift +// Nextcloud +// +// Created by Marino Faggiana on 22/08/2019. +// Copyright (c) 2019 Marino Faggiana. All rights reserved. +// +// Author Marino Faggiana +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// import UIKit @preconcurrency import WebKit diff --git a/iOSClient/Data/NCManageDatabase+Metadata.swift b/iOSClient/Data/NCManageDatabase+Metadata.swift index 8d93019f18..5c82a8767a 100644 --- a/iOSClient/Data/NCManageDatabase+Metadata.swift +++ b/iOSClient/Data/NCManageDatabase+Metadata.swift @@ -1,12 +1,30 @@ -// SPDX-FileCopyrightText: Nextcloud GmbH -// SPDX-FileCopyrightText: 2021 Marino Faggiana -// SPDX-License-Identifier: GPL-3.0-or-later +// +// NCManageDatabase+Metadata.swift +// Nextcloud +// +// Created by Henrik Storch on 30.11.21. +// Copyright © 2021 Marino Faggiana. All rights reserved. +// +// Author Marino Faggiana +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// import Foundation import UIKit import RealmSwift import NextcloudKit -import Photos class tableMetadata: Object { override func isEqual(_ object: Any?) -> Bool { @@ -33,7 +51,8 @@ class tableMetadata: Object { self.altitude == object.altitude, self.status == object.status, Array(self.tags).elementsEqual(Array(object.tags)), - Array(self.shareType).elementsEqual(Array(object.shareType)) { + Array(self.shareType).elementsEqual(Array(object.shareType)), + Array(self.sharePermissionsCloudMesh).elementsEqual(Array(object.sharePermissionsCloudMesh)) { return true } else { return false @@ -56,6 +75,7 @@ class tableMetadata: Object { @objc dynamic var e2eEncrypted: Bool = false @objc dynamic var edited: Bool = false @objc dynamic var etag = "" + @objc dynamic var etagResource = "" let exifPhotos = List() @objc dynamic var favorite: Bool = false @objc dynamic var fileId = "" @@ -82,7 +102,6 @@ class tableMetadata: Object { @objc public var lockOwnerDisplayName = "" @objc public var lockTime: Date? @objc public var lockTimeOut: Date? - @objc dynamic var mediaSearch: Bool = false @objc dynamic var path = "" @objc dynamic var permissions = "" @objc dynamic var placePhotos: String? @@ -92,15 +111,15 @@ class tableMetadata: Object { @objc dynamic var richWorkspace: String? @objc dynamic var sceneIdentifier: String? @objc dynamic var serverUrl = "" - @objc dynamic var serverUrlFileName = "" - @objc dynamic var destination = "" + @objc dynamic var serveUrlFileName = "" + @objc dynamic var serverUrlTo = "" @objc dynamic var session = "" @objc dynamic var sessionDate: Date? @objc dynamic var sessionError = "" @objc dynamic var sessionSelector = "" @objc dynamic var sessionTaskIdentifier: Int = 0 - /// The integer for sharing permissions. @objc dynamic var sharePermissionsCollaborationServices: Int = 0 + let sharePermissionsCloudMesh = List() let shareType = List() @objc dynamic var size: Int64 = 0 @objc dynamic var status: Int = 0 @@ -123,7 +142,6 @@ class tableMetadata: Object { @objc dynamic var errorCode: Int = 0 @objc dynamic var nativeFormat: Bool = false @objc dynamic var autoUploadServerUrlBase: String? - @objc dynamic var typeIdentifier: String = "" override static func primaryKey() -> String { return "ocId" @@ -140,9 +158,6 @@ extension tableMetadata { } var isRenameable: Bool { - if !NCMetadataPermissions.canRename(self) { - return false - } if lock { return false } @@ -161,48 +176,44 @@ extension tableMetadata { } return false } - + var isSavebleInCameraRoll: Bool { - return (classFile == NKTypeClassFile.image.rawValue && contentType != "image/svg+xml") || classFile == NKTypeClassFile.video.rawValue + return (classFile == NKCommon.TypeClassFile.image.rawValue && contentType != "image/svg+xml") || classFile == NKCommon.TypeClassFile.video.rawValue } + var isDocumentViewableOnly: Bool { sharePermissionsCollaborationServices == NCPermissions().permissionReadShare && classFile == NKCommon.TypeClassFile.document.rawValue } var isAudioOrVideo: Bool { - return classFile == NKTypeClassFile.audio.rawValue || classFile == NKTypeClassFile.video.rawValue + return classFile == NKCommon.TypeClassFile.audio.rawValue || classFile == NKCommon.TypeClassFile.video.rawValue } var isImageOrVideo: Bool { - return classFile == NKTypeClassFile.image.rawValue || classFile == NKTypeClassFile.video.rawValue + return classFile == NKCommon.TypeClassFile.image.rawValue || classFile == NKCommon.TypeClassFile.video.rawValue } var isVideo: Bool { - return classFile == NKTypeClassFile.video.rawValue + return classFile == NKCommon.TypeClassFile.video.rawValue } var isAudio: Bool { - return classFile == NKTypeClassFile.audio.rawValue + return classFile == NKCommon.TypeClassFile.audio.rawValue } var isImage: Bool { - return classFile == NKTypeClassFile.image.rawValue + return classFile == NKCommon.TypeClassFile.image.rawValue } var isSavebleAsImage: Bool { - classFile == NKTypeClassFile.image.rawValue && contentType != "image/svg+xml" + classFile == NKCommon.TypeClassFile.image.rawValue && contentType != "image/svg+xml" } var isCopyableInPasteboard: Bool { !isDocumentViewableOnly && !directory } -#if !EXTENSION_FILE_PROVIDER_EXTENSION - @objc var isDirectoryE2EE: Bool { - return NCUtilityFileSystem().isDirectoryE2EE(serverUrl: serverUrl, urlBase: urlBase, userId: userId, account: account) - } - var isCopyableMovable: Bool { !isDocumentViewableOnly && !isDirectoryE2EE && !e2eEncrypted } @@ -214,51 +225,28 @@ extension tableMetadata { return isPDF || isImage } - var isRenameable: Bool { - if !NCMetadataPermissions.canRename(self) { - return false - } - if lock { - return false - } - if !isDirectoryE2EE && e2eEncrypted { - return false - } - return true - } - - var canUnsetDirectoryAsE2EE: Bool { - return !isDirectoryE2EE && directory && size == 0 && e2eEncrypted && NCPreferences().isEndToEndEnabled(account: account) - } - var isDeletable: Bool { - if (!isDirectoryE2EE && e2eEncrypted) || !NCMetadataPermissions.canDelete(self) { + if !isDirectoryE2EE && e2eEncrypted { return false } return true } var canSetAsAvailableOffline: Bool { - return session.isEmpty && !isDocumentViewableOnly //!isDirectoryE2EE && !e2eEncrypted +// return session.isEmpty && !isDocumentViewableOnly //!isDirectoryE2EE && !e2eEncrypted + return session.isEmpty && !isDirectoryE2EE && !e2eEncrypted } - // Return if is sharable - func isSharable() -> Bool { - guard let capabilities = NCNetworking.shared.capabilities[account] else { - return false - } - if !capabilities.fileSharingApiEnabled || (capabilities.e2EEEnabled && isDirectoryE2EE) { - return false - } - return true - } - var canShare: Bool { return session.isEmpty && !isDocumentViewableOnly && !directory && !NCBrandOptions.shared.disable_openin_file } + var canSetDirectoryAsE2EE: Bool { + return directory && size == 0 && !e2eEncrypted && NCKeychain().isEndToEndEnabled(account: account) + } + var canUnsetDirectoryAsE2EE: Bool { - return !isDirectoryE2EE && directory && size == 0 && e2eEncrypted && NCPreferences().isEndToEndEnabled(account: account) + return !isDirectoryE2EE && directory && size == 0 && e2eEncrypted && NCKeychain().isEndToEndEnabled(account: account) } var canOpenExternalEditor: Bool { @@ -300,7 +288,12 @@ extension tableMetadata { } @objc var isDirectoryE2EE: Bool { - return NCUtilityFileSystem().isDirectoryE2EE(serverUrl: serverUrl, urlBase: urlBase, userId: userId, account: account) + let session = NCSession.Session(account: account, urlBase: urlBase, user: user, userId: userId) + return NCUtilityFileSystem().isDirectoryE2EE(session: session, serverUrl: serverUrl) + } + + var isDirectoryE2EETop: Bool { + NCUtilityFileSystem().isDirectoryE2EETop(account: account, serverUrl: serverUrl) } var isLivePhoto: Bool { @@ -316,27 +309,21 @@ extension tableMetadata { } var hasPreviewBorder: Bool { - !isImage && !isAudioOrVideo && hasPreview && NCUtilityFileSystem().fileProviderStorageImageExists(ocId, etag: etag, ext: NCGlobal.shared.previewExt1024, userId: userId, urlBase: urlBase) + !isImage && !isAudioOrVideo && hasPreview && NCUtilityFileSystem().fileProviderStorageImageExists(ocId, etag: etag, ext: NCGlobal.shared.previewExt1024) } var isAvailableEditorView: Bool { guard !isPDF, - classFile == NKTypeClassFile.document.rawValue, - NextcloudKit.shared.isNetworkReachable() else { - return false - } + classFile == NKCommon.TypeClassFile.document.rawValue, + NextcloudKit.shared.isNetworkReachable() else { return false } let utility = NCUtility() - let directEditingEditors = utility.editorsDirectEditing(account: account, contentType: contentType).map { $0.lowercased() } + let directEditingEditors = utility.editorsDirectEditing(account: account, contentType: contentType) let richDocumentEditor = utility.isTypeFileRichDocument(self) - let capabilities = NCNetworking.shared.capabilities[account] - if let capabilities, - capabilities.richDocumentsEnabled, - richDocumentEditor, - directEditingEditors.isEmpty { + if NCCapabilities.shared.getCapabilities(account: account).capabilityRichDocumentsEnabled && richDocumentEditor && directEditingEditors.isEmpty { // RichDocument: Collabora return true - } else if directEditingEditors.contains("nextcloud text") || directEditingEditors.contains("onlyoffice") { + } else if directEditingEditors.contains(NCGlobal.shared.editorText) || directEditingEditors.contains(NCGlobal.shared.editorOnlyoffice) { // DirectEditing: Nextcloud Text - OnlyOffice return true } @@ -344,9 +331,8 @@ extension tableMetadata { } var isAvailableRichDocumentEditorView: Bool { - guard let capabilities = NCNetworking.shared.capabilities[account], - classFile == NKTypeClassFile.document.rawValue, - capabilities.richDocumentsEnabled, + guard classFile == NKCommon.TypeClassFile.document.rawValue, + NCCapabilities.shared.getCapabilities(account: account).capabilityRichDocumentsEnabled, NextcloudKit.shared.isNetworkReachable() else { return false } if NCUtility().isTypeFileRichDocument(self) { @@ -356,12 +342,10 @@ extension tableMetadata { } var isAvailableDirectEditingEditorView: Bool { - guard (classFile == NKTypeClassFile.document.rawValue) && NextcloudKit.shared.isNetworkReachable() else { - return false - } - let editors = NCUtility().editorsDirectEditing(account: account, contentType: contentType).map { $0.lowercased() } + guard (classFile == NKCommon.TypeClassFile.document.rawValue) && NextcloudKit.shared.isNetworkReachable() else { return false } + let editors = NCUtility().editorsDirectEditing(account: account, contentType: contentType) - if editors.contains("nextcloud text") || editors.contains("onlyoffice") { + if editors.contains(NCGlobal.shared.editorText) || editors.contains(NCGlobal.shared.editorOnlyoffice) { return true } return false @@ -371,52 +355,6 @@ extension tableMetadata { return (contentType == "application/pdf" || contentType == "com.adobe.pdf") } - var isCreatable: Bool { - if isDirectory { - return NCMetadataPermissions.canCreateFolder(self) - } else { - return NCMetadataPermissions.canCreateFile(self) - } - } - -#endif - - var canShare: Bool { - return session.isEmpty && !directory && !NCBrandOptions.shared.disable_openin_file - } - - var isDownload: Bool { - status == NCGlobal.shared.metadataStatusWaitDownload || status == NCGlobal.shared.metadataStatusDownloading - } - - var isUpload: Bool { - status == NCGlobal.shared.metadataStatusWaitUpload || status == NCGlobal.shared.metadataStatusUploading - } - - var isDirectory: Bool { - directory - } - - var isLivePhoto: Bool { - !livePhotoFile.isEmpty - } - - var isLivePhotoVideo: Bool { - !livePhotoFile.isEmpty && classFile == NKTypeClassFile.video.rawValue - } - - var isLivePhotoImage: Bool { - !livePhotoFile.isEmpty && classFile == NKTypeClassFile.image.rawValue - } - - var isNotFlaggedAsLivePhotoByServer: Bool { - !isFlaggedAsLivePhotoByServer - } - - var imageSize: CGSize { - CGSize(width: width, height: height) - } - /// Returns false if the user is lokced out of the file. I.e. The file is locked but by somone else func canUnlock(as user: String) -> Bool { return !lock || (lockOwner == user && lockOwnerType == 0) @@ -424,694 +362,828 @@ extension tableMetadata { // Return if is sharable func isSharable() -> Bool { - guard let capabilities = NCNetworking.shared.capabilities[account] else { - return false - } - if !capabilities.fileSharingApiEnabled || (capabilities.e2EEEnabled && isDirectoryE2EE) { -// if !NCCapabilities.shared.getCapabilities(account: account).capabilityFileSharingApiEnabled || (NCCapabilities.shared.getCapabilities(account: account).capabilityE2EEEnabled && isDirectoryE2EE), !e2eEncrypted { + if !NCCapabilities.shared.getCapabilities(account: account).capabilityFileSharingApiEnabled || (NCCapabilities.shared.getCapabilities(account: account).capabilityE2EEEnabled && isDirectoryE2EE), !e2eEncrypted { return false } return !e2eEncrypted } - - /// Returns a detached (unmanaged) deep copy of the current `tableMetadata` object. - /// - /// - Note: The Realm `List` properties containing primitive types (e.g., `tags`, `shareType`) are copied automatically - /// by the Realm initializer `init(value:)`. For `List` containing Realm objects (e.g., `exifPhotos`), this method - /// creates new instances to ensure the copy is fully detached and safe to use outside of a Realm context. - /// - /// - Returns: A new `tableMetadata` instance fully detached from Realm. - func detachedCopy() -> tableMetadata { - // Use Realm's built-in copy constructor for primitive properties and List of primitives - let detached = tableMetadata(value: self) - - // Deep copy of List of Realm objects (exifPhotos) - detached.exifPhotos.removeAll() - detached.exifPhotos.append(objectsIn: self.exifPhotos.map { NCKeyValue(value: $0) }) - - return detached - } } extension NCManageDatabase { -#if !EXTENSION - - func isMetadataShareOrMounted(metadata: tableMetadata, metadataFolder: tableMetadata?) -> Bool { - var isShare = false - var isMounted = false - - if metadataFolder != nil { - isShare = metadata.permissions.contains(NCMetadataPermissions.permissionShared) && !metadataFolder!.permissions.contains(NCMetadataPermissions.permissionShared) - isMounted = metadata.permissions.contains(NCMetadataPermissions.permissionMounted) && !metadataFolder!.permissions.contains(NCMetadataPermissions.permissionMounted) - } else if let directory = getTableDirectory(predicate: NSPredicate(format: "account == %@ AND serverUrl == %@", metadata.account, metadata.serverUrl)) { - isShare = metadata.permissions.contains(NCMetadataPermissions.permissionShared) && !directory.permissions.contains(NCMetadataPermissions.permissionShared) - isMounted = metadata.permissions.contains(NCMetadataPermissions.permissionMounted) && !directory.permissions.contains(NCMetadataPermissions.permissionMounted) + func convertFileToMetadata(_ file: NKFile, isDirectoryE2EE: Bool) -> tableMetadata { + let metadata = tableMetadata() + + metadata.account = file.account + metadata.checksums = file.checksums + metadata.commentsUnread = file.commentsUnread + metadata.contentType = file.contentType + if let date = file.creationDate { + metadata.creationDate = date as NSDate + } else { + metadata.creationDate = file.date as NSDate } - - if isShare || isMounted { - return true + metadata.dataFingerprint = file.dataFingerprint + metadata.date = file.date as NSDate + if let datePhotosOriginal = file.datePhotosOriginal { + metadata.datePhotosOriginal = datePhotosOriginal as NSDate } else { - return false + metadata.datePhotosOriginal = metadata.date + } + metadata.directory = file.directory + metadata.downloadURL = file.downloadURL + metadata.e2eEncrypted = file.e2eEncrypted + metadata.etag = file.etag + for dict in file.exifPhotos { + for (key, value) in dict { + let keyValue = NCKeyValue() + keyValue.key = key + keyValue.value = value + metadata.exifPhotos.append(keyValue) + } + } + metadata.favorite = file.favorite + metadata.fileId = file.fileId + metadata.fileName = file.fileName + metadata.fileNameView = file.fileName + metadata.hasPreview = file.hasPreview + metadata.hidden = file.hidden + switch (file.fileName as NSString).pathExtension { + case "odg": + metadata.iconName = "diagram" + case "csv", "xlsm" : + metadata.iconName = "file_xls" + default: + metadata.iconName = file.iconName + } + metadata.mountType = file.mountType + metadata.name = file.name + metadata.note = file.note + metadata.ocId = file.ocId + metadata.ocIdTransfer = file.ocId + metadata.ownerId = file.ownerId + metadata.ownerDisplayName = file.ownerDisplayName + metadata.lock = file.lock + metadata.lockOwner = file.lockOwner + metadata.lockOwnerEditor = file.lockOwnerEditor + metadata.lockOwnerType = file.lockOwnerType + metadata.lockOwnerDisplayName = file.lockOwnerDisplayName + metadata.lockTime = file.lockTime + metadata.lockTimeOut = file.lockTimeOut + metadata.path = file.path + metadata.permissions = file.permissions + metadata.placePhotos = file.placePhotos + metadata.quotaUsedBytes = file.quotaUsedBytes + metadata.quotaAvailableBytes = file.quotaAvailableBytes + metadata.richWorkspace = file.richWorkspace + metadata.resourceType = file.resourceType + metadata.serverUrl = file.serverUrl + metadata.serveUrlFileName = file.serverUrl + "/" + file.fileName + metadata.sharePermissionsCollaborationServices = file.sharePermissionsCollaborationServices + for element in file.sharePermissionsCloudMesh { + metadata.sharePermissionsCloudMesh.append(element) + } + for element in file.shareType { + metadata.shareType.append(element) + } + for element in file.tags { + metadata.tags.append(element) + } + metadata.size = file.size + metadata.classFile = file.classFile + // iOS 12.0,* don't detect UTI text/markdown, text/x-markdown + if (metadata.contentType == "text/markdown" || metadata.contentType == "text/x-markdown") && metadata.classFile == NKCommon.TypeClassFile.unknow.rawValue { + metadata.classFile = NKCommon.TypeClassFile.document.rawValue + } + if let date = file.uploadDate { + metadata.uploadDate = date as NSDate + } else { + metadata.uploadDate = file.date as NSDate + } + metadata.urlBase = file.urlBase + metadata.user = file.user + metadata.userId = file.userId + metadata.latitude = file.latitude + metadata.longitude = file.longitude + metadata.altitude = file.altitude + metadata.height = Int(file.height) + metadata.width = Int(file.width) + metadata.livePhotoFile = file.livePhotoFile + metadata.isFlaggedAsLivePhotoByServer = file.isFlaggedAsLivePhotoByServer + + // E2EE find the fileName for fileNameView + if isDirectoryE2EE || file.e2eEncrypted { + if let tableE2eEncryption = getE2eEncryption(predicate: NSPredicate(format: "account == %@ AND serverUrl == %@ AND fileNameIdentifier == %@", file.account, file.serverUrl, file.fileName)) { + metadata.fileNameView = tableE2eEncryption.fileName + let results = NextcloudKit.shared.nkCommonInstance.getInternalType(fileName: metadata.fileNameView, mimeType: file.contentType, directory: file.directory, account: file.account) + metadata.contentType = results.mimeType + metadata.iconName = results.iconName + metadata.classFile = results.classFile + } } + return metadata } - func getMetadataProcess() async -> [tableMetadata] { - return await core.performRealmReadAsync { realm in - let predicate = NSPredicate(format: "status != %d", NCGlobal.shared.metadataStatusNormal) - let sortDescriptors = [ - RealmSwift.SortDescriptor(keyPath: "status", ascending: false), - RealmSwift.SortDescriptor(keyPath: "sessionDate", ascending: true) - ] - let limit = NCBrandOptions.shared.numMaximumProcess * 4 - - let results = realm.objects(tableMetadata.self) - .filter(predicate) - .sorted(by: sortDescriptors) - - let sliced = results.prefix(limit) - return sliced.map { $0.detachedCopy() } - } ?? [] - } -#endif + func convertFilesToMetadatas(_ files: [NKFile], useFirstAsMetadataFolder: Bool, completion: @escaping (_ metadataFolder: tableMetadata, _ metadatas: [tableMetadata]) -> Void) { + var counter: Int = 0 + var isDirectoryE2EE: Bool = false + let listServerUrl = ThreadSafeDictionary() + var metadataFolder = tableMetadata() + var metadatas: [tableMetadata] = [] - // MARK: - Realm Write + for file in files { + if let key = listServerUrl[file.serverUrl] { + isDirectoryE2EE = key + } else { + isDirectoryE2EE = NCUtilityFileSystem().isDirectoryE2EE(file: file) + listServerUrl[file.serverUrl] = isDirectoryE2EE + } - func addAndReturnMetadata(_ metadata: tableMetadata) -> tableMetadata? { - let detached = metadata.detachedCopy() + let metadata = convertFileToMetadata(file, isDirectoryE2EE: isDirectoryE2EE) - core.performRealmWrite { realm in - realm.add(detached, update: .all) - } + if counter == 0 && useFirstAsMetadataFolder { + metadataFolder = tableMetadata(value: metadata) + } else { + metadatas.append(metadata) + } - return core.performRealmRead { realm in - realm.objects(tableMetadata.self) - .filter("ocId == %@", metadata.ocId) - .first - .map { $0.detachedCopy() } + counter += 1 } + completion(metadataFolder, metadatas) } + + func convertFilesToMetadatas(_ files: [NKFile], useMetadataFolder: Bool, completion: @escaping (_ metadataFolder: tableMetadata, _ metadatasFolder: [tableMetadata], _ metadatas: [tableMetadata]) -> Void) { - func addAndReturnMetadataAsync(_ metadata: tableMetadata) async -> tableMetadata? { - let detached = metadata.detachedCopy() + var counter: Int = 0 + var isDirectoryE2EE: Bool = false + let listServerUrl = ThreadSafeDictionary() - await core.performRealmWriteAsync { realm in - realm.add(detached, update: .all) - } + var metadataFolder = tableMetadata() + var metadataFolders: [tableMetadata] = [] + var metadatas: [tableMetadata] = [] - return await core.performRealmReadAsync { realm in - realm.objects(tableMetadata.self) - .filter("ocId == %@", metadata.ocId) - .first? - .detachedCopy() - } - } + for file in files { - func addMetadata(_ metadata: tableMetadata, sync: Bool = true) { - let detached = metadata.detachedCopy() + if let key = listServerUrl[file.serverUrl] { + isDirectoryE2EE = key + } else { + isDirectoryE2EE = NCUtilityFileSystem().isDirectoryE2EE(file: file) + listServerUrl[file.serverUrl] = isDirectoryE2EE + } - core.performRealmWrite(sync: sync) { realm in - realm.add(detached, update: .all) - } - } + let metadata = convertFileToMetadata(file, isDirectoryE2EE: isDirectoryE2EE) + + if counter == 0 && useMetadataFolder { + metadataFolder = tableMetadata.init(value: metadata) + } else { + metadatas.append(metadata) + if metadata.directory { + metadataFolders.append(metadata) + } + } - func addMetadataAsync(_ metadata: tableMetadata) async { - let detached = metadata.detachedCopy() + counter += 1 + } + + completion(metadataFolder, metadataFolders, metadatas) + } + +// func convertFilesToMetadatas(_ files: [NKFile], useMetadataFolder: Bool, completion: @escaping (_ metadataFolder: tableMetadata, _ metadatasFolder: [tableMetadata], _ metadatas: [tableMetadata]) -> Void) { +// +// var counter: Int = 0 +// var isDirectoryE2EE: Bool = false +// let listServerUrl = ThreadSafeDictionary() +// +// var metadataFolder = tableMetadata() +// var metadataFolders: [tableMetadata] = [] +// var metadatas: [tableMetadata] = [] +// +// for file in files { +// +// if let key = listServerUrl[file.serverUrl] { +// isDirectoryE2EE = key +// } else { +// isDirectoryE2EE = NCUtilityFileSystem().isDirectoryE2EE(file: file) +// listServerUrl[file.serverUrl] = isDirectoryE2EE +// } +// +// let metadata = convertFileToMetadata(file, isDirectoryE2EE: isDirectoryE2EE) +// +// if counter == 0 && useMetadataFolder { +// metadataFolder = tableMetadata.init(value: metadata) +// } else { +// metadatas.append(metadata) +// if metadata.directory { +// metadataFolders.append(metadata) +// } +// } +// +// counter += 1 +// } +// +// completion(metadataFolder, metadataFolders, metadatas) +// } + + func getMetadataDirectoryFrom(files: [NKFile]) -> tableMetadata? { + guard let file = files.first else { return nil } + let isDirectoryE2EE = NCUtilityFileSystem().isDirectoryE2EE(file: file) + let metadata = convertFileToMetadata(file, isDirectoryE2EE: isDirectoryE2EE) - await core.performRealmWriteAsync { realm in - realm.add(detached, update: .all) - } + return metadata } - func addMetadatas(_ metadatas: [tableMetadata], sync: Bool = true) { - let detached = metadatas.map { $0.detachedCopy() } - - core.performRealmWrite(sync: sync) { realm in - realm.add(detached, update: .all) - } + func convertFilesToMetadatas(_ files: [NKFile], useFirstAsMetadataFolder: Bool) async -> (metadataFolder: tableMetadata, metadatas: [tableMetadata]) { + await withUnsafeContinuation({ continuation in + convertFilesToMetadatas(files, useFirstAsMetadataFolder: useFirstAsMetadataFolder) { metadataFolder, metadatas in + continuation.resume(returning: (metadataFolder, metadatas)) + } + }) } - func addMetadatasAsync(_ metadatas: [tableMetadata]) async { - let detached = metadatas.map { $0.detachedCopy() } + func createMetadata(fileName: String, fileNameView: String, ocId: String, serverUrl: String, url: String, contentType: String, isUrl: Bool = false, name: String = NCGlobal.shared.appName, subline: String? = nil, iconName: String? = nil, iconUrl: String? = nil, directory: Bool = false, session: NCSession.Session, sceneIdentifier: String?) -> tableMetadata { + let metadata = tableMetadata() - await core.performRealmWriteAsync { realm in - realm.add(detached, update: .all) + if isUrl { + metadata.contentType = "text/uri-list" + if let iconName = iconName { + metadata.iconName = iconName + } else { + metadata.iconName = NKCommon.TypeClassFile.url.rawValue + } + metadata.classFile = NKCommon.TypeClassFile.url.rawValue + } else { + let (mimeType, classFile, iconName, _, _, _) = NextcloudKit.shared.nkCommonInstance.getInternalType(fileName: fileName, mimeType: contentType, directory: directory, account: session.account) + metadata.contentType = mimeType + metadata.iconName = iconName + metadata.classFile = classFile + // iOS 12.0,* don't detect UTI text/markdown, text/x-markdown + if classFile == NKCommon.TypeClassFile.unknow.rawValue && (mimeType == "text/x-markdown" || mimeType == "text/markdown") { + metadata.iconName = NKCommon.TypeIconFile.txt.rawValue + metadata.classFile = NKCommon.TypeClassFile.document.rawValue + } } + if let iconUrl = iconUrl { + metadata.iconUrl = iconUrl + } + + let fileName = fileName.trimmingCharacters(in: .whitespacesAndNewlines) + + metadata.account = session.account + metadata.creationDate = Date() as NSDate + metadata.date = Date() as NSDate + metadata.directory = directory + metadata.hasPreview = true + metadata.etag = ocId + metadata.fileName = fileName + metadata.fileNameView = fileName + metadata.name = name + metadata.ocId = ocId + metadata.ocIdTransfer = ocId + metadata.permissions = "RGDNVW" + metadata.serverUrl = serverUrl + metadata.serveUrlFileName = serverUrl + "/" + fileName + metadata.subline = subline + metadata.uploadDate = Date() as NSDate + metadata.url = url + metadata.urlBase = session.urlBase + metadata.user = session.user + metadata.userId = session.userId + metadata.sceneIdentifier = sceneIdentifier + metadata.nativeFormat = !NCKeychain().formatCompatibility + + if !metadata.urlBase.isEmpty, metadata.serverUrl.hasPrefix(metadata.urlBase) { + metadata.path = String(metadata.serverUrl.dropFirst(metadata.urlBase.count)) + "/" + } + return metadata } - func addMetadataIfNotExistsAsync(_ metadata: tableMetadata) async { - let detached = metadata.detachedCopy() + func isMetadataShareOrMounted(metadata: tableMetadata, metadataFolder: tableMetadata?) -> Bool { + let permissions = NCPermissions() + var isShare = false + var isMounted = false - await core.performRealmWriteAsync { realm in - if realm.object(ofType: tableMetadata.self, forPrimaryKey: metadata.ocId) == nil { - realm.add(detached) - } + if metadataFolder != nil { + isShare = metadata.permissions.contains(permissions.permissionShared) && !metadataFolder!.permissions.contains(permissions.permissionShared) + isMounted = metadata.permissions.contains(permissions.permissionMounted) && !metadataFolder!.permissions.contains(permissions.permissionMounted) + } else if let directory = getTableDirectory(predicate: NSPredicate(format: "account == %@ AND serverUrl == %@", metadata.account, metadata.serverUrl)) { + isShare = metadata.permissions.contains(permissions.permissionShared) && !directory.permissions.contains(permissions.permissionShared) + isMounted = metadata.permissions.contains(permissions.permissionMounted) && !directory.permissions.contains(permissions.permissionMounted) } - } - func deleteMetadataAsync(predicate: NSPredicate) async { - await core.performRealmWriteAsync { realm in - let result = realm.objects(tableMetadata.self) - .filter(predicate) - realm.delete(result) + if isShare || isMounted { + return true + } else { + return false } } - func deleteMetadataAsync(id: String?) async { - guard let id else { return } + // MARK: - Set - await core.performRealmWriteAsync { realm in - let result = realm.objects(tableMetadata.self) - .filter("ocId == %@ OR fileId == %@", id, id) - realm.delete(result) + @discardableResult + func addMetadata(_ metadata: tableMetadata) -> tableMetadata { + do { + let realm = try Realm() + try realm.write { + return tableMetadata(value: realm.create(tableMetadata.self, value: metadata, update: .all)) + } + } catch let error { + NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] Could not write to database: \(error)") } + + return tableMetadata(value: metadata) } - func deleteMetadataAsync(ocId: String) async { - await core.performRealmWriteAsync { realm in - if let object = realm.object(ofType: tableMetadata.self, forPrimaryKey: ocId) { - realm.delete(object) + func addMetadatas(_ metadatas: [tableMetadata]) { + do { + let realm = try Realm() + try realm.write { + for metadata in metadatas { + realm.create(tableMetadata.self, value: metadata, update: .all) + } } + } catch let error { + NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] Could not write to database: \(error)") } } - func replaceMetadataAsync(ocId: String, metadata: tableMetadata) async { - let detached = metadata.detachedCopy() - - await core.performRealmWriteAsync { realm in - if let object = realm.object(ofType: tableMetadata.self, forPrimaryKey: ocId) { - realm.delete(object) + func deleteMetadata(predicate: NSPredicate) { + do { + let realm = try Realm() + try realm.write { + let results = realm.objects(tableMetadata.self).filter(predicate) + realm.delete(results) } - realm.add(detached, update: .modified) + } catch let error { + NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] Could not write to database: \(error)") } } - func replaceMetadatasAsync(ocId: [String], metadatas: [tableMetadata]) async { - guard !ocId.isEmpty else { - return - } - var detacheds: [tableMetadata] = [] - for metadata in metadatas { - metadata.ocIdTransfer = metadata.ocId - detacheds.append(metadata.detachedCopy()) - } + func deleteMetadataOcId(_ ocId: String?) { + guard let ocId else { return } - await core.performRealmWriteAsync { realm in - let results = realm.objects(tableMetadata.self) - .filter("ocId IN %@", ocId) - realm.delete(results) - realm.add(detacheds, update: .all) + do { + let realm = try Realm() + try realm.write { + let results = realm.objects(tableMetadata.self).filter("ocId == %@", ocId) + realm.delete(results) + } + } catch let error as NSError { + NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] Could not access database: \(error)") } } - // Asynchronously deletes an array of `tableMetadata` entries from the Realm database. - /// - Parameter metadatas: The `tableMetadata` objects to be deleted. - func deleteMetadatasAsync(_ metadatas: [tableMetadata]) async { - guard !metadatas.isEmpty else { - return + func deleteMetadataOcIds(_ ocIds: [String]) { + do { + let realm = try Realm() + try realm.write { + let results = realm.objects(tableMetadata.self).filter("ocId IN %@", ocIds) + realm.delete(results) + } + } catch let error as NSError { + NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] Could not access database: \(error)") } - let detached = metadatas.map { $0.detachedCopy() } + } - await core.performRealmWriteAsync { realm in - for detached in detached { - if let managed = realm.object(ofType: tableMetadata.self, forPrimaryKey: detached.ocId) { - realm.delete(managed) - } + func deleteMetadatas(_ metadatas: [tableMetadata]) { + do { + let realm = try Realm() + try realm.write { + realm.delete(metadatas) } + } catch let error { + NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] Could not write to database: \(error)") } } - func renameMetadata(fileNameNew: String, ocId: String, status: Int = NCGlobal.shared.metadataStatusNormal) async { - await core.performRealmWriteAsync { realm in - guard let metadata = realm.objects(tableMetadata.self) - .filter("ocId == %@", ocId) - .first else { - return - } + func renameMetadata(fileNameNew: String, ocId: String, status: Int = NCGlobal.shared.metadataStatusNormal) { + do { + let realm = try Realm() + try realm.write { + if let result = realm.objects(tableMetadata.self).filter("ocId == %@", ocId).first { + let fileNameView = result.fileNameView + let fileIdMOV = result.livePhotoFile + let directoryServerUrl = self.utilityFileSystem.stringAppendServerUrl(result.serverUrl, addFileName: fileNameView) + let resultsType = NextcloudKit.shared.nkCommonInstance.getInternalType(fileName: fileNameNew, mimeType: "", directory: result.directory, account: result.account) + + result.fileName = fileNameNew + result.fileNameView = fileNameNew + result.iconName = resultsType.iconName + result.contentType = resultsType.mimeType + result.classFile = resultsType.classFile + result.status = status + + if status == NCGlobal.shared.metadataStatusNormal { + result.sessionDate = nil + } else { + result.sessionDate = Date() + } - let utilityFileSystem = NCUtilityFileSystem() - let oldFileNameView = metadata.fileNameView - let account = metadata.account - let originalServerUrl = metadata.serverUrl + if result.directory, + let resultDirectory = realm.objects(tableDirectory.self).filter("account == %@ AND serverUrl == %@", result.account, directoryServerUrl).first { + let serverUrlTo = self.utilityFileSystem.stringAppendServerUrl(result.serverUrl, addFileName: fileNameNew) - metadata.fileName = fileNameNew - metadata.fileNameView = fileNameNew - metadata.status = status - metadata.sessionDate = (status == NCGlobal.shared.metadataStatusNormal) ? nil : Date() + resultDirectory.serverUrl = serverUrlTo + } else { + let atPath = self.utilityFileSystem.getDirectoryProviderStorageOcId(result.ocId) + "/" + fileNameView + let toPath = self.utilityFileSystem.getDirectoryProviderStorageOcId(result.ocId) + "/" + fileNameNew - if metadata.directory { - let oldDirUrl = utilityFileSystem.createServerUrl(serverUrl: originalServerUrl, fileName: oldFileNameView) - let newDirUrl = utilityFileSystem.createServerUrl(serverUrl: originalServerUrl, fileName: fileNameNew) + self.utilityFileSystem.moveFile(atPath: atPath, toPath: toPath) + } + + if result.isLivePhoto, + let resultMOV = realm.objects(tableMetadata.self).filter("fileId == %@ AND account == %@", fileIdMOV, result.account).first { + let fileNameView = resultMOV.fileNameView + let fileName = (fileNameNew as NSString).deletingPathExtension + let ext = (resultMOV.fileName as NSString).pathExtension + resultMOV.fileName = fileName + "." + ext + resultMOV.fileNameView = fileName + "." + ext - if let dir = realm.objects(tableDirectory.self) - .filter("account == %@ AND serverUrl == %@", account, oldDirUrl) - .first { - dir.serverUrl = newDirUrl + let atPath = self.utilityFileSystem.getDirectoryProviderStorageOcId(resultMOV.ocId) + "/" + fileNameView + let toPath = self.utilityFileSystem.getDirectoryProviderStorageOcId(resultMOV.ocId) + "/" + fileName + "." + ext + + self.utilityFileSystem.moveFile(atPath: atPath, toPath: toPath) + } } - } else { - let atPath = utilityFileSystem.getDirectoryProviderStorageOcId(metadata.ocId, userId: metadata.userId, urlBase: metadata.urlBase) + "/" + oldFileNameView - let toPath = utilityFileSystem.getDirectoryProviderStorageOcId(metadata.ocId, userId: metadata.userId, urlBase: metadata.urlBase) + "/" + fileNameNew - utilityFileSystem.moveFile(atPath: atPath, toPath: toPath) } + } catch let error { + NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] Could not write to database: \(error)") } } - /// Asynchronously restores the file name of a metadata entry and updates related file system and Realm entries. - /// - Parameter ocId: The object ID (ocId) of the file to restore. - func restoreMetadataFileNameAsync(ocId: String) async { - await core.performRealmWriteAsync { realm in - guard let result = realm.objects(tableMetadata.self) - .filter("ocId == %@", ocId) - .first, - let encodedURLString = result.serverUrlFileName.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed), - let url = URL(string: encodedURLString) - else { - return - } + func restoreMetadataFileName(ocId: String) { + do { + let realm = try Realm() + try realm.write { + if let result = realm.objects(tableMetadata.self).filter("ocId == %@", ocId).first, + let encodedURLString = result.serveUrlFileName.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed), + let url = URL(string: encodedURLString) { + let fileIdMOV = result.livePhotoFile + let directoryServerUrl = self.utilityFileSystem.stringAppendServerUrl(result.serverUrl, addFileName: result.fileNameView) + let lastPathComponent = url.lastPathComponent + let fileName = lastPathComponent.removingPercentEncoding ?? lastPathComponent + let fileNameView = result.fileNameView + + result.fileName = fileName + result.fileNameView = fileName + result.status = NCGlobal.shared.metadataStatusNormal + result.sessionDate = nil + + if result.directory, + let resultDirectory = realm.objects(tableDirectory.self).filter("account == %@ AND serverUrl == %@", result.account, directoryServerUrl).first { + let serverUrlTo = self.utilityFileSystem.stringAppendServerUrl(result.serverUrl, addFileName: fileName) + + resultDirectory.serverUrl = serverUrlTo + } else { + let atPath = self.utilityFileSystem.getDirectoryProviderStorageOcId(result.ocId) + "/" + fileNameView + let toPath = self.utilityFileSystem.getDirectoryProviderStorageOcId(result.ocId) + "/" + fileName + + self.utilityFileSystem.moveFile(atPath: atPath, toPath: toPath) + } - let utilityFileSystem = NCUtilityFileSystem() - let fileIdMOV = result.livePhotoFile - let directoryServerUrl = utilityFileSystem.createServerUrl(serverUrl: result.serverUrl, fileName: result.fileNameView) - let lastPathComponent = url.lastPathComponent - let fileName = lastPathComponent.removingPercentEncoding ?? lastPathComponent - let fileNameView = result.fileNameView - - result.fileName = fileName - result.fileNameView = fileName - result.status = NCGlobal.shared.metadataStatusNormal - result.sessionDate = nil - - if result.directory, - let resultDirectory = realm.objects(tableDirectory.self) - .filter("account == %@ AND serverUrl == %@", result.account, directoryServerUrl) - .first { - let serverUrlTo = utilityFileSystem.createServerUrl(serverUrl: result.serverUrl, fileName: fileName) - resultDirectory.serverUrl = serverUrlTo - } else { - let atPath = utilityFileSystem.getDirectoryProviderStorageOcId(result.ocId, userId: result.userId, urlBase: result.urlBase) + "/" + fileNameView - let toPath = utilityFileSystem.getDirectoryProviderStorageOcId(result.ocId, userId: result.userId, urlBase: result.urlBase) + "/" + fileName - utilityFileSystem.moveFile(atPath: atPath, toPath: toPath) - } + if result.isLivePhoto, + let resultMOV = realm.objects(tableMetadata.self).filter("fileId == %@ AND account == %@", fileIdMOV, result.account).first { + let fileNameView = resultMOV.fileNameView + let fileName = (fileName as NSString).deletingPathExtension + let ext = (resultMOV.fileName as NSString).pathExtension + resultMOV.fileName = fileName + "." + ext + resultMOV.fileNameView = fileName + "." + ext - if result.isLivePhoto, - let resultMOV = realm.objects(tableMetadata.self) - .filter("fileId == %@ AND account == %@", fileIdMOV, result.account) - .first { - let fileNameViewMOV = resultMOV.fileNameView - let baseName = (fileName as NSString).deletingPathExtension - let ext = (resultMOV.fileName as NSString).pathExtension - let fullFileName = baseName + "." + ext - - resultMOV.fileName = fullFileName - resultMOV.fileNameView = fullFileName - - let atPath = utilityFileSystem.getDirectoryProviderStorageOcId(resultMOV.ocId, userId: resultMOV.userId, urlBase: resultMOV.urlBase) + "/" + fileNameViewMOV - let toPath = utilityFileSystem.getDirectoryProviderStorageOcId(resultMOV.ocId, userId: resultMOV.userId, urlBase: resultMOV.urlBase) + "/" + fullFileName - utilityFileSystem.moveFile(atPath: atPath, toPath: toPath) - } - } - } + let atPath = self.utilityFileSystem.getDirectoryProviderStorageOcId(resultMOV.ocId) + "/" + fileNameView + let toPath = self.utilityFileSystem.getDirectoryProviderStorageOcId(resultMOV.ocId) + "/" + fileName + "." + ext - func setMetadataServerUrlFileNameStatusNormalAsync(ocId: String) async { - await core.performRealmWriteAsync { realm in - if let result = realm.objects(tableMetadata.self) - .filter("ocId == %@", ocId) - .first { - result.serverUrlFileName = NCUtilityFileSystem().createServerUrl(serverUrl: result.serverUrl, fileName: result.fileName) - result.status = NCGlobal.shared.metadataStatusNormal - result.sessionDate = nil + self.utilityFileSystem.moveFile(atPath: atPath, toPath: toPath) + } + } } + } catch let error { + NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] Could not write to database: \(error)") } } - func setMetadataLivePhotoByServerAsync(account: String, - ocId: String, - livePhotoFile: String) async { - await core.performRealmWriteAsync { realm in - if let result = realm.objects(tableMetadata.self) - .filter("account == %@ AND ocId == %@", account, ocId) - .first { - result.isFlaggedAsLivePhotoByServer = true - result.livePhotoFile = livePhotoFile + func setMetadataServeUrlFileNameStatusNormal(ocId: String) { + do { + let realm = try Realm() + try realm.write { + if let result = realm.objects(tableMetadata.self).filter("ocId == %@", ocId).first { + result.serveUrlFileName = self.utilityFileSystem.stringAppendServerUrl(result.serverUrl, addFileName: result.fileName) + result.status = NCGlobal.shared.metadataStatusNormal + result.sessionDate = nil + } } + } catch let error { + NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] Could not write to database: \(error)") } } - func updateMetadatasFavoriteAsync(account: String, metadatas: [tableMetadata]) async { - guard !metadatas.isEmpty else { return } - - await core.performRealmWriteAsync { realm in - let oldFavorites = realm.objects(tableMetadata.self) - .filter("account == %@ AND favorite == true", account) - for item in oldFavorites { - item.favorite = false - } - realm.add(metadatas, update: .all) - } - } - - /// Asynchronously updates a list of `tableMetadata` entries in Realm for a given account and server URL. - /// - /// This function performs the following steps: - /// 1. Skips all entries with `status != metadataStatusNormal`. - /// 2. Deletes existing metadata entries with `status == metadataStatusNormal` that are not in the skip list. - /// 3. Copies matching `mediaSearch` from previously deleted metadata to the incoming list. - /// 4. Inserts or updates new metadata entries into Realm, except those in the skip list. - /// - /// - Parameters: - /// - metadatas: An array of incoming detached `tableMetadata` objects to insert or update. - /// - serverUrl: The server URL associated with the metadata entries. - /// - account: The account identifier used to scope the metadata update. - func updateMetadatasFilesAsync(_ metadatas: [tableMetadata], serverUrl: String, account: String) async { - await core.performRealmWriteAsync { realm in - let ocIdsToSkip = Set( - realm.objects(tableMetadata.self) - .filter("status != %d", NCGlobal.shared.metadataStatusNormal) - .map(\.ocId) - ) - - let resultsToDelete = realm.objects(tableMetadata.self) - .filter("account == %@ AND serverUrl == %@ AND status == %d AND fileName != %@", account, serverUrl, NCGlobal.shared.metadataStatusNormal, NextcloudKit.shared.nkCommonInstance.rootFileName) - .filter { !ocIdsToSkip.contains($0.ocId) } - - // Cache mediaSearch (and anything else needed) before deletion, keyed by ocId. - let metadatasByOcId: [String: tableMetadata] = Dictionary( - uniqueKeysWithValues: resultsToDelete.map { object in - (object.ocId, tableMetadata(value: object)) - } - ) - - realm.delete(resultsToDelete) + func setMetadataEtagResource(ocId: String, etagResource: String?) { + guard let etagResource else { return } - for metadata in metadatas { - guard !ocIdsToSkip.contains(metadata.ocId) else { - continue - } - if let previous = metadatasByOcId[metadata.ocId] { - metadata.mediaSearch = previous.mediaSearch - } - - realm.add(metadata.detachedCopy(), update: .all) + do { + let realm = try Realm() + try realm.write { + let result = realm.objects(tableMetadata.self).filter("ocId == %@", ocId).first + result?.etagResource = etagResource } + } catch let error { + NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] Could not write to database: \(error)") } } - func setMetadataEncryptedAsync(ocId: String, encrypted: Bool) async { - await core.performRealmWriteAsync { realm in - let result = realm.objects(tableMetadata.self) - .filter("ocId == %@", ocId) - .first - result?.e2eEncrypted = encrypted + func setMetadataLivePhotoByServer(account: String, ocId: String, livePhotoFile: String) { + do { + let realm = try Realm() + try realm.write { + if let result = realm.objects(tableMetadata.self).filter("account == %@ AND ocId == %@", account, ocId).first { + result.isFlaggedAsLivePhotoByServer = true + result.livePhotoFile = livePhotoFile + } + } + } catch let error { + NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] Could not write to database: \(error)") } } - func setMetadataFileNameViewAsync(serverUrl: String, fileName: String, newFileNameView: String, account: String) async { - await core.performRealmWriteAsync { realm in - let result = realm.objects(tableMetadata.self) - .filter("account == %@ AND serverUrl == %@ AND fileName == %@", account, serverUrl, fileName) - .first - result?.fileNameView = newFileNameView + func updateMetadatasFavorite(account: String, metadatas: [tableMetadata]) { + do { + let realm = try Realm() + try realm.write { + let results = realm.objects(tableMetadata.self).filter("account == %@ AND favorite == true", account) + for result in results { + result.favorite = false + } + realm.add(metadatas, update: .all) + } + } catch let error { + NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] Could not write to database: \(error)") } } - func moveMetadataAsync(ocId: String, serverUrlTo: String) async { - await core.performRealmWriteAsync { realm in - if let result = realm.objects(tableMetadata.self) - .filter("ocId == %@", ocId) - .first { - result.serverUrl = serverUrlTo + func updateMetadatasFiles(_ metadatas: [tableMetadata], serverUrl: String, account: String) { + do { + let realm = try Realm() + try realm.write { + let results = realm.objects(tableMetadata.self).filter(NSPredicate(format: "account == %@ AND serverUrl == %@ AND status == %d", account, serverUrl, NCGlobal.shared.metadataStatusNormal)) + realm.delete(results) + for metadata in metadatas { + if realm.objects(tableMetadata.self).filter(NSPredicate(format: "ocId == %@ AND status != %d", metadata.ocId, NCGlobal.shared.metadataStatusNormal)).first != nil { + continue + } + realm.add(tableMetadata(value: metadata), update: .all) + } } + } catch let error { + NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] Could not write to database: \(error)") } } - func setLivePhotoFile(fileId: String, livePhotoFile: String) async { - await core.performRealmWriteAsync { realm in - let result = realm.objects(tableMetadata.self) - .filter("fileId == %@", fileId) - .first - result?.livePhotoFile = livePhotoFile - } - } - - func clearAssetLocalIdentifiersAsync(_ assetLocalIdentifiers: [String]) async { - await core.performRealmWriteAsync { realm in - let results = realm.objects(tableMetadata.self) - .filter("assetLocalIdentifier IN %@", assetLocalIdentifiers) - for result in results { - result.assetLocalIdentifier = "" + func setMetadataEncrypted(ocId: String, encrypted: Bool) { + do { + let realm = try Realm() + try realm.write { + let result = realm.objects(tableMetadata.self).filter("ocId == %@", ocId).first + result?.e2eEncrypted = encrypted } + } catch let error { + NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] Could not write to database: \(error)") } } - /// Asynchronously sets the favorite status of a `tableMetadata` entry. - /// Optionally stores the previous favorite flag and updates the sync status. - func setMetadataFavoriteAsync(ocId: String, favorite: Bool?, saveOldFavorite: String?, status: Int) async { - await core.performRealmWriteAsync { realm in - guard let result = realm.objects(tableMetadata.self) - .filter("ocId == %@", ocId) - .first else { - return - } - - if let favorite { - result.favorite = favorite + func setMetadataFileNameView(serverUrl: String, fileName: String, newFileNameView: String, account: String) { + do { + let realm = try Realm() + try realm.write { + let result = realm.objects(tableMetadata.self).filter("account == %@ AND serverUrl == %@ AND fileName == %@", account, serverUrl, fileName).first + result?.fileNameView = newFileNameView } - - result.storeFlag = saveOldFavorite - result.status = status - result.sessionDate = (status == NCGlobal.shared.metadataStatusNormal) ? nil : Date() + } catch let error { + NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] Could not write to database: \(error)") } } - /// Asynchronously updates a `tableMetadata` entry to set copy/move status and target server URL. - func setMetadataCopyMoveAsync(ocId: String, destination: String, overwrite: String?, status: Int) async { - await core.performRealmWriteAsync { realm in - guard let result = realm.objects(tableMetadata.self) - .filter("ocId == %@", ocId) - .first else { - return + func moveMetadata(ocId: String, serverUrlTo: String) { + do { + let realm = try Realm() + try realm.write { + if let result = realm.objects(tableMetadata.self).filter("ocId == %@", ocId).first { + result.serverUrl = serverUrlTo + } } - - result.destination = destination - result.storeFlag = overwrite - result.status = status - result.sessionDate = (status == NCGlobal.shared.metadataStatusNormal) ? nil : Date() + } catch let error { + NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] Could not write to database: \(error)") } } - func clearMetadatasUploadAsync(account: String) async { - await core.performRealmWriteAsync { realm in - let results = realm.objects(tableMetadata.self) - .filter("account == %@ AND (status == %d OR status == %d)", account, NCGlobal.shared.metadataStatusWaitUpload, NCGlobal.shared.metadataStatusUploadError) - realm.delete(results) + func clearAssetLocalIdentifiers(_ assetLocalIdentifiers: [String]) { + do { + let realm = try Realm() + try realm.write { + let results = realm.objects(tableMetadata.self).filter("assetLocalIdentifier IN %@", assetLocalIdentifiers) + for result in results { + result.assetLocalIdentifier = "" + } + } + } catch let error { + NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] Could not write to database: \(error)") } } - /// Syncs the remote and local metadata. - /// Returns true if there were changes (additions or deletions), false if everything was already up-to-date. - func mergeRemoteMetadatasAsync(remoteMetadatas: [tableMetadata], localMetadatas: [tableMetadata]) async -> Bool { - // Set of ocId - let remoteOcIds = Set(remoteMetadatas.map { $0.ocId }) - let localOcIds = Set(localMetadatas.map { $0.ocId }) - - // Calculate diffs - let toDeleteOcIds = localOcIds.subtracting(remoteOcIds) - let toAddOcIds = remoteOcIds.subtracting(localOcIds) + func setMetadataFavorite(ocId: String, favorite: Bool?, saveOldFavorite: String?, status: Int) { + do { + let realm = try Realm() + try realm.write { + let result = realm.objects(tableMetadata.self).filter("ocId == %@", ocId).first + if let favorite { + result?.favorite = favorite + } + result?.storeFlag = saveOldFavorite + result?.status = status - guard !toDeleteOcIds.isEmpty || !toAddOcIds.isEmpty else { - return false // No changes needed + if status == NCGlobal.shared.metadataStatusNormal { + result?.sessionDate = nil + } else { + result?.sessionDate = Date() + } + } + } catch let error { + NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] Could not write to database: \(error)") } + } - let toDeleteKeys = Array(toDeleteOcIds) + func setMetadataCopyMove(ocId: String, serverUrlTo: String, overwrite: String?, status: Int) { + do { + let realm = try Realm() + try realm.write { + let result = realm.objects(tableMetadata.self).filter("ocId == %@", ocId).first + result?.serverUrlTo = serverUrlTo + result?.storeFlag = overwrite + result?.status = status - await core.performRealmWriteAsync { realm in - let toAdd = remoteMetadatas.filter { toAddOcIds.contains($0.ocId) } - let toDelete = toDeleteKeys.compactMap { - realm.object(ofType: tableMetadata.self, forPrimaryKey: $0) + if status == NCGlobal.shared.metadataStatusNormal { + result?.sessionDate = nil + } else { + result?.sessionDate = Date() + } } - - realm.delete(toDelete) - realm.add(toAdd, update: .modified) + } catch let error { + NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] Could not write to database: \(error)") } - - return true } - // MARK: - Realm Read - - func getAllTableMetadataAsync() async -> [tableMetadata] { - return await core.performRealmReadAsync { realm in - realm.objects(tableMetadata.self).map { tableMetadata(value: $0) } - } ?? [] - } + // MARK: - GetMetadata func getMetadata(predicate: NSPredicate) -> tableMetadata? { - return core.performRealmRead { realm in - realm.objects(tableMetadata.self) - .filter(predicate) - .first - .map { $0.detachedCopy() } - } - } - - func getMetadataAsync(predicate: NSPredicate) async -> tableMetadata? { - return await core.performRealmReadAsync { realm in - realm.objects(tableMetadata.self) - .filter(predicate) - .first - .map { $0.detachedCopy() } + do { + let realm = try Realm() + guard let result = realm.objects(tableMetadata.self).filter(predicate).first else { return nil } + return tableMetadata(value: result) + } catch let error as NSError { + NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] Could not access database: \(error)") } - } - func getResultsMetadatasAsync(predicate: NSPredicate) async -> Results? { - await core.performRealmReadAsync { realm in - let results = realm.objects(tableMetadata.self) - .filter(predicate) - return results.freeze() - } + return nil } func getMetadatas(predicate: NSPredicate) -> [tableMetadata] { - core.performRealmRead { realm in - realm.objects(tableMetadata.self) - .filter(predicate) - .map { $0.detachedCopy() } - } ?? [] + do { + let realm = try Realm() + let results = realm.objects(tableMetadata.self).filter(predicate) + return Array(results.map { tableMetadata(value: $0) }) + } catch let error as NSError { + NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] Could not access database: \(error)") + } + return [] } - func getMetadatas(predicate: NSPredicate, - sortedByKeyPath: String, - ascending: Bool = false) -> [tableMetadata]? { - return core.performRealmRead { realm in - realm.objects(tableMetadata.self) - .filter(predicate) - .sorted(byKeyPath: sortedByKeyPath, ascending: ascending) - .map { $0.detachedCopy() } + func getMetadatas(predicate: NSPredicate, sortedByKeyPath: String, ascending: Bool = false) -> [tableMetadata]? { + do { + let realm = try Realm() + let results = realm.objects(tableMetadata.self).filter(predicate).sorted(byKeyPath: sortedByKeyPath, ascending: ascending) + return Array(results.map { tableMetadata(value: $0) }) + } catch let error as NSError { + NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] Could not access database: \(error)") } + return nil } - func getMetadatasAsync(predicate: NSPredicate, - sortedByKeyPath: String, - ascending: Bool = false, - limit: Int? = nil) async -> [tableMetadata]? { - return await core.performRealmReadAsync { realm in - let results = realm.objects(tableMetadata.self) - .filter(predicate) - .sorted(byKeyPath: sortedByKeyPath, - ascending: ascending) + func getMetadatas(predicate: NSPredicate, numItems: Int, sorted: String, ascending: Bool) -> [tableMetadata] { + var counter: Int = 0 + var metadatas: [tableMetadata] = [] - if let limit { - let sliced = results.prefix(limit) - return sliced.map { $0.detachedCopy() } - } else { - return results.map { $0.detachedCopy() } + do { + let realm = try Realm() + let results = realm.objects(tableMetadata.self).filter(predicate).sorted(byKeyPath: sorted, ascending: ascending) + for result in results where counter < numItems { + metadatas.append(tableMetadata(value: result)) + counter += 1 } + } catch let error as NSError { + NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] Could not access database: \(error)") } - } - - func getMetadatas(predicate: NSPredicate, - numItems: Int, - sorted: String, - ascending: Bool) -> [tableMetadata] { - return core.performRealmRead { realm in - let results = realm.objects(tableMetadata.self) - .filter(predicate) - .sorted(byKeyPath: sorted, ascending: ascending) - return results.prefix(numItems) - .map { $0.detachedCopy() } - } ?? [] + return metadatas } func getMetadataFromOcId(_ ocId: String?) -> tableMetadata? { guard let ocId else { return nil } - return core.performRealmRead { realm in - realm.objects(tableMetadata.self) - .filter("ocId == %@", ocId) - .first - .map { $0.detachedCopy() } - } - } - - func getMetadataFromOcIdAsync(_ ocId: String?) async -> tableMetadata? { + do { + let realm = try Realm() + guard let result = realm.objects(tableMetadata.self).filter("ocId == %@", ocId).first else { return nil } + return tableMetadata(value: result) + } catch let error as NSError { + NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] Could not access database: \(error)") + } + return nil + } + +// func getMetadataFromOcIdAndocIdTransfer(_ ocId: String?) -> tableMetadata? { +// guard let ocId else { return nil } +// +// do { +// let realm = try Realm() +// if let result = realm.objects(tableMetadata.self).filter("ocId == %@", ocId).first { +// return tableMetadata(value: result) +// } +// if let result = realm.objects(tableMetadata.self).filter("ocIdTransfer == %@", ocId).first { +// return tableMetadata(value: result) +// } +// } catch let error as NSError { +// NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] Could not access database: \(error)") +// } +// return nil +// } + + func getMetadataFromOcIdAndocIdTransfer(_ ocId: String?) -> tableMetadata? { guard let ocId else { return nil } - return await core.performRealmReadAsync { realm in - realm.objects(tableMetadata.self) - .filter("ocId == %@", ocId) - .first - .map { $0.detachedCopy() } - } - } - - func getMetadataFromOcIdAndocIdTransferAsync(_ ocId: String?) async -> tableMetadata? { - guard let ocId else { - return nil - } - - return await core.performRealmReadAsync { realm in + return performRealmRead { realm in realm.objects(tableMetadata.self) .filter("ocId == %@ OR ocIdTransfer == %@", ocId, ocId) .first - .map { $0.detachedCopy() } + .map { tableMetadata(value: $0) } } } - - /// Asynchronously retrieves the metadata for a folder, based on its session and serverUrl. - /// Handles the home directory case rootFileName) and detaches the Realm object before returning. - func getMetadataFolderAsync(session: NCSession.Session, serverUrl: String) async -> tableMetadata? { + +// func getMetadataFromOcIdAndocIdTransfer(_ ocId: String?) -> tableMetadata? { +// guard let ocId else { return nil } +// +// return performRealmRead { realm in +// // First, try to find by ocId +// if let result = realm.objects(tableMetadata.self) +// .filter("ocId == %@", ocId) +// .first { +// return tableMetadata(value: result) +// } +// +// // If not found, try to find by ocIdTransfer +// if let result = realm.objects(tableMetadata.self) +// .filter("ocIdTransfer == %@", ocId) +// .first { +// return tableMetadata(value: result) +// } +// +// // Not found +// return nil +// } +// } + + func getMetadataFolder(session: NCSession.Session, serverUrl: String) -> tableMetadata? { var serverUrl = serverUrl var fileName = "" - let home = NCUtilityFileSystem().getHomeServer(session: session) + let serverUrlHome = utilityFileSystem.getHomeServer(session: session) - if home == serverUrl { - fileName = NextcloudKit.shared.nkCommonInstance.rootFileName + if serverUrlHome == serverUrl { + fileName = "." + serverUrl = ".." } else { fileName = (serverUrl as NSString).lastPathComponent - if let serverDirectoryUp = NCUtilityFileSystem().serverDirectoryUp(serverUrl: serverUrl, home: home) { - serverUrl = serverDirectoryUp + if let path = utilityFileSystem.deleteLastPath(serverUrlPath: serverUrl) { + serverUrl = path } } - return await core.performRealmReadAsync { realm in - realm.objects(tableMetadata.self) - .filter("account == %@ AND serverUrl == %@ AND fileName == %@", session.account, serverUrl, fileName) - .first - .map { $0.detachedCopy() } + do { + let realm = try Realm() + guard let result = realm.objects(tableMetadata.self).filter("account == %@ AND serverUrl == %@ AND fileName == %@", session.account, serverUrl, fileName).first else { return nil } + return tableMetadata(value: result) + } catch let error as NSError { + NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] Could not access database: \(error)") } + return nil } func getMetadataLivePhoto(metadata: tableMetadata) -> tableMetadata? { - guard metadata.isLivePhoto else { - return nil - } - let detached = metadata.detachedCopy() - - return core.performRealmRead { realm in - realm.objects(tableMetadata.self) - .filter(NSPredicate(format: "account == %@ AND serverUrl == %@ AND fileId == %@", - detached.account, - detached.serverUrl, - detached.livePhotoFile)) - .first - .map { $0.detachedCopy() } - } - } - - func getMetadataLivePhotoAsync(metadata: tableMetadata) async -> tableMetadata? { - guard metadata.isLivePhoto else { - return nil - } - let detached = metadata.detachedCopy() + guard metadata.isLivePhoto else { return nil } - return await core.performRealmReadAsync { realm in - realm.objects(tableMetadata.self) - .filter(NSPredicate(format: "account == %@ AND serverUrl == %@ AND fileId == %@", - detached.account, - detached.serverUrl, - detached.livePhotoFile)) - .first - .map { $0.detachedCopy() } + do { + let realm = try Realm() + guard let result = realm.objects(tableMetadata.self).filter(NSPredicate(format: "account == %@ AND serverUrl == %@ AND fileId == %@", + metadata.account, + metadata.serverUrl, + metadata.livePhotoFile)).first else { return nil } + return tableMetadata(value: result) + } catch let error as NSError { + NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] Could not access database: \(error)") } + return nil } func getMetadataConflict(account: String, serverUrl: String, fileNameView: String, nativeFormat: Bool) -> tableMetadata? { @@ -1128,109 +1200,122 @@ extension NCManageDatabase { fileNameConflict)) } - func getTableMetadatasDirectoryFavoriteIdentifierRankAsync(account: String) async -> [String: NSNumber] { - let result = await core.performRealmReadAsync { realm in - var listIdentifierRank: [String: NSNumber] = [:] - var counter = Int64(10) + // MARK: - GetResult(s)Metadata - let results = realm.objects(tableMetadata.self) - .filter("account == %@ AND directory == true AND favorite == true", account) - .sorted(byKeyPath: "fileNameView", ascending: true) - - results.forEach { item in - counter += 1 - listIdentifierRank[item.ocId] = NSNumber(value: counter) + func getResultsMetadatasPredicate(_ predicate: NSPredicate, layoutForView: NCDBLayoutForView?, directoryOnTop: Bool = true) -> [tableMetadata] { + do { + let realm = try Realm() + var results = realm.objects(tableMetadata.self).filter(predicate).freeze() + let layout: NCDBLayoutForView = layoutForView ?? NCDBLayoutForView() + + if layout.sort == "fileName" { + let sortedResults = results.sorted { + let ordered = layout.ascending ? ComparisonResult.orderedAscending : ComparisonResult.orderedDescending + // 1. favorite order + if $0.favorite == $1.favorite { + // 2. directory order TOP + if directoryOnTop { + if $0.directory == $1.directory { + // 3. natural fileName + return $0.fileNameView.localizedStandardCompare($1.fileNameView) == ordered + } else { + return $0.directory && !$1.directory + } + } else { + return $0.fileNameView.localizedStandardCompare($1.fileNameView) == ordered + } + } else { + return $0.favorite && !$1.favorite + } + } + return sortedResults + } else { + if directoryOnTop { + results = results.sorted(byKeyPath: layout.sort, ascending: layout.ascending).sorted(byKeyPath: "favorite", ascending: false).sorted(byKeyPath: "directory", ascending: false) + } else { + results = results.sorted(byKeyPath: layout.sort, ascending: layout.ascending).sorted(byKeyPath: "favorite", ascending: false) + } } + return Array(results) - return listIdentifierRank + } catch let error as NSError { + NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] Could not access database: \(error)") } - return result ?? [:] + return [] } -#if !EXTENSION - /// Asynchronously retrieves and sorts `tableMetadata` associated with groupfolders for a given session. - /// - Parameters: - /// - session: The `NCSession.Session` containing account and server information. - /// - layoutForView: An optional layout configuration used for sorting. - /// - Returns: An array of sorted and detached `tableMetadata` objects. - func getMetadatasFromGroupfoldersAsync(session: NCSession.Session, layoutForView: NCDBLayoutForView?) async -> [tableMetadata] { - let homeServerUrl = NCUtilityFileSystem().getHomeServer(session: session) + func getResultsMetadatas(predicate: NSPredicate, sortedByKeyPath: String, ascending: Bool, arraySlice: Int) -> [tableMetadata] { + do { + let realm = try Realm() + let results = realm.objects(tableMetadata.self).filter(predicate).sorted(byKeyPath: sortedByKeyPath, ascending: ascending).prefix(arraySlice) + return Array(results) + } catch let error as NSError { + NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] Could not access database: \(error)") + } + return [] + } + + func getResultMetadata(predicate: NSPredicate) -> tableMetadata? { + do { + let realm = try Realm() + return realm.objects(tableMetadata.self).filter(predicate).first + } catch let error as NSError { + NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] Could not access database: \(error)") + } + return nil + } + + func getResultMetadataFromFileName(_ fileName: String, serverUrl: String, sessionTaskIdentifier: Int) -> tableMetadata? { + do { + let realm = try Realm() + return realm.objects(tableMetadata.self).filter("fileName == %@ AND serverUrl == %@ AND sessionTaskIdentifier == %d", fileName, serverUrl, sessionTaskIdentifier).first + } catch let error as NSError { + NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] Could not access database: \(error)") + } + return nil + } - let detachedMetadatas: [tableMetadata] = await core.performRealmReadAsync { realm in - var ocIds: [String] = [] + func getResultsMetadatasFromGroupfolders(session: NCSession.Session) -> Results? { + var ocId: [String] = [] + let homeServerUrl = utilityFileSystem.getHomeServer(session: session) - // Safely fetch and detach groupfolders - let groupfolders = realm.objects(TableGroupfolders.self) - .filter("account == %@", session.account) - .sorted(byKeyPath: "mountPoint", ascending: true) - .map { TableGroupfolders(value: $0) } + do { + let realm = try Realm() + let groupfolders = realm.objects(TableGroupfolders.self).filter("account == %@", session.account).sorted(byKeyPath: "mountPoint", ascending: true) for groupfolder in groupfolders { let mountPoint = groupfolder.mountPoint.hasPrefix("/") ? groupfolder.mountPoint : "/" + groupfolder.mountPoint let serverUrlFileName = homeServerUrl + mountPoint - if let directory = realm.objects(tableDirectory.self) - .filter("account == %@ AND serverUrl == %@", session.account, serverUrlFileName) - .first, - let metadata = realm.objects(tableMetadata.self) - .filter("ocId == %@", directory.ocId) - .first { - ocIds.append(metadata.ocId) + if let directory = realm.objects(tableDirectory.self).filter("account == %@ AND serverUrl == %@", session.account, serverUrlFileName).first, + let result = realm.objects(tableMetadata.self).filter("ocId == %@", directory.ocId).first { + ocId.append(result.ocId) } } - // Fetch and detach the corresponding metadatas - return realm.objects(tableMetadata.self) - .filter("ocId IN %@", ocIds) - .map { $0.detachedCopy() } - } ?? [] - - let sorted = await self.sortedMetadata(layoutForView: layoutForView, account: session.account, metadatas: detachedMetadatas) - return sorted - } -#endif - - func getRootContainerMetadataAsync(accout: String) async -> tableMetadata? { - return await core.performRealmReadAsync { realm in - realm.objects(tableMetadata.self) - .filter("fileName == %@ AND account == %@", NextcloudKit.shared.nkCommonInstance.rootFileName, accout) - .first - .map { $0.detachedCopy() } + return realm.objects(tableMetadata.self).filter("ocId IN %@", ocId) + } catch let error as NSError { + NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] Could not access database: \(error)") } - } - func getMetadatasAsync(predicate: NSPredicate) async -> [tableMetadata] { - await core.performRealmReadAsync { realm in - realm.objects(tableMetadata.self) - .filter(predicate) - .map { $0.detachedCopy() } - } ?? [] + return nil } - func getTableMetadatasDirectoryFavoriteIdentifierRankAsync(account: String) async -> [String: NSNumber] { - let result = await performRealmReadAsync { realm in - var listIdentifierRank: [String: NSNumber] = [:] - var counter = Int64(10) + func getTableMetadatasDirectoryFavoriteIdentifierRank(account: String) -> [String: NSNumber] { + var listIdentifierRank: [String: NSNumber] = [:] + var counter = 10 as Int64 - let results = realm.objects(tableMetadata.self) - .filter("account == %@ AND directory == true AND favorite == true", account) - .sorted(byKeyPath: "fileNameView", ascending: true) - - results.forEach { item in + do { + let realm = try Realm() + let results = realm.objects(tableMetadata.self).filter("account == %@ AND directory == true AND favorite == true", account).sorted(byKeyPath: "fileNameView", ascending: true) + for result in results { counter += 1 - listIdentifierRank[item.ocId] = NSNumber(value: counter) + listIdentifierRank[result.ocId] = NSNumber(value: Int64(counter)) } - - return listIdentifierRank - } - return result ?? [:] - } - - func getAssetLocalIdentifiersUploadedAsync() async -> [String]? { - return await core.performRealmReadAsync { realm in - let results = realm.objects(tableMetadata.self).filter("assetLocalIdentifier != ''") - return results.map { $0.assetLocalIdentifier } + } catch let error as NSError { + NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] Could not access database: \(error)") } + return listIdentifierRank } @objc func clearMetadatasUpload(account: String) { @@ -1244,106 +1329,59 @@ extension NCManageDatabase { NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] Could not write to database: \(error)") } } - - func getMetadataFromFileId(_ fileId: String?) -> tableMetadata? { - guard let fileId else { - return nil - } - return core.performRealmRead { realm in - realm.objects(tableMetadata.self) - .filter("fileId == %@", fileId) - .first - .map { $0.detachedCopy() } - } - } - - /// Asynchronously retrieves a `tableMetadata` object matching the given `fileId`, if available. - /// - Parameter fileId: The file identifier used to query the Realm database. - /// - Returns: A detached copy of the `tableMetadata` object, or `nil` if not found. - func getMetadataFromFileIdAsync(_ fileId: String?) async -> tableMetadata? { - guard let fileId else { - return nil - } + func getAssetLocalIdentifiersUploaded() -> [String]? { + var assetLocalIdentifiers: [String] = [] - return await core.performRealmReadAsync { realm in - let object = realm.objects(tableMetadata.self) - .filter("fileId == %@", fileId) - .first - return object?.detachedCopy() + do { + let realm = try Realm() + let results = realm.objects(tableMetadata.self).filter("assetLocalIdentifier != ''") + for result in results { + assetLocalIdentifiers.append(result.assetLocalIdentifier) + } + return assetLocalIdentifiers + } catch let error as NSError { + NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] Could not access database: \(error)") } + return nil } -#if !EXTENSION_FILE_PROVIDER_EXTENSION - /// Asynchronously retrieves and sorts `tableMetadata` objects matching a given predicate and layout. - func getMetadatasAsync(predicate: NSPredicate, - withLayout layoutForView: NCDBLayoutForView?, - withAccount account: String) async -> [tableMetadata] { - let detachedMetadatas = await core.performRealmReadAsync { realm in - realm.objects(tableMetadata.self) - .filter(predicate) - .map { $0.detachedCopy() } - } ?? [] - - let sorted = await self.sortedMetadata(layoutForView: layoutForView, account: account, metadatas: detachedMetadatas) - return sorted - } - - /// Asynchronously retrieves and sorts `tableMetadata` objects matching a given predicate and layout. - func getMetadatasAsyncDataSource(withServerUrl serverUrl: String, - withUserId userId: String, - withAccount account: String, - withLayout layoutForView: NCDBLayoutForView?, - withPreficate predicateSource: NSPredicate? = nil) async -> [tableMetadata] { - var predicate = NSPredicate(format: "account == %@ AND serverUrl == %@ AND fileName != %@ AND NOT (status IN %@)", account, serverUrl, NextcloudKit.shared.nkCommonInstance.rootFileName, NCGlobal.shared.metadataStatusHideInView) - - if NCPreferences().getPersonalFilesOnly(account: account) { - predicate = NSPredicate(format: "account == %@ AND serverUrl == %@ AND fileName != %@ AND (ownerId == %@ || ownerId == '') AND mountType == '' AND NOT (status IN %@)", account, serverUrl, NextcloudKit.shared.nkCommonInstance.rootFileName, userId, NCGlobal.shared.metadataStatusHideInView) - } - - if let predicateSource { - predicate = predicateSource + func getMetadataFromDirectory(account: String, serverUrl: String) -> Bool { + do { + let realm = try Realm() + guard let directory = realm.objects(tableDirectory.self).filter("account == %@ AND serverUrl == %@", account, serverUrl).first, + realm.objects(tableMetadata.self).filter("ocId == %@", directory.ocId).first != nil else { return false } + return true + } catch let error as NSError { + NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] Could not access database: \(error)") } - - let detachedMetadatas = await core.performRealmReadAsync { realm in - realm.objects(tableMetadata.self) - .filter(predicate) - .map { $0.detachedCopy() } - } ?? [] - - let cleanedMetadatas = filterAndNormalizeLivePhotos(from: detachedMetadatas) - let sorted = await self.sortedMetadata(layoutForView: layoutForView, account: account, metadatas: cleanedMetadatas) - - return sorted + return false } -#endif - func getMetadatasAsync(predicate: NSPredicate, - withSort sortDescriptors: [RealmSwift.SortDescriptor] = [], - withLimit limit: Int? = nil) async -> [tableMetadata]? { - await core.performRealmReadAsync { realm in - var results = realm.objects(tableMetadata.self) - .filter(predicate) - - if !sortDescriptors.isEmpty { - results = results.sorted(by: sortDescriptors) - } + func getMetadataFromFileId(_ fileId: String?) -> tableMetadata? { + guard let fileId else { return nil } - if let limit { - let sliced = results.prefix(limit) - return sliced.map { $0.detachedCopy() } - } else { - return results.map { $0.detachedCopy() } + do { + let realm = try Realm() + if let result = realm.objects(tableMetadata.self).filter("fileId == %@", fileId).first { + return tableMetadata(value: result) } + } catch let error as NSError { + NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] Could not access database: \(error)") } + return nil } - func hasUploadingMetadataWithChunksOrE2EE() -> Bool { - return core.performRealmRead { realm in - realm.objects(tableMetadata.self) - .filter("status == %d AND (chunk > 0 OR e2eEncrypted == true)", NCGlobal.shared.metadataStatusUploading) - .first != nil - } ?? false + func getResultMetadataFromOcId(_ ocId: String?) -> tableMetadata? { + guard let ocId else { return nil } + + do { + let realm = try Realm() + return realm.objects(tableMetadata.self).filter("ocId == %@", ocId).first + } catch let error as NSError { + NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] Could not access database: \(error)") + } + return nil } func getMetadataDirectoryAsync(serverUrl: String, account: String) async -> tableMetadata? { @@ -1359,7 +1397,7 @@ extension NCManageDatabase { return nil } - return await core.performRealmReadAsync { realm in + return await performRealmReadAsync { realm in let object = realm.objects(tableMetadata.self) .filter("account == %@ AND serverUrl == %@ AND fileName == %@", account, decodedBaseUrl, fileName) .first @@ -1379,8 +1417,8 @@ extension NCManageDatabase { guard let decodedBaseUrl = baseUrl.removingPercentEncoding else { return nil } - - return core.performRealmRead { realm in + + return performRealmRead { realm in let object = realm.objects(tableMetadata.self) .filter("account == %@ AND serverUrl == %@ AND fileName == %@", account, decodedBaseUrl, fileName) .first @@ -1388,39 +1426,141 @@ extension NCManageDatabase { } } - func getTransferAsync(tranfersSuccess: [tableMetadata]) async -> [tableMetadata] { - await core.performRealmReadAsync { realm in - let predicate = NSPredicate(format: "status IN %@", NCGlobal.shared.metadataStatusTransfers) - let sortDescriptors = [ - RealmSwift.SortDescriptor(keyPath: "status", ascending: false), - RealmSwift.SortDescriptor(keyPath: "sessionDate", ascending: true) - ] + func createMetadatasFolder(assets: [PHAsset], + useSubFolder: Bool, + session: NCSession.Session, completion: @escaping ([tableMetadata]) -> Void) { + var foldersCreated: Set = [] + var metadatas: [tableMetadata] = [] + let serverUrlBase = getAccountAutoUploadDirectory(session: session) + let fileNameBase = getAccountAutoUploadFileName(account: session.account) + let predicate = NSPredicate(format: "account == %@ AND serverUrl BEGINSWITH %@ AND directory == true", session.account, serverUrlBase) + + func createMetadata(serverUrl: String, fileName: String, metadata: tableMetadata?) { + guard !foldersCreated.contains(serverUrl + "/" + fileName) else { + return + } + foldersCreated.insert(serverUrl + "/" + fileName) + + if let metadata { + metadata.status = NCGlobal.shared.metadataStatusWaitCreateFolder + metadata.sessionSelector = NCGlobal.shared.selectorUploadAutoUpload + metadata.sessionDate = Date() + metadatas.append(tableMetadata(value: metadata)) + } else { + let metadata = NCManageDatabase.shared.createMetadata(fileName: fileName, + fileNameView: fileName, + ocId: NSUUID().uuidString, + serverUrl: serverUrl, + url: "", + contentType: "httpd/unix-directory", + directory: true, + session: session, + sceneIdentifier: nil) + metadata.status = NCGlobal.shared.metadataStatusWaitCreateFolder + metadata.sessionSelector = NCGlobal.shared.selectorUploadAutoUpload + metadata.sessionDate = Date() + metadatas.append(metadata) + } + } + + let metadatasFolder = getMetadatas(predicate: predicate) + let targetPath = serverUrlBase + "/" + fileNameBase + let metadata = metadatasFolder.first(where: { $0.serverUrl + "/" + $0.fileNameView == targetPath }) + createMetadata(serverUrl: serverUrlBase, fileName: fileNameBase, metadata: metadata) + + if useSubFolder { + let autoUploadServerUrlBase = self.getAccountAutoUploadServerUrlBase(session: session) + let autoUploadSubfolderGranularity = self.getAccountAutoUploadSubfolderGranularity() + let folders = Set(assets.map { self.utilityFileSystem.createGranularityPath(asset: $0) }).sorted() + + for folder in folders { + let componentsDate = folder.split(separator: "/") + let year = componentsDate[0] + let serverUrl = autoUploadServerUrlBase + let fileName = String(year) + let targetPath = serverUrl + "/" + fileName + let metadata = metadatasFolder.first(where: { $0.serverUrl + "/" + $0.fileNameView == targetPath }) + + createMetadata(serverUrl: serverUrl, fileName: fileName, metadata: metadata) + + if autoUploadSubfolderGranularity >= NCGlobal.shared.subfolderGranularityMonthly { + let month = componentsDate[1] + let serverUrl = autoUploadServerUrlBase + "/" + year + let fileName = String(month) + let targetPath = serverUrl + "/" + fileName + let metadata = metadatasFolder.first(where: { $0.serverUrl + "/" + $0.fileNameView == targetPath }) + + createMetadata(serverUrl: serverUrl, fileName: fileName, metadata: metadata) - let results = realm.objects(tableMetadata.self) - .filter(predicate) - .sorted(by: sortDescriptors) + if autoUploadSubfolderGranularity == NCGlobal.shared.subfolderGranularityDaily { + let day = componentsDate[2] + let serverUrl = autoUploadServerUrlBase + "/" + year + "/" + month + let fileName = String(day) + let targetPath = serverUrl + "/" + fileName + let metadata = metadatasFolder.first(where: { $0.serverUrl + "/" + $0.fileNameView == targetPath }) - let excludedIds = Set(tranfersSuccess.compactMap { $0.ocIdTransfer }) - let filtered = results.filter { !excludedIds.contains($0.ocIdTransfer) } + createMetadata(serverUrl: serverUrl, fileName: fileName, metadata: metadata) + } - return filtered.map { $0.detachedCopy() } - } ?? [] + } + return results + } else { + let results = realm.objects(tableMetadata.self).filter(predicate) + if freeze { + return results.freeze() + } + return results + } + } catch let error as NSError { + NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] Could not access database: \(error)") + } + return nil } - func getMetadatasInWaitingCountAsync() async -> Int { - await core.performRealmReadAsync { realm in - realm.objects(tableMetadata.self) - .filter("status IN %@", NCGlobal.shared.metadatasStatusInWaiting) - .count - } ?? 0 + func getCalculateCumulativeHash(for metadatas: [tableMetadata], account: String, serverUrl: String) -> String { + let concatenatedEtags = metadatas.map { $0.etag }.joined(separator: "-") + return sha256Hash(concatenatedEtags) } + + func getMediaMetadatas(predicate: NSPredicate) -> ThreadSafeArray? { - func metadataExistsAsync(predicate: NSPredicate) async -> Bool { - await core.performRealmReadAsync { realm in - realm.objects(tableMetadata.self) - .filter(predicate) - .first != nil - } ?? false + do { + let realm = try Realm() + let results = realm.objects(tableMetadata.self).filter(predicate).sorted(byKeyPath: "date", ascending: false) + return ThreadSafeArray(results.map { tableMetadata.init(value: $0) }) + } catch let error as NSError { + NextcloudKit.shared.nkCommonInstance.writeLog("Could not access database: \(error)") + } + + return nil + } + + func getMediaMetadatas(predicate: NSPredicate, sorted: String? = nil, ascending: Bool = false) -> ThreadSafeArray? { + + do { + let realm = try Realm() + if let sorted { + var results: [tableMetadata] = [] + switch NCKeychain().mediaSortDate { + case "date": + results = realm.objects(tableMetadata.self).filter(predicate).sorted { ($0.date as Date) > ($1.date as Date) } + case "creationDate": + results = realm.objects(tableMetadata.self).filter(predicate).sorted { ($0.creationDate as Date) > ($1.creationDate as Date) } + case "uploadDate": + results = realm.objects(tableMetadata.self).filter(predicate).sorted { ($0.uploadDate as Date) > ($1.uploadDate as Date) } + default: + let results = realm.objects(tableMetadata.self).filter(predicate) + return ThreadSafeArray(results.map { tableMetadata.init(value: $0) }) + } + return ThreadSafeArray(results.map { tableMetadata.init(value: $0) }) + } else { + let results = realm.objects(tableMetadata.self).filter(predicate) + return ThreadSafeArray(results.map { tableMetadata.init(value: $0) }) + } + } catch let error as NSError { + NextcloudKit.shared.nkCommonInstance.writeLog("Could not access database: \(error)") + } + return nil } func getAdvancedMetadatas(predicate: NSPredicate, page: Int = 0, limit: Int = 0, sorted: String, ascending: Bool) -> [tableMetadata] { @@ -1451,4 +1591,16 @@ extension NCManageDatabase { return metadatas } + + func getResultMetadataFromFileId(_ fileId: String?) -> tableMetadata? { + guard let fileId else { return nil } + + do { + let realm = try Realm() + return realm.objects(tableMetadata.self).filter("fileId == %@", fileId).first + } catch let error as NSError { + NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] Could not access database: \(error)") + } + return nil + } } diff --git a/iOSClient/Main/Create/NCCreateFormUploadDocuments.swift b/iOSClient/Main/Create/NCCreateFormUploadDocuments.swift index ec489d893a..c9aae962b1 100644 --- a/iOSClient/Main/Create/NCCreateFormUploadDocuments.swift +++ b/iOSClient/Main/Create/NCCreateFormUploadDocuments.swift @@ -84,7 +84,7 @@ import XLForm self.navigationItem.rightBarButtonItem = saveButton self.navigationItem.rightBarButtonItem?.isEnabled = false - // title + // title self.title = titleForm fileName = NCUtilityFileSystem().createFileNameDate("Text", ext: getFileExtension()) @@ -113,7 +113,7 @@ import XLForm row = XLFormRowDescriptor(tag: "ButtonDestinationFolder", rowType: "kNMCFolderCustomCellType", title: "") row.action.formSelector = #selector(changeDestinationFolder(_:)) row.cellConfig["folderImage.image"] = UIImage(named: "folder")!.imageColor(NCBrandColor.shared.customer) - row.cellConfig["photoLabel.textAlignment"] = NSTextAlignment.right.rawValue + row.cellConfig["photoLabel.textAlignment"] = NSTextAlignment.left.rawValue row.cellConfig["photoLabel.font"] = UIFont.systemFont(ofSize: 15.0) row.cellConfig["photoLabel.textColor"] = UIColor.label //photos if(self.fileNameFolder == "/"){ @@ -288,10 +288,14 @@ import XLForm self.present(alert, animated: true) return } + + // Ensure fileName is not nil or empty guard var fileNameForm: String = fileName, !fileNameForm.isEmpty else { return } - // Trim whitespaces after checks above + // Trim whitespaces and newlines fileNameForm = fileNameForm.trimmingCharacters(in: .whitespacesAndNewlines) + + fileName = FileAutoRenamer.rename(fileNameForm, account: session.account) let result = NextcloudKit.shared.nkCommonInstance.getInternalType(fileName: fileNameForm, mimeType: "", directory: false, account: session.account ) @@ -299,19 +303,25 @@ import XLForm fileNameForm = (fileNameForm as NSString).deletingPathExtension + "." + fileNameExtension } - if NCManageDatabase.shared.getMetadataConflict(account: session.account, serverUrl: serverUrl, fileNameView: String(describing: fileNameForm), nativeFormat: false) != nil { - - let metadataForUpload = NCManageDatabase.shared.createMetadata(fileName: String(describing: fileNameForm), fileNameView: String(describing: fileNameForm), ocId: UUID().uuidString, serverUrl: serverUrl, url: "", contentType: "", session: session, sceneIdentifier: self.appDelegate.sceneIdentifier) - - guard let conflict = UIStoryboard(name: "NCCreateFormUploadConflict", bundle: nil).instantiateInitialViewController() as? NCCreateFormUploadConflict else { return } - - conflict.textLabelDetailNewFile = NSLocalizedString("_now_", comment: "") - conflict.alwaysNewFileNameNumber = true - conflict.serverUrl = serverUrl - conflict.metadatasUploadInConflict = [metadataForUpload] - conflict.delegate = self - - self.present(conflict, animated: true, completion: nil) + // verify if already exists + if NCManageDatabase.shared.getMetadata(predicate: NSPredicate(format: "account == %@ AND serverUrl == %@ AND fileNameView == %@", session.account, self.serverUrl, fileNameForm)) != nil { + NCContentPresenter().showError(error: NKError(errorCode: 0, errorDescription: "_rename_already_exists_")) + return +// } +// +// if NCManageDatabase.shared.getMetadataConflict(account: session.account, serverUrl: serverUrl, fileNameView: String(describing: fileNameForm), nativeFormat: false) != nil { +// +// let metadataForUpload = NCManageDatabase.shared.createMetadata(fileName: String(describing: fileNameForm), fileNameView: String(describing: fileNameForm), ocId: UUID().uuidString, serverUrl: serverUrl, url: "", contentType: "", session: session, sceneIdentifier: self.appDelegate.sceneIdentifier) +// +// guard let conflict = UIStoryboard(name: "NCCreateFormUploadConflict", bundle: nil).instantiateInitialViewController() as? NCCreateFormUploadConflict else { return } +// +// conflict.textLabelDetailNewFile = NSLocalizedString("_now_", comment: "") +// conflict.alwaysNewFileNameNumber = true +// conflict.serverUrl = serverUrl +// conflict.metadatasUploadInConflict = [metadataForUpload] +// conflict.delegate = self +// +// self.present(conflict, animated: true, completion: nil) } else { @@ -348,7 +358,7 @@ import XLForm options = NKRequestOptions(customUserAgent: utility.getCustomUserAgentNCText()) } - NextcloudKit.shared.NCTextCreateFile(fileNamePath: fileNamePath, editorId: editorId, creatorId: creatorId, templateId: templateIdentifier, account: session.account, options: options) { account, url, _, error in + NextcloudKit.shared.textCreateFile(fileNamePath: fileNamePath, editorId: editorId, creatorId: creatorId, templateId: templateIdentifier, account: session.account, options: options) { account, url, _, error in guard error == .success, account == self.session.account, let url = url else { self.navigationItem.rightBarButtonItem?.isEnabled = true NCContentPresenter().showError(error: error) @@ -364,6 +374,7 @@ import XLForm self.dismiss(animated: true, completion: { let metadata = NCManageDatabase.shared.createMetadata(fileName: fileName, fileNameView: fileName, ocId: UUID, serverUrl: self.serverUrl, url: url, contentType: results.mimeType, session: self.session, sceneIdentifier: self.appDelegate.sceneIdentifier) if let viewController = self.appDelegate.activeViewController { +// NCViewer().view(viewController: viewController, metadata: metadata) NCViewer().view(viewController: viewController, metadata: metadata, metadatas: [metadata]) } }) @@ -384,6 +395,7 @@ import XLForm let metadata = NCManageDatabase.shared.createMetadata(fileName: createFileName, fileNameView: createFileName, ocId: UUID, serverUrl: self.serverUrl, url: url, contentType: "", session: self.session, sceneIdentifier: self.appDelegate.sceneIdentifier) AnalyticsHelper.shared.trackCreateFile(metadata: metadata) if let viewController = self.appDelegate.activeViewController { +// NCViewer().view(viewController: viewController, metadata: metadata) NCViewer().view(viewController: viewController, metadata: metadata, metadatas: [metadata]) } }) @@ -412,7 +424,7 @@ import XLForm options = NKRequestOptions(customUserAgent: utility.getCustomUserAgentNCText()) } - NextcloudKit.shared.NCTextGetListOfTemplates(account: session.account, options: options) { account, templates, _, error in + NextcloudKit.shared.textGetListOfTemplates(account: session.account, options: options) { account, templates, _, error in self.indicator.stopAnimating() diff --git a/iOSClient/NMC Custom Views/FolderPathCustomCell.swift b/iOSClient/NMC Custom Views/FolderPathCustomCell.swift index 901ce667ae..cb7553eda7 100644 --- a/iOSClient/NMC Custom Views/FolderPathCustomCell.swift +++ b/iOSClient/NMC Custom Views/FolderPathCustomCell.swift @@ -7,6 +7,7 @@ // import UIKit +import XLForm class FolderPathCustomCell: XLFormButtonCell{ diff --git a/iOSClient/NMC Custom Views/FolderPathCustomCell.xib b/iOSClient/NMC Custom Views/FolderPathCustomCell.xib index a231ae7c72..caca063abf 100644 --- a/iOSClient/NMC Custom Views/FolderPathCustomCell.xib +++ b/iOSClient/NMC Custom Views/FolderPathCustomCell.xib @@ -1,9 +1,9 @@ - + - + @@ -18,13 +18,13 @@ - + @@ -55,6 +55,7 @@ + @@ -68,7 +69,7 @@ - + diff --git a/iOSClient/NMC Custom Views/NCCreateDocumentCustomTextField.swift b/iOSClient/NMC Custom Views/NCCreateDocumentCustomTextField.swift index 0242fa0f7a..3983e413a6 100644 --- a/iOSClient/NMC Custom Views/NCCreateDocumentCustomTextField.swift +++ b/iOSClient/NMC Custom Views/NCCreateDocumentCustomTextField.swift @@ -7,6 +7,7 @@ // import UIKit +import XLForm class NCCreateDocumentCustomTextField: XLFormBaseCell,UITextFieldDelegate { From ce85acca0de85a3ccc31786c37870858f26d10be Mon Sep 17 00:00:00 2001 From: harshada-15-tsys Date: Tue, 16 Dec 2025 14:51:10 +0530 Subject: [PATCH 4/4] NMC 1933 - Collabora customisation changes --- iOSClient/BrowserWeb/NCBrowserWeb.swift | 27 +- .../Data/NCManageDatabase+Metadata.swift | 1919 ++++++++--------- .../Create/NCCreateFormUploadDocuments.swift | 226 +- 3 files changed, 971 insertions(+), 1201 deletions(-) diff --git a/iOSClient/BrowserWeb/NCBrowserWeb.swift b/iOSClient/BrowserWeb/NCBrowserWeb.swift index 10ee6d71ca..206197c76c 100644 --- a/iOSClient/BrowserWeb/NCBrowserWeb.swift +++ b/iOSClient/BrowserWeb/NCBrowserWeb.swift @@ -1,25 +1,6 @@ -// -// NCBrowserWeb.swift -// Nextcloud -// -// Created by Marino Faggiana on 22/08/2019. -// Copyright (c) 2019 Marino Faggiana. All rights reserved. -// -// Author Marino Faggiana -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . -// +// SPDX-FileCopyrightText: Nextcloud GmbH +// SPDX-FileCopyrightText: 2019 Marino Faggiana +// SPDX-License-Identifier: GPL-3.0-or-later import UIKit @preconcurrency import WebKit @@ -55,7 +36,7 @@ class NCBrowserWeb: UIViewController { buttonExit.isHidden = true } else { self.view.bringSubviewToFront(buttonExit) - let image = NCUtility().loadImage(named: "xmark", colors: [NCBrandColor.shared.customer]) + let image = NCUtility().loadImage(named: "xmark", colors: [.systemBlue]) buttonExit.setImage(image, for: .normal) } diff --git a/iOSClient/Data/NCManageDatabase+Metadata.swift b/iOSClient/Data/NCManageDatabase+Metadata.swift index 5c82a8767a..39ab3d0da6 100644 --- a/iOSClient/Data/NCManageDatabase+Metadata.swift +++ b/iOSClient/Data/NCManageDatabase+Metadata.swift @@ -1,30 +1,12 @@ -// -// NCManageDatabase+Metadata.swift -// Nextcloud -// -// Created by Henrik Storch on 30.11.21. -// Copyright © 2021 Marino Faggiana. All rights reserved. -// -// Author Marino Faggiana -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . -// +// SPDX-FileCopyrightText: Nextcloud GmbH +// SPDX-FileCopyrightText: 2021 Marino Faggiana +// SPDX-License-Identifier: GPL-3.0-or-later import Foundation import UIKit import RealmSwift import NextcloudKit +import Photos class tableMetadata: Object { override func isEqual(_ object: Any?) -> Bool { @@ -51,8 +33,7 @@ class tableMetadata: Object { self.altitude == object.altitude, self.status == object.status, Array(self.tags).elementsEqual(Array(object.tags)), - Array(self.shareType).elementsEqual(Array(object.shareType)), - Array(self.sharePermissionsCloudMesh).elementsEqual(Array(object.sharePermissionsCloudMesh)) { + Array(self.shareType).elementsEqual(Array(object.shareType)) { return true } else { return false @@ -75,7 +56,6 @@ class tableMetadata: Object { @objc dynamic var e2eEncrypted: Bool = false @objc dynamic var edited: Bool = false @objc dynamic var etag = "" - @objc dynamic var etagResource = "" let exifPhotos = List() @objc dynamic var favorite: Bool = false @objc dynamic var fileId = "" @@ -102,6 +82,7 @@ class tableMetadata: Object { @objc public var lockOwnerDisplayName = "" @objc public var lockTime: Date? @objc public var lockTimeOut: Date? + @objc dynamic var mediaSearch: Bool = false @objc dynamic var path = "" @objc dynamic var permissions = "" @objc dynamic var placePhotos: String? @@ -111,15 +92,15 @@ class tableMetadata: Object { @objc dynamic var richWorkspace: String? @objc dynamic var sceneIdentifier: String? @objc dynamic var serverUrl = "" - @objc dynamic var serveUrlFileName = "" - @objc dynamic var serverUrlTo = "" + @objc dynamic var serverUrlFileName = "" + @objc dynamic var destination = "" @objc dynamic var session = "" @objc dynamic var sessionDate: Date? @objc dynamic var sessionError = "" @objc dynamic var sessionSelector = "" @objc dynamic var sessionTaskIdentifier: Int = 0 + /// The integer for sharing permissions. @objc dynamic var sharePermissionsCollaborationServices: Int = 0 - let sharePermissionsCloudMesh = List() let shareType = List() @objc dynamic var size: Int64 = 0 @objc dynamic var status: Int = 0 @@ -142,6 +123,8 @@ class tableMetadata: Object { @objc dynamic var errorCode: Int = 0 @objc dynamic var nativeFormat: Bool = false @objc dynamic var autoUploadServerUrlBase: String? + @objc dynamic var typeIdentifier: String = "" + @objc dynamic var progress: Double = 0 override static func primaryKey() -> String { return "ocId" @@ -158,6 +141,9 @@ extension tableMetadata { } var isRenameable: Bool { + if !NCMetadataPermissions.canRename(self) { + return false + } if lock { return false } @@ -166,115 +152,95 @@ extension tableMetadata { } return true } - + var isPrintable: Bool { if isDocumentViewableOnly { return false } - if ["application/pdf", "com.adobe.pdf"].contains(contentType) || contentType.hasPrefix("text/") || classFile == NKCommon.TypeClassFile.image.rawValue { + if ["application/pdf", "com.adobe.pdf"].contains(contentType) || contentType.hasPrefix("text/") || classFile == NKTypeClassFile.image.rawValue { return true } return false } var isSavebleInCameraRoll: Bool { - return (classFile == NKCommon.TypeClassFile.image.rawValue && contentType != "image/svg+xml") || classFile == NKCommon.TypeClassFile.video.rawValue + return (classFile == NKTypeClassFile.image.rawValue && contentType != "image/svg+xml") || classFile == NKTypeClassFile.video.rawValue } - var isDocumentViewableOnly: Bool { - sharePermissionsCollaborationServices == NCPermissions().permissionReadShare && classFile == NKCommon.TypeClassFile.document.rawValue + sharePermissionsCollaborationServices == NCPermissions().permissionReadShare && classFile == NKTypeClassFile.document.rawValue } - + var isAudioOrVideo: Bool { - return classFile == NKCommon.TypeClassFile.audio.rawValue || classFile == NKCommon.TypeClassFile.video.rawValue + return classFile == NKTypeClassFile.audio.rawValue || classFile == NKTypeClassFile.video.rawValue } var isImageOrVideo: Bool { - return classFile == NKCommon.TypeClassFile.image.rawValue || classFile == NKCommon.TypeClassFile.video.rawValue + return classFile == NKTypeClassFile.image.rawValue || classFile == NKTypeClassFile.video.rawValue } var isVideo: Bool { - return classFile == NKCommon.TypeClassFile.video.rawValue + return classFile == NKTypeClassFile.video.rawValue } var isAudio: Bool { - return classFile == NKCommon.TypeClassFile.audio.rawValue + return classFile == NKTypeClassFile.audio.rawValue } var isImage: Bool { - return classFile == NKCommon.TypeClassFile.image.rawValue + return classFile == NKTypeClassFile.image.rawValue } var isSavebleAsImage: Bool { - classFile == NKCommon.TypeClassFile.image.rawValue && contentType != "image/svg+xml" + classFile == NKTypeClassFile.image.rawValue && contentType != "image/svg+xml" } var isCopyableInPasteboard: Bool { - !isDocumentViewableOnly && !directory + !directory } var isCopyableMovable: Bool { - !isDocumentViewableOnly && !isDirectoryE2EE && !e2eEncrypted + !isDirectoryE2EE && !e2eEncrypted } var isModifiableWithQuickLook: Bool { - if directory || isDocumentViewableOnly || isDirectoryE2EE { + if directory || isDirectoryE2EE { return false } return isPDF || isImage } + var isCreatable: Bool { + if isDirectory { + return NCMetadataPermissions.canCreateFolder(self) + } else { + return NCMetadataPermissions.canCreateFile(self) + } + } + var isDeletable: Bool { - if !isDirectoryE2EE && e2eEncrypted { + if (!isDirectoryE2EE && e2eEncrypted) || !NCMetadataPermissions.canDelete(self) { return false } return true } var canSetAsAvailableOffline: Bool { -// return session.isEmpty && !isDocumentViewableOnly //!isDirectoryE2EE && !e2eEncrypted return session.isEmpty && !isDirectoryE2EE && !e2eEncrypted } var canShare: Bool { - return session.isEmpty && !isDocumentViewableOnly && !directory && !NCBrandOptions.shared.disable_openin_file + return session.isEmpty && !directory && !NCBrandOptions.shared.disable_openin_file } var canSetDirectoryAsE2EE: Bool { - return directory && size == 0 && !e2eEncrypted && NCKeychain().isEndToEndEnabled(account: account) + return directory && size == 0 && !e2eEncrypted && NCPreferences().isEndToEndEnabled(account: account) } var canUnsetDirectoryAsE2EE: Bool { - return !isDirectoryE2EE && directory && size == 0 && e2eEncrypted && NCKeychain().isEndToEndEnabled(account: account) - } - - var canOpenExternalEditor: Bool { - if isDocumentViewableOnly { - return false - } - let utility = NCUtility() - let editors = utility.editorsDirectEditing(account: account, contentType: contentType) - let isRichDocument = utility.isTypeFileRichDocument(self) - return classFile == NKCommon.TypeClassFile.document.rawValue && editors.contains(NCGlobal.shared.editorText) && ((editors.contains(NCGlobal.shared.editorOnlyoffice) || isRichDocument)) - } - - var isWaitingTransfer: Bool { - status == NCGlobal.shared.metadataStatusWaitDownload || status == NCGlobal.shared.metadataStatusWaitUpload || status == NCGlobal.shared.metadataStatusUploadError + return !isDirectoryE2EE && directory && size == 0 && e2eEncrypted && NCPreferences().isEndToEndEnabled(account: account) } - var isInTransfer: Bool { - status == NCGlobal.shared.metadataStatusDownloading || status == NCGlobal.shared.metadataStatusUploading - } - - var isTransferInForeground: Bool { - (status > 0 && (chunk > 0 || e2eEncrypted)) - } - - var isDownloadUpload: Bool { - status == NCGlobal.shared.metadataStatusDownloading || status == NCGlobal.shared.metadataStatusUploading - } - var isDownload: Bool { status == NCGlobal.shared.metadataStatusWaitDownload || status == NCGlobal.shared.metadataStatusDownloading } @@ -288,12 +254,7 @@ extension tableMetadata { } @objc var isDirectoryE2EE: Bool { - let session = NCSession.Session(account: account, urlBase: urlBase, user: user, userId: userId) - return NCUtilityFileSystem().isDirectoryE2EE(session: session, serverUrl: serverUrl) - } - - var isDirectoryE2EETop: Bool { - NCUtilityFileSystem().isDirectoryE2EETop(account: account, serverUrl: serverUrl) + return NCUtilityFileSystem().isDirectoryE2EE(serverUrl: serverUrl, urlBase: urlBase, userId: userId, account: account) } var isLivePhoto: Bool { @@ -309,21 +270,27 @@ extension tableMetadata { } var hasPreviewBorder: Bool { - !isImage && !isAudioOrVideo && hasPreview && NCUtilityFileSystem().fileProviderStorageImageExists(ocId, etag: etag, ext: NCGlobal.shared.previewExt1024) + !isImage && !isAudioOrVideo && hasPreview && NCUtilityFileSystem().fileProviderStorageImageExists(ocId, etag: etag, ext: NCGlobal.shared.previewExt1024, userId: userId, urlBase: urlBase) } var isAvailableEditorView: Bool { guard !isPDF, - classFile == NKCommon.TypeClassFile.document.rawValue, - NextcloudKit.shared.isNetworkReachable() else { return false } + classFile == NKTypeClassFile.document.rawValue, + NextcloudKit.shared.isNetworkReachable() else { + return false + } let utility = NCUtility() - let directEditingEditors = utility.editorsDirectEditing(account: account, contentType: contentType) + let directEditingEditors = utility.editorsDirectEditing(account: account, contentType: contentType).map { $0.lowercased() } let richDocumentEditor = utility.isTypeFileRichDocument(self) + let capabilities = NCNetworking.shared.capabilities[account] - if NCCapabilities.shared.getCapabilities(account: account).capabilityRichDocumentsEnabled && richDocumentEditor && directEditingEditors.isEmpty { + if let capabilities, + capabilities.richDocumentsEnabled, + richDocumentEditor, + directEditingEditors.isEmpty { // RichDocument: Collabora return true - } else if directEditingEditors.contains(NCGlobal.shared.editorText) || directEditingEditors.contains(NCGlobal.shared.editorOnlyoffice) { + } else if directEditingEditors.contains("nextcloud text") || directEditingEditors.contains("onlyoffice") { // DirectEditing: Nextcloud Text - OnlyOffice return true } @@ -331,8 +298,9 @@ extension tableMetadata { } var isAvailableRichDocumentEditorView: Bool { - guard classFile == NKCommon.TypeClassFile.document.rawValue, - NCCapabilities.shared.getCapabilities(account: account).capabilityRichDocumentsEnabled, + guard let capabilities = NCNetworking.shared.capabilities[account], + classFile == NKTypeClassFile.document.rawValue, + capabilities.richDocumentsEnabled, NextcloudKit.shared.isNetworkReachable() else { return false } if NCUtility().isTypeFileRichDocument(self) { @@ -342,10 +310,12 @@ extension tableMetadata { } var isAvailableDirectEditingEditorView: Bool { - guard (classFile == NKCommon.TypeClassFile.document.rawValue) && NextcloudKit.shared.isNetworkReachable() else { return false } - let editors = NCUtility().editorsDirectEditing(account: account, contentType: contentType) + guard (classFile == NKTypeClassFile.document.rawValue) && NextcloudKit.shared.isNetworkReachable() else { + return false + } + let editors = NCUtility().editorsDirectEditing(account: account, contentType: contentType).map { $0.lowercased() } - if editors.contains(NCGlobal.shared.editorText) || editors.contains(NCGlobal.shared.editorOnlyoffice) { + if editors.contains("nextcloud text") || editors.contains("onlyoffice") { return true } return false @@ -362,1026 +332,1003 @@ extension tableMetadata { // Return if is sharable func isSharable() -> Bool { - if !NCCapabilities.shared.getCapabilities(account: account).capabilityFileSharingApiEnabled || (NCCapabilities.shared.getCapabilities(account: account).capabilityE2EEEnabled && isDirectoryE2EE), !e2eEncrypted { + guard let capabilities = NCNetworking.shared.capabilities[account] else { + return false + } + if !capabilities.fileSharingApiEnabled || (capabilities.e2EEEnabled && isDirectoryE2EE), !e2eEncrypted { return false } return !e2eEncrypted } + + /// Returns a detached (unmanaged) deep copy of the current `tableMetadata` object. + /// + /// - Note: The Realm `List` properties containing primitive types (e.g., `tags`, `shareType`) are copied automatically + /// by the Realm initializer `init(value:)`. For `List` containing Realm objects (e.g., `exifPhotos`), this method + /// creates new instances to ensure the copy is fully detached and safe to use outside of a Realm context. + /// + /// - Returns: A new `tableMetadata` instance fully detached from Realm. + func detachedCopy() -> tableMetadata { + // Use Realm's built-in copy constructor for primitive properties and List of primitives + let detached = tableMetadata(value: self) + + // Deep copy of List of Realm objects (exifPhotos) + detached.exifPhotos.removeAll() + detached.exifPhotos.append(objectsIn: self.exifPhotos.map { NCKeyValue(value: $0) }) + + return detached + } } extension NCManageDatabase { - func convertFileToMetadata(_ file: NKFile, isDirectoryE2EE: Bool) -> tableMetadata { - let metadata = tableMetadata() - - metadata.account = file.account - metadata.checksums = file.checksums - metadata.commentsUnread = file.commentsUnread - metadata.contentType = file.contentType - if let date = file.creationDate { - metadata.creationDate = date as NSDate - } else { - metadata.creationDate = file.date as NSDate - } - metadata.dataFingerprint = file.dataFingerprint - metadata.date = file.date as NSDate - if let datePhotosOriginal = file.datePhotosOriginal { - metadata.datePhotosOriginal = datePhotosOriginal as NSDate - } else { - metadata.datePhotosOriginal = metadata.date - } - metadata.directory = file.directory - metadata.downloadURL = file.downloadURL - metadata.e2eEncrypted = file.e2eEncrypted - metadata.etag = file.etag - for dict in file.exifPhotos { - for (key, value) in dict { - let keyValue = NCKeyValue() - keyValue.key = key - keyValue.value = value - metadata.exifPhotos.append(keyValue) - } + func isMetadataShareOrMounted(metadata: tableMetadata, metadataFolder: tableMetadata?) -> Bool { + var isShare = false + var isMounted = false + + if metadataFolder != nil { + isShare = metadata.permissions.contains(NCMetadataPermissions.permissionShared) && !metadataFolder!.permissions.contains(NCMetadataPermissions.permissionShared) + isMounted = metadata.permissions.contains(NCMetadataPermissions.permissionMounted) && !metadataFolder!.permissions.contains(NCMetadataPermissions.permissionMounted) + } else if let directory = getTableDirectory(predicate: NSPredicate(format: "account == %@ AND serverUrl == %@", metadata.account, metadata.serverUrl)) { + isShare = metadata.permissions.contains(NCMetadataPermissions.permissionShared) && !directory.permissions.contains(NCMetadataPermissions.permissionShared) + isMounted = metadata.permissions.contains(NCMetadataPermissions.permissionMounted) && !directory.permissions.contains(NCMetadataPermissions.permissionMounted) } - metadata.favorite = file.favorite - metadata.fileId = file.fileId - metadata.fileName = file.fileName - metadata.fileNameView = file.fileName - metadata.hasPreview = file.hasPreview - metadata.hidden = file.hidden - switch (file.fileName as NSString).pathExtension { - case "odg": - metadata.iconName = "diagram" - case "csv", "xlsm" : - metadata.iconName = "file_xls" - default: - metadata.iconName = file.iconName - } - metadata.mountType = file.mountType - metadata.name = file.name - metadata.note = file.note - metadata.ocId = file.ocId - metadata.ocIdTransfer = file.ocId - metadata.ownerId = file.ownerId - metadata.ownerDisplayName = file.ownerDisplayName - metadata.lock = file.lock - metadata.lockOwner = file.lockOwner - metadata.lockOwnerEditor = file.lockOwnerEditor - metadata.lockOwnerType = file.lockOwnerType - metadata.lockOwnerDisplayName = file.lockOwnerDisplayName - metadata.lockTime = file.lockTime - metadata.lockTimeOut = file.lockTimeOut - metadata.path = file.path - metadata.permissions = file.permissions - metadata.placePhotos = file.placePhotos - metadata.quotaUsedBytes = file.quotaUsedBytes - metadata.quotaAvailableBytes = file.quotaAvailableBytes - metadata.richWorkspace = file.richWorkspace - metadata.resourceType = file.resourceType - metadata.serverUrl = file.serverUrl - metadata.serveUrlFileName = file.serverUrl + "/" + file.fileName - metadata.sharePermissionsCollaborationServices = file.sharePermissionsCollaborationServices - for element in file.sharePermissionsCloudMesh { - metadata.sharePermissionsCloudMesh.append(element) - } - for element in file.shareType { - metadata.shareType.append(element) - } - for element in file.tags { - metadata.tags.append(element) - } - metadata.size = file.size - metadata.classFile = file.classFile - // iOS 12.0,* don't detect UTI text/markdown, text/x-markdown - if (metadata.contentType == "text/markdown" || metadata.contentType == "text/x-markdown") && metadata.classFile == NKCommon.TypeClassFile.unknow.rawValue { - metadata.classFile = NKCommon.TypeClassFile.document.rawValue - } - if let date = file.uploadDate { - metadata.uploadDate = date as NSDate + + if isShare || isMounted { + return true } else { - metadata.uploadDate = file.date as NSDate - } - metadata.urlBase = file.urlBase - metadata.user = file.user - metadata.userId = file.userId - metadata.latitude = file.latitude - metadata.longitude = file.longitude - metadata.altitude = file.altitude - metadata.height = Int(file.height) - metadata.width = Int(file.width) - metadata.livePhotoFile = file.livePhotoFile - metadata.isFlaggedAsLivePhotoByServer = file.isFlaggedAsLivePhotoByServer - - // E2EE find the fileName for fileNameView - if isDirectoryE2EE || file.e2eEncrypted { - if let tableE2eEncryption = getE2eEncryption(predicate: NSPredicate(format: "account == %@ AND serverUrl == %@ AND fileNameIdentifier == %@", file.account, file.serverUrl, file.fileName)) { - metadata.fileNameView = tableE2eEncryption.fileName - let results = NextcloudKit.shared.nkCommonInstance.getInternalType(fileName: metadata.fileNameView, mimeType: file.contentType, directory: file.directory, account: file.account) - metadata.contentType = results.mimeType - metadata.iconName = results.iconName - metadata.classFile = results.classFile - } + return false } - return metadata } - func convertFilesToMetadatas(_ files: [NKFile], useFirstAsMetadataFolder: Bool, completion: @escaping (_ metadataFolder: tableMetadata, _ metadatas: [tableMetadata]) -> Void) { - var counter: Int = 0 - var isDirectoryE2EE: Bool = false - let listServerUrl = ThreadSafeDictionary() - var metadataFolder = tableMetadata() - var metadatas: [tableMetadata] = [] - - for file in files { - if let key = listServerUrl[file.serverUrl] { - isDirectoryE2EE = key - } else { - isDirectoryE2EE = NCUtilityFileSystem().isDirectoryE2EE(file: file) - listServerUrl[file.serverUrl] = isDirectoryE2EE - } + // MARK: - Realm Write - let metadata = convertFileToMetadata(file, isDirectoryE2EE: isDirectoryE2EE) + func addMetadataIfNeededAsync(_ metadata: tableMetadata, sync: Bool = true) { + let detached = metadata.detachedCopy() - if counter == 0 && useFirstAsMetadataFolder { - metadataFolder = tableMetadata(value: metadata) - } else { - metadatas.append(metadata) + performRealmWrite(sync: sync) { realm in + if realm.object(ofType: tableMetadata.self, forPrimaryKey: metadata.ocId) == nil { + realm.add(detached) } - - counter += 1 } - completion(metadataFolder, metadatas) } - - func convertFilesToMetadatas(_ files: [NKFile], useMetadataFolder: Bool, completion: @escaping (_ metadataFolder: tableMetadata, _ metadatasFolder: [tableMetadata], _ metadatas: [tableMetadata]) -> Void) { - var counter: Int = 0 - var isDirectoryE2EE: Bool = false - let listServerUrl = ThreadSafeDictionary() + func addAndReturnMetadata(_ metadata: tableMetadata) -> tableMetadata? { + let detached = metadata.detachedCopy() - var metadataFolder = tableMetadata() - var metadataFolders: [tableMetadata] = [] - var metadatas: [tableMetadata] = [] + performRealmWrite { realm in + realm.add(detached, update: .all) + } - for file in files { + return performRealmRead { realm in + realm.objects(tableMetadata.self) + .filter("ocId == %@", metadata.ocId) + .first + .map { $0.detachedCopy() } + } + } - if let key = listServerUrl[file.serverUrl] { - isDirectoryE2EE = key - } else { - isDirectoryE2EE = NCUtilityFileSystem().isDirectoryE2EE(file: file) - listServerUrl[file.serverUrl] = isDirectoryE2EE - } + func addAndReturnMetadataAsync(_ metadata: tableMetadata) async -> tableMetadata? { + let detached = metadata.detachedCopy() - let metadata = convertFileToMetadata(file, isDirectoryE2EE: isDirectoryE2EE) + await performRealmWriteAsync { realm in + realm.add(detached, update: .all) + } - if counter == 0 && useMetadataFolder { - metadataFolder = tableMetadata.init(value: metadata) - } else { - metadatas.append(metadata) - if metadata.directory { - metadataFolders.append(metadata) - } - } + return await performRealmReadAsync { realm in + realm.objects(tableMetadata.self) + .filter("ocId == %@", metadata.ocId) + .first? + .detachedCopy() + } + } - counter += 1 - } - - completion(metadataFolder, metadataFolders, metadatas) - } - -// func convertFilesToMetadatas(_ files: [NKFile], useMetadataFolder: Bool, completion: @escaping (_ metadataFolder: tableMetadata, _ metadatasFolder: [tableMetadata], _ metadatas: [tableMetadata]) -> Void) { -// -// var counter: Int = 0 -// var isDirectoryE2EE: Bool = false -// let listServerUrl = ThreadSafeDictionary() -// -// var metadataFolder = tableMetadata() -// var metadataFolders: [tableMetadata] = [] -// var metadatas: [tableMetadata] = [] -// -// for file in files { -// -// if let key = listServerUrl[file.serverUrl] { -// isDirectoryE2EE = key -// } else { -// isDirectoryE2EE = NCUtilityFileSystem().isDirectoryE2EE(file: file) -// listServerUrl[file.serverUrl] = isDirectoryE2EE -// } -// -// let metadata = convertFileToMetadata(file, isDirectoryE2EE: isDirectoryE2EE) -// -// if counter == 0 && useMetadataFolder { -// metadataFolder = tableMetadata.init(value: metadata) -// } else { -// metadatas.append(metadata) -// if metadata.directory { -// metadataFolders.append(metadata) -// } -// } -// -// counter += 1 -// } -// -// completion(metadataFolder, metadataFolders, metadatas) -// } - - func getMetadataDirectoryFrom(files: [NKFile]) -> tableMetadata? { - guard let file = files.first else { return nil } - let isDirectoryE2EE = NCUtilityFileSystem().isDirectoryE2EE(file: file) - let metadata = convertFileToMetadata(file, isDirectoryE2EE: isDirectoryE2EE) + func addMetadata(_ metadata: tableMetadata, sync: Bool = true) { + let detached = metadata.detachedCopy() - return metadata + performRealmWrite(sync: sync) { realm in + realm.add(detached, update: .all) + } } - func convertFilesToMetadatas(_ files: [NKFile], useFirstAsMetadataFolder: Bool) async -> (metadataFolder: tableMetadata, metadatas: [tableMetadata]) { - await withUnsafeContinuation({ continuation in - convertFilesToMetadatas(files, useFirstAsMetadataFolder: useFirstAsMetadataFolder) { metadataFolder, metadatas in - continuation.resume(returning: (metadataFolder, metadatas)) - } - }) + func addMetadataAsync(_ metadata: tableMetadata) async { + let detached = metadata.detachedCopy() + + await performRealmWriteAsync { realm in + realm.add(detached, update: .all) + } } - func createMetadata(fileName: String, fileNameView: String, ocId: String, serverUrl: String, url: String, contentType: String, isUrl: Bool = false, name: String = NCGlobal.shared.appName, subline: String? = nil, iconName: String? = nil, iconUrl: String? = nil, directory: Bool = false, session: NCSession.Session, sceneIdentifier: String?) -> tableMetadata { - let metadata = tableMetadata() + func addMetadatas(_ metadatas: [tableMetadata], sync: Bool = true) { + let detached = metadatas.map { $0.detachedCopy() } - if isUrl { - metadata.contentType = "text/uri-list" - if let iconName = iconName { - metadata.iconName = iconName - } else { - metadata.iconName = NKCommon.TypeClassFile.url.rawValue - } - metadata.classFile = NKCommon.TypeClassFile.url.rawValue - } else { - let (mimeType, classFile, iconName, _, _, _) = NextcloudKit.shared.nkCommonInstance.getInternalType(fileName: fileName, mimeType: contentType, directory: directory, account: session.account) - metadata.contentType = mimeType - metadata.iconName = iconName - metadata.classFile = classFile - // iOS 12.0,* don't detect UTI text/markdown, text/x-markdown - if classFile == NKCommon.TypeClassFile.unknow.rawValue && (mimeType == "text/x-markdown" || mimeType == "text/markdown") { - metadata.iconName = NKCommon.TypeIconFile.txt.rawValue - metadata.classFile = NKCommon.TypeClassFile.document.rawValue - } + performRealmWrite(sync: sync) { realm in + realm.add(detached, update: .all) } - if let iconUrl = iconUrl { - metadata.iconUrl = iconUrl - } - - let fileName = fileName.trimmingCharacters(in: .whitespacesAndNewlines) - - metadata.account = session.account - metadata.creationDate = Date() as NSDate - metadata.date = Date() as NSDate - metadata.directory = directory - metadata.hasPreview = true - metadata.etag = ocId - metadata.fileName = fileName - metadata.fileNameView = fileName - metadata.name = name - metadata.ocId = ocId - metadata.ocIdTransfer = ocId - metadata.permissions = "RGDNVW" - metadata.serverUrl = serverUrl - metadata.serveUrlFileName = serverUrl + "/" + fileName - metadata.subline = subline - metadata.uploadDate = Date() as NSDate - metadata.url = url - metadata.urlBase = session.urlBase - metadata.user = session.user - metadata.userId = session.userId - metadata.sceneIdentifier = sceneIdentifier - metadata.nativeFormat = !NCKeychain().formatCompatibility - - if !metadata.urlBase.isEmpty, metadata.serverUrl.hasPrefix(metadata.urlBase) { - metadata.path = String(metadata.serverUrl.dropFirst(metadata.urlBase.count)) + "/" - } - return metadata } - func isMetadataShareOrMounted(metadata: tableMetadata, metadataFolder: tableMetadata?) -> Bool { - let permissions = NCPermissions() - var isShare = false - var isMounted = false + func addMetadatasAsync(_ metadatas: [tableMetadata]) async { + let detached = metadatas.map { $0.detachedCopy() } - if metadataFolder != nil { - isShare = metadata.permissions.contains(permissions.permissionShared) && !metadataFolder!.permissions.contains(permissions.permissionShared) - isMounted = metadata.permissions.contains(permissions.permissionMounted) && !metadataFolder!.permissions.contains(permissions.permissionMounted) - } else if let directory = getTableDirectory(predicate: NSPredicate(format: "account == %@ AND serverUrl == %@", metadata.account, metadata.serverUrl)) { - isShare = metadata.permissions.contains(permissions.permissionShared) && !directory.permissions.contains(permissions.permissionShared) - isMounted = metadata.permissions.contains(permissions.permissionMounted) && !directory.permissions.contains(permissions.permissionMounted) + await performRealmWriteAsync { realm in + realm.add(detached, update: .all) } + } - if isShare || isMounted { - return true - } else { - return false + func deleteMetadataAsync(predicate: NSPredicate) async { + await performRealmWriteAsync { realm in + let result = realm.objects(tableMetadata.self) + .filter(predicate) + realm.delete(result) } } - // MARK: - Set + func deleteMetadataAsync(id: String?) async { + guard let id else { return } - @discardableResult - func addMetadata(_ metadata: tableMetadata) -> tableMetadata { - do { - let realm = try Realm() - try realm.write { - return tableMetadata(value: realm.create(tableMetadata.self, value: metadata, update: .all)) - } - } catch let error { - NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] Could not write to database: \(error)") + await performRealmWriteAsync { realm in + let result = realm.objects(tableMetadata.self) + .filter("ocId == %@ OR fileId == %@", id, id) + realm.delete(result) } - - return tableMetadata(value: metadata) } - func addMetadatas(_ metadatas: [tableMetadata]) { - do { - let realm = try Realm() - try realm.write { - for metadata in metadatas { - realm.create(tableMetadata.self, value: metadata, update: .all) - } - } - } catch let error { - NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] Could not write to database: \(error)") + func deleteMetadataOcIds(_ ocIds: [String], sync: Bool = true) { + performRealmWrite(sync: sync) { realm in + let result = realm.objects(tableMetadata.self) + .filter("ocId IN %@", ocIds) + realm.delete(result) } } - func deleteMetadata(predicate: NSPredicate) { - do { - let realm = try Realm() - try realm.write { - let results = realm.objects(tableMetadata.self).filter(predicate) - realm.delete(results) - } - } catch let error { - NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] Could not write to database: \(error)") + func replaceMetadataAsync(id: String, metadata: tableMetadata) async { + let detached = metadata.detachedCopy() + + await performRealmWriteAsync { realm in + let result = realm.objects(tableMetadata.self) + .filter("ocId == %@ OR ocIdTransfer == %@", id, id) + realm.delete(result) + realm.add(detached, update: .all) } } - func deleteMetadataOcId(_ ocId: String?) { - guard let ocId else { return } - - do { - let realm = try Realm() - try realm.write { - let results = realm.objects(tableMetadata.self).filter("ocId == %@", ocId) - realm.delete(results) - } - } catch let error as NSError { - NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] Could not access database: \(error)") + // Asynchronously deletes an array of `tableMetadata` entries from the Realm database. + /// - Parameter metadatas: The `tableMetadata` objects to be deleted. + func deleteMetadatasAsync(_ metadatas: [tableMetadata]) async { + guard !metadatas.isEmpty else { + return } - } + let detached = metadatas.map { $0.detachedCopy() } - func deleteMetadataOcIds(_ ocIds: [String]) { - do { - let realm = try Realm() - try realm.write { - let results = realm.objects(tableMetadata.self).filter("ocId IN %@", ocIds) - realm.delete(results) + await performRealmWriteAsync { realm in + for detached in detached { + if let managed = realm.object(ofType: tableMetadata.self, forPrimaryKey: detached.ocId) { + realm.delete(managed) + } } - } catch let error as NSError { - NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] Could not access database: \(error)") } } - func deleteMetadatas(_ metadatas: [tableMetadata]) { - do { - let realm = try Realm() - try realm.write { - realm.delete(metadatas) + func renameMetadata(fileNameNew: String, ocId: String, status: Int = NCGlobal.shared.metadataStatusNormal) async { + await performRealmWriteAsync { realm in + guard let metadata = realm.objects(tableMetadata.self) + .filter("ocId == %@", ocId) + .first else { + return } - } catch let error { - NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] Could not write to database: \(error)") - } - } - func renameMetadata(fileNameNew: String, ocId: String, status: Int = NCGlobal.shared.metadataStatusNormal) { - do { - let realm = try Realm() - try realm.write { - if let result = realm.objects(tableMetadata.self).filter("ocId == %@", ocId).first { - let fileNameView = result.fileNameView - let fileIdMOV = result.livePhotoFile - let directoryServerUrl = self.utilityFileSystem.stringAppendServerUrl(result.serverUrl, addFileName: fileNameView) - let resultsType = NextcloudKit.shared.nkCommonInstance.getInternalType(fileName: fileNameNew, mimeType: "", directory: result.directory, account: result.account) - - result.fileName = fileNameNew - result.fileNameView = fileNameNew - result.iconName = resultsType.iconName - result.contentType = resultsType.mimeType - result.classFile = resultsType.classFile - result.status = status - - if status == NCGlobal.shared.metadataStatusNormal { - result.sessionDate = nil - } else { - result.sessionDate = Date() - } - - if result.directory, - let resultDirectory = realm.objects(tableDirectory.self).filter("account == %@ AND serverUrl == %@", result.account, directoryServerUrl).first { - let serverUrlTo = self.utilityFileSystem.stringAppendServerUrl(result.serverUrl, addFileName: fileNameNew) - - resultDirectory.serverUrl = serverUrlTo - } else { - let atPath = self.utilityFileSystem.getDirectoryProviderStorageOcId(result.ocId) + "/" + fileNameView - let toPath = self.utilityFileSystem.getDirectoryProviderStorageOcId(result.ocId) + "/" + fileNameNew - - self.utilityFileSystem.moveFile(atPath: atPath, toPath: toPath) - } - - if result.isLivePhoto, - let resultMOV = realm.objects(tableMetadata.self).filter("fileId == %@ AND account == %@", fileIdMOV, result.account).first { - let fileNameView = resultMOV.fileNameView - let fileName = (fileNameNew as NSString).deletingPathExtension - let ext = (resultMOV.fileName as NSString).pathExtension - resultMOV.fileName = fileName + "." + ext - resultMOV.fileNameView = fileName + "." + ext - - let atPath = self.utilityFileSystem.getDirectoryProviderStorageOcId(resultMOV.ocId) + "/" + fileNameView - let toPath = self.utilityFileSystem.getDirectoryProviderStorageOcId(resultMOV.ocId) + "/" + fileName + "." + ext - - self.utilityFileSystem.moveFile(atPath: atPath, toPath: toPath) - } + let oldFileNameView = metadata.fileNameView + let account = metadata.account + let originalServerUrl = metadata.serverUrl + + metadata.fileName = fileNameNew + metadata.fileNameView = fileNameNew + metadata.status = status + metadata.sessionDate = (status == NCGlobal.shared.metadataStatusNormal) ? nil : Date() + + if metadata.directory { + let oldDirUrl = self.utilityFileSystem.createServerUrl(serverUrl: originalServerUrl, fileName: oldFileNameView) + let newDirUrl = self.utilityFileSystem.createServerUrl(serverUrl: originalServerUrl, fileName: fileNameNew) + + if let dir = realm.objects(tableDirectory.self) + .filter("account == %@ AND serverUrl == %@", account, oldDirUrl) + .first { + dir.serverUrl = newDirUrl } + } else { + let atPath = self.utilityFileSystem.getDirectoryProviderStorageOcId(metadata.ocId, userId: metadata.userId, urlBase: metadata.urlBase) + "/" + oldFileNameView + let toPath = self.utilityFileSystem.getDirectoryProviderStorageOcId(metadata.ocId, userId: metadata.userId, urlBase: metadata.urlBase) + "/" + fileNameNew + self.utilityFileSystem.moveFile(atPath: atPath, toPath: toPath) } - } catch let error { - NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] Could not write to database: \(error)") } } - func restoreMetadataFileName(ocId: String) { - do { - let realm = try Realm() - try realm.write { - if let result = realm.objects(tableMetadata.self).filter("ocId == %@", ocId).first, - let encodedURLString = result.serveUrlFileName.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed), - let url = URL(string: encodedURLString) { - let fileIdMOV = result.livePhotoFile - let directoryServerUrl = self.utilityFileSystem.stringAppendServerUrl(result.serverUrl, addFileName: result.fileNameView) - let lastPathComponent = url.lastPathComponent - let fileName = lastPathComponent.removingPercentEncoding ?? lastPathComponent - let fileNameView = result.fileNameView - - result.fileName = fileName - result.fileNameView = fileName - result.status = NCGlobal.shared.metadataStatusNormal - result.sessionDate = nil + func restoreMetadataFileName(ocId: String, sync: Bool = true) { + performRealmWrite(sync: sync) { realm in + if let result = realm.objects(tableMetadata.self) + .filter("ocId == %@", ocId) + .first, + let encodedURLString = result.serverUrlFileName.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed), + let url = URL(string: encodedURLString) { + let fileIdMOV = result.livePhotoFile + let directoryServerUrl = self.utilityFileSystem.createServerUrl(serverUrl: result.serverUrl, fileName: result.fileNameView) + let lastPathComponent = url.lastPathComponent + let fileName = lastPathComponent.removingPercentEncoding ?? lastPathComponent + let fileNameView = result.fileNameView - if result.directory, - let resultDirectory = realm.objects(tableDirectory.self).filter("account == %@ AND serverUrl == %@", result.account, directoryServerUrl).first { - let serverUrlTo = self.utilityFileSystem.stringAppendServerUrl(result.serverUrl, addFileName: fileName) + result.fileName = fileName + result.fileNameView = fileName + result.status = NCGlobal.shared.metadataStatusNormal + result.sessionDate = nil - resultDirectory.serverUrl = serverUrlTo - } else { - let atPath = self.utilityFileSystem.getDirectoryProviderStorageOcId(result.ocId) + "/" + fileNameView - let toPath = self.utilityFileSystem.getDirectoryProviderStorageOcId(result.ocId) + "/" + fileName + if result.directory, + let resultDirectory = realm.objects(tableDirectory.self).filter("account == %@ AND serverUrl == %@", result.account, directoryServerUrl).first { + let serverUrlTo = self.utilityFileSystem.createServerUrl(serverUrl: result.serverUrl, fileName: fileName) - self.utilityFileSystem.moveFile(atPath: atPath, toPath: toPath) - } + resultDirectory.serverUrl = serverUrlTo + } else { + let atPath = self.utilityFileSystem.getDirectoryProviderStorageOcId(result.ocId, userId: result.userId, urlBase: result.urlBase) + "/" + fileNameView + let toPath = self.utilityFileSystem.getDirectoryProviderStorageOcId(result.ocId, userId: result.userId, urlBase: result.urlBase) + "/" + fileName + + self.utilityFileSystem.moveFile(atPath: atPath, toPath: toPath) + } - if result.isLivePhoto, - let resultMOV = realm.objects(tableMetadata.self).filter("fileId == %@ AND account == %@", fileIdMOV, result.account).first { - let fileNameView = resultMOV.fileNameView - let fileName = (fileName as NSString).deletingPathExtension - let ext = (resultMOV.fileName as NSString).pathExtension - resultMOV.fileName = fileName + "." + ext - resultMOV.fileNameView = fileName + "." + ext + if result.isLivePhoto, + let resultMOV = realm.objects(tableMetadata.self).filter("fileId == %@ AND account == %@", fileIdMOV, result.account).first { + let fileNameView = resultMOV.fileNameView + let fileName = (fileName as NSString).deletingPathExtension + let ext = (resultMOV.fileName as NSString).pathExtension + resultMOV.fileName = fileName + "." + ext + resultMOV.fileNameView = fileName + "." + ext - let atPath = self.utilityFileSystem.getDirectoryProviderStorageOcId(resultMOV.ocId) + "/" + fileNameView - let toPath = self.utilityFileSystem.getDirectoryProviderStorageOcId(resultMOV.ocId) + "/" + fileName + "." + ext + let atPath = self.utilityFileSystem.getDirectoryProviderStorageOcId(resultMOV.ocId, userId: resultMOV.userId, urlBase: resultMOV.urlBase) + "/" + fileNameView + let toPath = self.utilityFileSystem.getDirectoryProviderStorageOcId(resultMOV.ocId, userId: resultMOV.userId, urlBase: resultMOV.urlBase) + "/" + fileName + "." + ext - self.utilityFileSystem.moveFile(atPath: atPath, toPath: toPath) - } + self.utilityFileSystem.moveFile(atPath: atPath, toPath: toPath) } } - } catch let error { - NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] Could not write to database: \(error)") } } - func setMetadataServeUrlFileNameStatusNormal(ocId: String) { - do { - let realm = try Realm() - try realm.write { - if let result = realm.objects(tableMetadata.self).filter("ocId == %@", ocId).first { - result.serveUrlFileName = self.utilityFileSystem.stringAppendServerUrl(result.serverUrl, addFileName: result.fileName) - result.status = NCGlobal.shared.metadataStatusNormal - result.sessionDate = nil - } + /// Asynchronously restores the file name of a metadata entry and updates related file system and Realm entries. + /// - Parameter ocId: The object ID (ocId) of the file to restore. + func restoreMetadataFileNameAsync(ocId: String) async { + await performRealmWriteAsync { realm in + guard let result = realm.objects(tableMetadata.self) + .filter("ocId == %@", ocId) + .first, + let encodedURLString = result.serverUrlFileName.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed), + let url = URL(string: encodedURLString) + else { + return + } + + let fileIdMOV = result.livePhotoFile + let directoryServerUrl = self.utilityFileSystem.createServerUrl(serverUrl: result.serverUrl, fileName: result.fileNameView) + let lastPathComponent = url.lastPathComponent + let fileName = lastPathComponent.removingPercentEncoding ?? lastPathComponent + let fileNameView = result.fileNameView + + result.fileName = fileName + result.fileNameView = fileName + result.status = NCGlobal.shared.metadataStatusNormal + result.sessionDate = nil + + if result.directory, + let resultDirectory = realm.objects(tableDirectory.self) + .filter("account == %@ AND serverUrl == %@", result.account, directoryServerUrl) + .first { + let serverUrlTo = self.utilityFileSystem.createServerUrl(serverUrl: result.serverUrl, fileName: fileName) + resultDirectory.serverUrl = serverUrlTo + } else { + let atPath = self.utilityFileSystem.getDirectoryProviderStorageOcId(result.ocId, userId: result.userId, urlBase: result.urlBase) + "/" + fileNameView + let toPath = self.utilityFileSystem.getDirectoryProviderStorageOcId(result.ocId, userId: result.userId, urlBase: result.urlBase) + "/" + fileName + self.utilityFileSystem.moveFile(atPath: atPath, toPath: toPath) + } + + if result.isLivePhoto, + let resultMOV = realm.objects(tableMetadata.self) + .filter("fileId == %@ AND account == %@", fileIdMOV, result.account) + .first { + let fileNameViewMOV = resultMOV.fileNameView + let baseName = (fileName as NSString).deletingPathExtension + let ext = (resultMOV.fileName as NSString).pathExtension + let fullFileName = baseName + "." + ext + + resultMOV.fileName = fullFileName + resultMOV.fileNameView = fullFileName + + let atPath = self.utilityFileSystem.getDirectoryProviderStorageOcId(resultMOV.ocId, userId: resultMOV.userId, urlBase: resultMOV.urlBase) + "/" + fileNameViewMOV + let toPath = self.utilityFileSystem.getDirectoryProviderStorageOcId(resultMOV.ocId, userId: resultMOV.userId, urlBase: resultMOV.urlBase) + "/" + fullFileName + self.utilityFileSystem.moveFile(atPath: atPath, toPath: toPath) } - } catch let error { - NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] Could not write to database: \(error)") } } - func setMetadataEtagResource(ocId: String, etagResource: String?) { - guard let etagResource else { return } - - do { - let realm = try Realm() - try realm.write { - let result = realm.objects(tableMetadata.self).filter("ocId == %@", ocId).first - result?.etagResource = etagResource + func setMetadataServerUrlFileNameStatusNormal(ocId: String, sync: Bool = true) { + performRealmWrite(sync: sync) { realm in + if let result = realm.objects(tableMetadata.self) + .filter("ocId == %@", ocId) + .first { + result.serverUrlFileName = self.utilityFileSystem.createServerUrl(serverUrl: result.serverUrl, fileName: result.fileName) + result.status = NCGlobal.shared.metadataStatusNormal + result.sessionDate = nil } - } catch let error { - NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] Could not write to database: \(error)") } } - func setMetadataLivePhotoByServer(account: String, ocId: String, livePhotoFile: String) { - do { - let realm = try Realm() - try realm.write { - if let result = realm.objects(tableMetadata.self).filter("account == %@ AND ocId == %@", account, ocId).first { - result.isFlaggedAsLivePhotoByServer = true - result.livePhotoFile = livePhotoFile - } + func setMetadataServerUrlFileNameStatusNormalAsync(ocId: String) async { + await performRealmWriteAsync { realm in + if let result = realm.objects(tableMetadata.self) + .filter("ocId == %@", ocId) + .first { + result.serverUrlFileName = self.utilityFileSystem.createServerUrl(serverUrl: result.serverUrl, fileName: result.fileName) + result.status = NCGlobal.shared.metadataStatusNormal + result.sessionDate = nil } - } catch let error { - NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] Could not write to database: \(error)") } } - func updateMetadatasFavorite(account: String, metadatas: [tableMetadata]) { - do { - let realm = try Realm() - try realm.write { - let results = realm.objects(tableMetadata.self).filter("account == %@ AND favorite == true", account) - for result in results { - result.favorite = false - } - realm.add(metadatas, update: .all) + func setMetadataLivePhotoByServerAsync(account: String, + ocId: String, + livePhotoFile: String) async { + await performRealmWriteAsync { realm in + if let result = realm.objects(tableMetadata.self) + .filter("account == %@ AND ocId == %@", account, ocId) + .first { + result.isFlaggedAsLivePhotoByServer = true + result.livePhotoFile = livePhotoFile } - } catch let error { - NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] Could not write to database: \(error)") } } - func updateMetadatasFiles(_ metadatas: [tableMetadata], serverUrl: String, account: String) { - do { - let realm = try Realm() - try realm.write { - let results = realm.objects(tableMetadata.self).filter(NSPredicate(format: "account == %@ AND serverUrl == %@ AND status == %d", account, serverUrl, NCGlobal.shared.metadataStatusNormal)) - realm.delete(results) - for metadata in metadatas { - if realm.objects(tableMetadata.self).filter(NSPredicate(format: "ocId == %@ AND status != %d", metadata.ocId, NCGlobal.shared.metadataStatusNormal)).first != nil { - continue - } - realm.add(tableMetadata(value: metadata), update: .all) + func updateMetadatasFavoriteAsync(account: String, metadatas: [tableMetadata]) async { + guard !metadatas.isEmpty else { return } + + await performRealmWriteAsync { realm in + let oldFavorites = realm.objects(tableMetadata.self) + .filter("account == %@ AND favorite == true", account) + for item in oldFavorites { + item.favorite = false + } + realm.add(metadatas, update: .all) + } + } + + /// Asynchronously updates a list of `tableMetadata` entries in Realm for a given account and server URL. + /// + /// This function performs the following steps: + /// 1. Skips all entries with `status != metadataStatusNormal`. + /// 2. Deletes existing metadata entries with `status == metadataStatusNormal` that are not in the skip list. + /// 3. Copies matching `mediaSearch` from previously deleted metadata to the incoming list. + /// 4. Inserts or updates new metadata entries into Realm, except those in the skip list. + /// + /// - Parameters: + /// - metadatas: An array of incoming detached `tableMetadata` objects to insert or update. + /// - serverUrl: The server URL associated with the metadata entries. + /// - account: The account identifier used to scope the metadata update. + func updateMetadatasFilesAsync(_ metadatas: [tableMetadata], serverUrl: String, account: String) async { + await performRealmWriteAsync { realm in + let ocIdsToSkip = Set( + realm.objects(tableMetadata.self) + .filter("status != %d", NCGlobal.shared.metadataStatusNormal) + .map(\.ocId) + ) + + let resultsToDelete = realm.objects(tableMetadata.self) + .filter("account == %@ AND serverUrl == %@ AND status == %d AND fileName != %@", account, serverUrl, NCGlobal.shared.metadataStatusNormal, NextcloudKit.shared.nkCommonInstance.rootFileName) + .filter { !ocIdsToSkip.contains($0.ocId) } + let metadatasCopy = Array(resultsToDelete).map { tableMetadata(value: $0) } + + realm.delete(resultsToDelete) + + for metadata in metadatas { + guard !ocIdsToSkip.contains(metadata.ocId) else { + continue } + if let match = metadatasCopy.first(where: { $0.ocId == metadata.ocId }) { + metadata.mediaSearch = match.mediaSearch + } + realm.add(metadata.detachedCopy(), update: .all) } - } catch let error { - NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] Could not write to database: \(error)") } } - func setMetadataEncrypted(ocId: String, encrypted: Bool) { - do { - let realm = try Realm() - try realm.write { - let result = realm.objects(tableMetadata.self).filter("ocId == %@", ocId).first - result?.e2eEncrypted = encrypted + func setMetadataEncrypted(ocId: String, encrypted: Bool, sync: Bool = true) { + performRealmWrite(sync: sync) { realm in + let result = realm.objects(tableMetadata.self) + .filter("ocId == %@", ocId) + .first + result?.e2eEncrypted = encrypted + } + } + + func setMetadataEncryptedAsync(ocId: String, encrypted: Bool) async { + await performRealmWriteAsync { realm in + let result = realm.objects(tableMetadata.self) + .filter("ocId == %@", ocId) + .first + result?.e2eEncrypted = encrypted + } + } + + func setMetadataFileNameView(serverUrl: String, fileName: String, newFileNameView: String, account: String, sync: Bool = true) { + performRealmWrite(sync: sync) { realm in + let result = realm.objects(tableMetadata.self) + .filter("account == %@ AND serverUrl == %@ AND fileName == %@", account, serverUrl, fileName) + .first + result?.fileNameView = newFileNameView + } + } + + func setMetadataFileNameViewAsync(serverUrl: String, fileName: String, newFileNameView: String, account: String) async { + await performRealmWriteAsync { realm in + let result = realm.objects(tableMetadata.self) + .filter("account == %@ AND serverUrl == %@ AND fileName == %@", account, serverUrl, fileName) + .first + result?.fileNameView = newFileNameView + } + } + + func moveMetadata(ocId: String, serverUrlTo: String, sync: Bool = true) { + performRealmWrite(sync: sync) { realm in + if let result = realm.objects(tableMetadata.self) + .filter("ocId == %@", ocId) + .first { + result.serverUrl = serverUrlTo } - } catch let error { - NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] Could not write to database: \(error)") } } - func setMetadataFileNameView(serverUrl: String, fileName: String, newFileNameView: String, account: String) { - do { - let realm = try Realm() - try realm.write { - let result = realm.objects(tableMetadata.self).filter("account == %@ AND serverUrl == %@ AND fileName == %@", account, serverUrl, fileName).first - result?.fileNameView = newFileNameView + func moveMetadataAsync(ocId: String, serverUrlTo: String) async { + await performRealmWriteAsync { realm in + if let result = realm.objects(tableMetadata.self) + .filter("ocId == %@", ocId) + .first { + result.serverUrl = serverUrlTo } - } catch let error { - NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] Could not write to database: \(error)") } } - func moveMetadata(ocId: String, serverUrlTo: String) { - do { - let realm = try Realm() - try realm.write { - if let result = realm.objects(tableMetadata.self).filter("ocId == %@", ocId).first { - result.serverUrl = serverUrlTo - } + func setLivePhotoFile(fileId: String, livePhotoFile: String) async { + await performRealmWriteAsync { realm in + let result = realm.objects(tableMetadata.self) + .filter("fileId == %@", fileId) + .first + result?.livePhotoFile = livePhotoFile + } + } + + func clearAssetLocalIdentifiersAsync(_ assetLocalIdentifiers: [String]) async { + await performRealmWriteAsync { realm in + let results = realm.objects(tableMetadata.self) + .filter("assetLocalIdentifier IN %@", assetLocalIdentifiers) + for result in results { + result.assetLocalIdentifier = "" } - } catch let error { - NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] Could not write to database: \(error)") } } - func clearAssetLocalIdentifiers(_ assetLocalIdentifiers: [String]) { - do { - let realm = try Realm() - try realm.write { - let results = realm.objects(tableMetadata.self).filter("assetLocalIdentifier IN %@", assetLocalIdentifiers) - for result in results { - result.assetLocalIdentifier = "" - } + func setMetadataFavorite(ocId: String, favorite: Bool?, saveOldFavorite: String?, status: Int, sync: Bool = true) { + performRealmWrite(sync: sync) { realm in + let result = realm.objects(tableMetadata.self) + .filter("ocId == %@", ocId) + .first + if let favorite { + result?.favorite = favorite + } + result?.storeFlag = saveOldFavorite + result?.status = status + + if status == NCGlobal.shared.metadataStatusNormal { + result?.sessionDate = nil + } else { + result?.sessionDate = Date() } - } catch let error { - NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] Could not write to database: \(error)") } } - func setMetadataFavorite(ocId: String, favorite: Bool?, saveOldFavorite: String?, status: Int) { - do { - let realm = try Realm() - try realm.write { - let result = realm.objects(tableMetadata.self).filter("ocId == %@", ocId).first - if let favorite { - result?.favorite = favorite - } - result?.storeFlag = saveOldFavorite - result?.status = status + /// Asynchronously sets the favorite status of a `tableMetadata` entry. + /// Optionally stores the previous favorite flag and updates the sync status. + func setMetadataFavoriteAsync(ocId: String, favorite: Bool?, saveOldFavorite: String?, status: Int) async { + await performRealmWriteAsync { realm in + guard let result = realm.objects(tableMetadata.self) + .filter("ocId == %@", ocId) + .first else { + return + } - if status == NCGlobal.shared.metadataStatusNormal { - result?.sessionDate = nil - } else { - result?.sessionDate = Date() - } + if let favorite { + result.favorite = favorite } - } catch let error { - NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] Could not write to database: \(error)") + + result.storeFlag = saveOldFavorite + result.status = status + result.sessionDate = (status == NCGlobal.shared.metadataStatusNormal) ? nil : Date() } } - func setMetadataCopyMove(ocId: String, serverUrlTo: String, overwrite: String?, status: Int) { - do { - let realm = try Realm() - try realm.write { - let result = realm.objects(tableMetadata.self).filter("ocId == %@", ocId).first - result?.serverUrlTo = serverUrlTo - result?.storeFlag = overwrite - result?.status = status + func setMetadataCopyMove(ocId: String, serverUrlTo: String, overwrite: String?, status: Int, sync: Bool = true) { + performRealmWrite(sync: sync) { realm in + if let result = realm.objects(tableMetadata.self) + .filter("ocId == %@", ocId) + .first { + result.destination = serverUrlTo + result.storeFlag = overwrite + result.status = status if status == NCGlobal.shared.metadataStatusNormal { - result?.sessionDate = nil + result.sessionDate = nil } else { - result?.sessionDate = Date() + result.sessionDate = Date() } } - } catch let error { - NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] Could not write to database: \(error)") } } - // MARK: - GetMetadata + /// Asynchronously updates a `tableMetadata` entry to set copy/move status and target server URL. + func setMetadataCopyMoveAsync(ocId: String, destination: String, overwrite: String?, status: Int) async { + await performRealmWriteAsync { realm in + guard let result = realm.objects(tableMetadata.self) + .filter("ocId == %@", ocId) + .first else { + return + } - func getMetadata(predicate: NSPredicate) -> tableMetadata? { - do { - let realm = try Realm() - guard let result = realm.objects(tableMetadata.self).filter(predicate).first else { return nil } - return tableMetadata(value: result) - } catch let error as NSError { - NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] Could not access database: \(error)") + result.destination = destination + result.storeFlag = overwrite + result.status = status + result.sessionDate = (status == NCGlobal.shared.metadataStatusNormal) ? nil : Date() } - - return nil } - func getMetadatas(predicate: NSPredicate) -> [tableMetadata] { - do { - let realm = try Realm() - let results = realm.objects(tableMetadata.self).filter(predicate) - return Array(results.map { tableMetadata(value: $0) }) - } catch let error as NSError { - NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] Could not access database: \(error)") + func clearMetadatasUpload(account: String, sync: Bool = true) { + performRealmWrite(sync: sync) { realm in + let results = realm.objects(tableMetadata.self) + .filter("account == %@ AND (status == %d OR status == %d)", account, NCGlobal.shared.metadataStatusWaitUpload, NCGlobal.shared.metadataStatusUploadError) + realm.delete(results) } - return [] } - func getMetadatas(predicate: NSPredicate, sortedByKeyPath: String, ascending: Bool = false) -> [tableMetadata]? { - do { - let realm = try Realm() - let results = realm.objects(tableMetadata.self).filter(predicate).sorted(byKeyPath: sortedByKeyPath, ascending: ascending) - return Array(results.map { tableMetadata(value: $0) }) - } catch let error as NSError { - NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] Could not access database: \(error)") + func clearMetadatasUploadAsync(account: String) async { + await performRealmWriteAsync { realm in + let results = realm.objects(tableMetadata.self) + .filter("account == %@ AND (status == %d OR status == %d)", account, NCGlobal.shared.metadataStatusWaitUpload, NCGlobal.shared.metadataStatusUploadError) + realm.delete(results) } - return nil } - func getMetadatas(predicate: NSPredicate, numItems: Int, sorted: String, ascending: Bool) -> [tableMetadata] { - var counter: Int = 0 - var metadatas: [tableMetadata] = [] + /// Syncs the remote and local metadata. + /// Returns true if there were changes (additions or deletions), false if everything was already up-to-date. + func mergeRemoteMetadatasAsync(remoteMetadatas: [tableMetadata], localMetadatas: [tableMetadata]) async -> Bool { + // Set of ocId + let remoteOcIds = Set(remoteMetadatas.map { $0.ocId }) + let localOcIds = Set(localMetadatas.map { $0.ocId }) - do { - let realm = try Realm() - let results = realm.objects(tableMetadata.self).filter(predicate).sorted(byKeyPath: sorted, ascending: ascending) - for result in results where counter < numItems { - metadatas.append(tableMetadata(value: result)) - counter += 1 - } - } catch let error as NSError { - NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] Could not access database: \(error)") + // Calculate diffs + let toDeleteOcIds = localOcIds.subtracting(remoteOcIds) + let toAddOcIds = remoteOcIds.subtracting(localOcIds) + + guard !toDeleteOcIds.isEmpty || !toAddOcIds.isEmpty else { + return false // No changes needed } - return metadatas - } - func getMetadataFromOcId(_ ocId: String?) -> tableMetadata? { - guard let ocId else { return nil } + let toDeleteKeys = Array(toDeleteOcIds) - do { - let realm = try Realm() - guard let result = realm.objects(tableMetadata.self).filter("ocId == %@", ocId).first else { return nil } - return tableMetadata(value: result) - } catch let error as NSError { - NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] Could not access database: \(error)") + await performRealmWriteAsync { realm in + let toAdd = remoteMetadatas.filter { toAddOcIds.contains($0.ocId) } + let toDelete = toDeleteKeys.compactMap { + realm.object(ofType: tableMetadata.self, forPrimaryKey: $0) + } + + realm.delete(toDelete) + realm.add(toAdd, update: .modified) } - return nil + + return true } -// func getMetadataFromOcIdAndocIdTransfer(_ ocId: String?) -> tableMetadata? { -// guard let ocId else { return nil } -// -// do { -// let realm = try Realm() -// if let result = realm.objects(tableMetadata.self).filter("ocId == %@", ocId).first { -// return tableMetadata(value: result) -// } -// if let result = realm.objects(tableMetadata.self).filter("ocIdTransfer == %@", ocId).first { -// return tableMetadata(value: result) -// } -// } catch let error as NSError { -// NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] Could not access database: \(error)") -// } -// return nil -// } - - func getMetadataFromOcIdAndocIdTransfer(_ ocId: String?) -> tableMetadata? { - guard let ocId else { return nil } + // MARK: - Realm Read + func getAllTableMetadataAsync() async -> [tableMetadata] { + return await performRealmReadAsync { realm in + realm.objects(tableMetadata.self).map { tableMetadata(value: $0) } + } ?? [] + } + + func getMetadata(predicate: NSPredicate) -> tableMetadata? { return performRealmRead { realm in realm.objects(tableMetadata.self) - .filter("ocId == %@ OR ocIdTransfer == %@", ocId, ocId) + .filter(predicate) .first - .map { tableMetadata(value: $0) } + .map { $0.detachedCopy() } } } - -// func getMetadataFromOcIdAndocIdTransfer(_ ocId: String?) -> tableMetadata? { -// guard let ocId else { return nil } -// -// return performRealmRead { realm in -// // First, try to find by ocId -// if let result = realm.objects(tableMetadata.self) -// .filter("ocId == %@", ocId) -// .first { -// return tableMetadata(value: result) -// } -// -// // If not found, try to find by ocIdTransfer -// if let result = realm.objects(tableMetadata.self) -// .filter("ocIdTransfer == %@", ocId) -// .first { -// return tableMetadata(value: result) -// } -// -// // Not found -// return nil -// } -// } - - func getMetadataFolder(session: NCSession.Session, serverUrl: String) -> tableMetadata? { - var serverUrl = serverUrl - var fileName = "" - let serverUrlHome = utilityFileSystem.getHomeServer(session: session) - if serverUrlHome == serverUrl { - fileName = "." - serverUrl = ".." - } else { - fileName = (serverUrl as NSString).lastPathComponent - if let path = utilityFileSystem.deleteLastPath(serverUrlPath: serverUrl) { - serverUrl = path - } + func getMetadataAsync(predicate: NSPredicate) async -> tableMetadata? { + return await performRealmReadAsync { realm in + realm.objects(tableMetadata.self) + .filter(predicate) + .first + .map { $0.detachedCopy() } } + } + + func getMetadatas(predicate: NSPredicate) -> [tableMetadata] { + performRealmRead { realm in + realm.objects(tableMetadata.self) + .filter(predicate) + .map { $0.detachedCopy() } + } ?? [] + } + + func getMediaMetadatas(predicate: NSPredicate, sorted: String? = nil, ascending: Bool = false) -> ThreadSafeArray? { do { let realm = try Realm() - guard let result = realm.objects(tableMetadata.self).filter("account == %@ AND serverUrl == %@ AND fileName == %@", session.account, serverUrl, fileName).first else { return nil } - return tableMetadata(value: result) + if let sorted { + var results: [tableMetadata] = [] + switch sorted {//NCPreferences().mediaSortDate { + case "date": + results = realm.objects(tableMetadata.self).filter(predicate).sorted { ($0.date as Date) > ($1.date as Date) } + case "creationDate": + results = realm.objects(tableMetadata.self).filter(predicate).sorted { ($0.creationDate as Date) > ($1.creationDate as Date) } + case "uploadDate": + results = realm.objects(tableMetadata.self).filter(predicate).sorted { ($0.uploadDate as Date) > ($1.uploadDate as Date) } + default: + let results = realm.objects(tableMetadata.self).filter(predicate) + return ThreadSafeArray(results.map { tableMetadata.init(value: $0) }) + } + return ThreadSafeArray(results.map { tableMetadata.init(value: $0) }) + } else { + let results = realm.objects(tableMetadata.self).filter(predicate) + return ThreadSafeArray(results.map { tableMetadata.init(value: $0) }) + } } catch let error as NSError { - NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] Could not access database: \(error)") +// NextcloudKit.shared.nkCommonInstance.writeLog("Could not access database: \(error)") } return nil } - func getMetadataLivePhoto(metadata: tableMetadata) -> tableMetadata? { - guard metadata.isLivePhoto else { return nil } + func getMetadatas(predicate: NSPredicate, + sortedByKeyPath: String, + ascending: Bool = false) -> [tableMetadata]? { + return performRealmRead { realm in + realm.objects(tableMetadata.self) + .filter(predicate) + .sorted(byKeyPath: sortedByKeyPath, ascending: ascending) + .map { $0.detachedCopy() } + } + } - do { - let realm = try Realm() - guard let result = realm.objects(tableMetadata.self).filter(NSPredicate(format: "account == %@ AND serverUrl == %@ AND fileId == %@", - metadata.account, - metadata.serverUrl, - metadata.livePhotoFile)).first else { return nil } - return tableMetadata(value: result) - } catch let error as NSError { - NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] Could not access database: \(error)") + func getMetadatasAsync(predicate: NSPredicate, + sortedByKeyPath: String, + ascending: Bool = false, + limit: Int? = nil) async -> [tableMetadata]? { + return await performRealmReadAsync { realm in + let results = realm.objects(tableMetadata.self) + .filter(predicate) + .sorted(byKeyPath: sortedByKeyPath, + ascending: ascending) + + if let limit { + let sliced = results.prefix(limit) + return sliced.map { $0.detachedCopy() } + } else { + return results.map { $0.detachedCopy() } + } } - return nil } - func getMetadataConflict(account: String, serverUrl: String, fileNameView: String, nativeFormat: Bool) -> tableMetadata? { - let fileNameExtension = (fileNameView as NSString).pathExtension.lowercased() - let fileNameNoExtension = (fileNameView as NSString).deletingPathExtension - var fileNameConflict = fileNameView + func getMetadatas(predicate: NSPredicate, + numItems: Int, + sorted: String, + ascending: Bool) -> [tableMetadata] { + return performRealmRead { realm in + let results = realm.objects(tableMetadata.self) + .filter(predicate) + .sorted(byKeyPath: sorted, ascending: ascending) + return results.prefix(numItems) + .map { $0.detachedCopy() } + } ?? [] + } - if fileNameExtension == "heic", !nativeFormat { - fileNameConflict = fileNameNoExtension + ".jpg" + func getMetadataFromOcId(_ ocId: String?) -> tableMetadata? { + guard let ocId else { return nil } + + return performRealmRead { realm in + realm.objects(tableMetadata.self) + .filter("ocId == %@", ocId) + .first + .map { $0.detachedCopy() } } - return getMetadata(predicate: NSPredicate(format: "account == %@ AND serverUrl == %@ AND fileNameView == %@", - account, - serverUrl, - fileNameConflict)) } - // MARK: - GetResult(s)Metadata + func getMetadataFromOcIdAsync(_ ocId: String?) async -> tableMetadata? { + guard let ocId else { return nil } - func getResultsMetadatasPredicate(_ predicate: NSPredicate, layoutForView: NCDBLayoutForView?, directoryOnTop: Bool = true) -> [tableMetadata] { - do { - let realm = try Realm() - var results = realm.objects(tableMetadata.self).filter(predicate).freeze() - let layout: NCDBLayoutForView = layoutForView ?? NCDBLayoutForView() - - if layout.sort == "fileName" { - let sortedResults = results.sorted { - let ordered = layout.ascending ? ComparisonResult.orderedAscending : ComparisonResult.orderedDescending - // 1. favorite order - if $0.favorite == $1.favorite { - // 2. directory order TOP - if directoryOnTop { - if $0.directory == $1.directory { - // 3. natural fileName - return $0.fileNameView.localizedStandardCompare($1.fileNameView) == ordered - } else { - return $0.directory && !$1.directory - } - } else { - return $0.fileNameView.localizedStandardCompare($1.fileNameView) == ordered - } - } else { - return $0.favorite && !$1.favorite - } - } - return sortedResults - } else { - if directoryOnTop { - results = results.sorted(byKeyPath: layout.sort, ascending: layout.ascending).sorted(byKeyPath: "favorite", ascending: false).sorted(byKeyPath: "directory", ascending: false) - } else { - results = results.sorted(byKeyPath: layout.sort, ascending: layout.ascending).sorted(byKeyPath: "favorite", ascending: false) - } + return await performRealmReadAsync { realm in + realm.objects(tableMetadata.self) + .filter("ocId == %@", ocId) + .first + .map { $0.detachedCopy() } + } + } + + func getMetadataFromOcIdAndocIdTransferAsync(_ ocId: String?) async -> tableMetadata? { + guard let ocId else { + return nil + } + + return await performRealmReadAsync { realm in + realm.objects(tableMetadata.self) + .filter("ocId == %@ OR ocIdTransfer == %@", ocId, ocId) + .first + .map { $0.detachedCopy() } + } + } + + /// Asynchronously retrieves the metadata for a folder, based on its session and serverUrl. + /// Handles the home directory case rootFileName) and detaches the Realm object before returning. + func getMetadataFolderAsync(session: NCSession.Session, serverUrl: String) async -> tableMetadata? { + var serverUrl = serverUrl + var fileName = "" + let home = utilityFileSystem.getHomeServer(session: session) + + if home == serverUrl { + fileName = NextcloudKit.shared.nkCommonInstance.rootFileName + } else { + fileName = (serverUrl as NSString).lastPathComponent + if let serverDirectoryUp = utilityFileSystem.serverDirectoryUp(serverUrl: serverUrl, home: home) { + serverUrl = serverDirectoryUp } - return Array(results) + } - } catch let error as NSError { - NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] Could not access database: \(error)") + return await performRealmReadAsync { realm in + realm.objects(tableMetadata.self) + .filter("account == %@ AND serverUrl == %@ AND fileName == %@", session.account, serverUrl, fileName) + .first + .map { $0.detachedCopy() } } - return [] } - func getResultsMetadatas(predicate: NSPredicate, sortedByKeyPath: String, ascending: Bool, arraySlice: Int) -> [tableMetadata] { - do { - let realm = try Realm() - let results = realm.objects(tableMetadata.self).filter(predicate).sorted(byKeyPath: sortedByKeyPath, ascending: ascending).prefix(arraySlice) - return Array(results) - } catch let error as NSError { - NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] Could not access database: \(error)") + func getMetadataLivePhoto(metadata: tableMetadata) -> tableMetadata? { + guard metadata.isLivePhoto else { + return nil + } + let detached = metadata.detachedCopy() + + return performRealmRead { realm in + realm.objects(tableMetadata.self) + .filter(NSPredicate(format: "account == %@ AND serverUrl == %@ AND fileId == %@", + detached.account, + detached.serverUrl, + detached.livePhotoFile)) + .first + .map { $0.detachedCopy() } } - return [] } - func getResultMetadata(predicate: NSPredicate) -> tableMetadata? { - do { - let realm = try Realm() - return realm.objects(tableMetadata.self).filter(predicate).first - } catch let error as NSError { - NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] Could not access database: \(error)") + func getMetadataLivePhotoAsync(metadata: tableMetadata) async -> tableMetadata? { + guard metadata.isLivePhoto else { + return nil + } + let detached = metadata.detachedCopy() + + return await performRealmReadAsync { realm in + realm.objects(tableMetadata.self) + .filter(NSPredicate(format: "account == %@ AND serverUrl == %@ AND fileId == %@", + detached.account, + detached.serverUrl, + detached.livePhotoFile)) + .first + .map { $0.detachedCopy() } } - return nil } - func getResultMetadataFromFileName(_ fileName: String, serverUrl: String, sessionTaskIdentifier: Int) -> tableMetadata? { - do { - let realm = try Realm() - return realm.objects(tableMetadata.self).filter("fileName == %@ AND serverUrl == %@ AND sessionTaskIdentifier == %d", fileName, serverUrl, sessionTaskIdentifier).first - } catch let error as NSError { - NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] Could not access database: \(error)") + func getMetadataConflict(account: String, serverUrl: String, fileNameView: String, nativeFormat: Bool) -> tableMetadata? { + let fileNameExtension = (fileNameView as NSString).pathExtension.lowercased() + let fileNameNoExtension = (fileNameView as NSString).deletingPathExtension + var fileNameConflict = fileNameView + + if fileNameExtension == "heic", !nativeFormat { + fileNameConflict = fileNameNoExtension + ".jpg" } - return nil + return getMetadata(predicate: NSPredicate(format: "account == %@ AND serverUrl == %@ AND fileNameView == %@", + account, + serverUrl, + fileNameConflict)) } - func getResultsMetadatasFromGroupfolders(session: NCSession.Session) -> Results? { - var ocId: [String] = [] + /// Asynchronously retrieves and sorts `tableMetadata` associated with groupfolders for a given session. + /// - Parameters: + /// - session: The `NCSession.Session` containing account and server information. + /// - layoutForView: An optional layout configuration used for sorting. + /// - Returns: An array of sorted and detached `tableMetadata` objects. + func getMetadatasFromGroupfoldersAsync(session: NCSession.Session, layoutForView: NCDBLayoutForView?) async -> [tableMetadata] { let homeServerUrl = utilityFileSystem.getHomeServer(session: session) - do { - let realm = try Realm() - let groupfolders = realm.objects(TableGroupfolders.self).filter("account == %@", session.account).sorted(byKeyPath: "mountPoint", ascending: true) + let detachedMetadatas: [tableMetadata] = await performRealmReadAsync { realm in + var ocIds: [String] = [] + + // Safely fetch and detach groupfolders + let groupfolders = realm.objects(TableGroupfolders.self) + .filter("account == %@", session.account) + .sorted(byKeyPath: "mountPoint", ascending: true) + .map { TableGroupfolders(value: $0) } for groupfolder in groupfolders { let mountPoint = groupfolder.mountPoint.hasPrefix("/") ? groupfolder.mountPoint : "/" + groupfolder.mountPoint let serverUrlFileName = homeServerUrl + mountPoint - if let directory = realm.objects(tableDirectory.self).filter("account == %@ AND serverUrl == %@", session.account, serverUrlFileName).first, - let result = realm.objects(tableMetadata.self).filter("ocId == %@", directory.ocId).first { - ocId.append(result.ocId) + if let directory = realm.objects(tableDirectory.self) + .filter("account == %@ AND serverUrl == %@", session.account, serverUrlFileName) + .first, + let metadata = realm.objects(tableMetadata.self) + .filter("ocId == %@", directory.ocId) + .first { + ocIds.append(metadata.ocId) } } - return realm.objects(tableMetadata.self).filter("ocId IN %@", ocId) - } catch let error as NSError { - NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] Could not access database: \(error)") + // Fetch and detach the corresponding metadatas + return realm.objects(tableMetadata.self) + .filter("ocId IN %@", ocIds) + .map { $0.detachedCopy() } + } ?? [] + + let sorted = await self.sortedMetadata(layoutForView: layoutForView, account: session.account, metadatas: detachedMetadatas) + return sorted + } + + func getRootContainerMetadata(accout: String) -> tableMetadata? { + return performRealmRead { realm in + realm.objects(tableMetadata.self) + .filter("fileName == %@ AND account == %@", NextcloudKit.shared.nkCommonInstance.rootFileName, accout) + .first + .map { $0.detachedCopy() } } + } - return nil + func getRootContainerMetadataAsync(accout: String) async -> tableMetadata? { + return await performRealmReadAsync { realm in + realm.objects(tableMetadata.self) + .filter("fileName == %@ AND account == %@", NextcloudKit.shared.nkCommonInstance.rootFileName, accout) + .first + .map { $0.detachedCopy() } + } } - func getTableMetadatasDirectoryFavoriteIdentifierRank(account: String) -> [String: NSNumber] { - var listIdentifierRank: [String: NSNumber] = [:] - var counter = 10 as Int64 + func getMetadatasAsync(predicate: NSPredicate) async -> [tableMetadata] { + await performRealmReadAsync { realm in + realm.objects(tableMetadata.self) + .filter(predicate) + .map { $0.detachedCopy() } + } ?? [] + } - do { - let realm = try Realm() - let results = realm.objects(tableMetadata.self).filter("account == %@ AND directory == true AND favorite == true", account).sorted(byKeyPath: "fileNameView", ascending: true) - for result in results { + func getTableMetadatasDirectoryFavoriteIdentifierRankAsync(account: String) async -> [String: NSNumber] { + let result = await performRealmReadAsync { realm in + var listIdentifierRank: [String: NSNumber] = [:] + var counter = Int64(10) + + let results = realm.objects(tableMetadata.self) + .filter("account == %@ AND directory == true AND favorite == true", account) + .sorted(byKeyPath: "fileNameView", ascending: true) + + results.forEach { item in counter += 1 - listIdentifierRank[result.ocId] = NSNumber(value: Int64(counter)) + listIdentifierRank[item.ocId] = NSNumber(value: counter) } - } catch let error as NSError { - NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] Could not access database: \(error)") + + return listIdentifierRank } - return listIdentifierRank + return result ?? [:] } - @objc func clearMetadatasUpload(account: String) { - do { - let realm = try Realm() - try realm.write { - let results = realm.objects(tableMetadata.self).filter("account == %@ AND (status == %d OR status == %d)", account, NCGlobal.shared.metadataStatusWaitUpload, NCGlobal.shared.metadataStatusUploadError) - realm.delete(results) - } - } catch let error { - NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] Could not write to database: \(error)") + func getAssetLocalIdentifiersUploadedAsync() async -> [String]? { + return await performRealmReadAsync { realm in + let results = realm.objects(tableMetadata.self).filter("assetLocalIdentifier != ''") + return results.map { $0.assetLocalIdentifier } } } - func getAssetLocalIdentifiersUploaded() -> [String]? { - var assetLocalIdentifiers: [String] = [] + func getMetadataFromFileId(_ fileId: String?) -> tableMetadata? { + guard let fileId else { + return nil + } - do { - let realm = try Realm() - let results = realm.objects(tableMetadata.self).filter("assetLocalIdentifier != ''") - for result in results { - assetLocalIdentifiers.append(result.assetLocalIdentifier) - } - return assetLocalIdentifiers - } catch let error as NSError { - NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] Could not access database: \(error)") + return performRealmRead { realm in + realm.objects(tableMetadata.self) + .filter("fileId == %@", fileId) + .first + .map { $0.detachedCopy() } } - return nil } - func getMetadataFromDirectory(account: String, serverUrl: String) -> Bool { - do { - let realm = try Realm() - guard let directory = realm.objects(tableDirectory.self).filter("account == %@ AND serverUrl == %@", account, serverUrl).first, - realm.objects(tableMetadata.self).filter("ocId == %@", directory.ocId).first != nil else { return false } - return true - } catch let error as NSError { - NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] Could not access database: \(error)") + /// Asynchronously retrieves a `tableMetadata` object matching the given `fileId`, if available. + /// - Parameter fileId: The file identifier used to query the Realm database. + /// - Returns: A detached copy of the `tableMetadata` object, or `nil` if not found. + func getMetadataFromFileIdAsync(_ fileId: String?) async -> tableMetadata? { + guard let fileId else { + return nil + } + + return await performRealmReadAsync { realm in + let object = realm.objects(tableMetadata.self) + .filter("fileId == %@", fileId) + .first + return object?.detachedCopy() } - return false } - func getMetadataFromFileId(_ fileId: String?) -> tableMetadata? { - guard let fileId else { return nil } + /// Asynchronously retrieves and sorts `tableMetadata` objects matching a given predicate and layout. + func getMetadatasAsync(predicate: NSPredicate, + withLayout layoutForView: NCDBLayoutForView?, + withAccount account: String) async -> [tableMetadata] { + let detachedMetadatas = await performRealmReadAsync { realm in + realm.objects(tableMetadata.self) + .filter(predicate) + .map { $0.detachedCopy() } + } ?? [] - do { - let realm = try Realm() - if let result = realm.objects(tableMetadata.self).filter("fileId == %@", fileId).first { - return tableMetadata(value: result) - } - } catch let error as NSError { - NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] Could not access database: \(error)") + let sorted = await self.sortedMetadata(layoutForView: layoutForView, account: account, metadatas: detachedMetadatas) + return sorted + } + + /// Asynchronously retrieves and sorts `tableMetadata` objects matching a given predicate and layout. + func getMetadatasAsyncDataSource(withServerUrl serverUrl: String, + withUserId userId: String, + withAccount account: String, + withLayout layoutForView: NCDBLayoutForView?, + withPreficate predicateSource: NSPredicate? = nil) async -> [tableMetadata] { + var predicate = NSPredicate(format: "account == %@ AND serverUrl == %@ AND fileName != %@ AND NOT (status IN %@)", account, serverUrl, NextcloudKit.shared.nkCommonInstance.rootFileName, NCGlobal.shared.metadataStatusHideInView) + + if NCPreferences().getPersonalFilesOnly(account: account) { + predicate = NSPredicate(format: "account == %@ AND serverUrl == %@ AND fileName != %@ AND (ownerId == %@ || ownerId == '') AND mountType == '' AND NOT (status IN %@)", account, serverUrl, NextcloudKit.shared.nkCommonInstance.rootFileName, userId, NCGlobal.shared.metadataStatusHideInView) } - return nil + + if let predicateSource { + predicate = predicateSource + } + + let detachedMetadatas = await performRealmReadAsync { realm in + realm.objects(tableMetadata.self) + .filter(predicate) + .map { $0.detachedCopy() } + } ?? [] + + let cleanedMetadatas = filterAndNormalizeLivePhotos(from: detachedMetadatas) + let sorted = await self.sortedMetadata(layoutForView: layoutForView, account: account, metadatas: cleanedMetadatas) + + return sorted } - func getResultMetadataFromOcId(_ ocId: String?) -> tableMetadata? { - guard let ocId else { return nil } + func getMetadatasAsync(predicate: NSPredicate, + withSort sortDescriptors: [RealmSwift.SortDescriptor] = [], + withLimit limit: Int? = nil) async -> [tableMetadata]? { + await performRealmReadAsync { realm in + var results = realm.objects(tableMetadata.self) + .filter(predicate) - do { - let realm = try Realm() - return realm.objects(tableMetadata.self).filter("ocId == %@", ocId).first - } catch let error as NSError { - NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] Could not access database: \(error)") + if !sortDescriptors.isEmpty { + results = results.sorted(by: sortDescriptors) + } + + if let limit { + let sliced = results.prefix(limit) + return sliced.map { $0.detachedCopy() } + } else { + return results.map { $0.detachedCopy() } + } } - return nil + } + + func hasUploadingMetadataWithChunksOrE2EE() -> Bool { + return performRealmRead { realm in + realm.objects(tableMetadata.self) + .filter("status == %d AND (chunk > 0 OR e2eEncrypted == true)", NCGlobal.shared.metadataStatusUploading) + .first != nil + } ?? false } func getMetadataDirectoryAsync(serverUrl: String, account: String) async -> tableMetadata? { @@ -1417,7 +1364,7 @@ extension NCManageDatabase { guard let decodedBaseUrl = baseUrl.removingPercentEncoding else { return nil } - + return performRealmRead { realm in let object = realm.objects(tableMetadata.self) .filter("account == %@ AND serverUrl == %@ AND fileName == %@", account, decodedBaseUrl, fileName) @@ -1425,182 +1372,4 @@ extension NCManageDatabase { return object?.detachedCopy() } } - - func createMetadatasFolder(assets: [PHAsset], - useSubFolder: Bool, - session: NCSession.Session, completion: @escaping ([tableMetadata]) -> Void) { - var foldersCreated: Set = [] - var metadatas: [tableMetadata] = [] - let serverUrlBase = getAccountAutoUploadDirectory(session: session) - let fileNameBase = getAccountAutoUploadFileName(account: session.account) - let predicate = NSPredicate(format: "account == %@ AND serverUrl BEGINSWITH %@ AND directory == true", session.account, serverUrlBase) - - func createMetadata(serverUrl: String, fileName: String, metadata: tableMetadata?) { - guard !foldersCreated.contains(serverUrl + "/" + fileName) else { - return - } - foldersCreated.insert(serverUrl + "/" + fileName) - - if let metadata { - metadata.status = NCGlobal.shared.metadataStatusWaitCreateFolder - metadata.sessionSelector = NCGlobal.shared.selectorUploadAutoUpload - metadata.sessionDate = Date() - metadatas.append(tableMetadata(value: metadata)) - } else { - let metadata = NCManageDatabase.shared.createMetadata(fileName: fileName, - fileNameView: fileName, - ocId: NSUUID().uuidString, - serverUrl: serverUrl, - url: "", - contentType: "httpd/unix-directory", - directory: true, - session: session, - sceneIdentifier: nil) - metadata.status = NCGlobal.shared.metadataStatusWaitCreateFolder - metadata.sessionSelector = NCGlobal.shared.selectorUploadAutoUpload - metadata.sessionDate = Date() - metadatas.append(metadata) - } - } - - let metadatasFolder = getMetadatas(predicate: predicate) - let targetPath = serverUrlBase + "/" + fileNameBase - let metadata = metadatasFolder.first(where: { $0.serverUrl + "/" + $0.fileNameView == targetPath }) - createMetadata(serverUrl: serverUrlBase, fileName: fileNameBase, metadata: metadata) - - if useSubFolder { - let autoUploadServerUrlBase = self.getAccountAutoUploadServerUrlBase(session: session) - let autoUploadSubfolderGranularity = self.getAccountAutoUploadSubfolderGranularity() - let folders = Set(assets.map { self.utilityFileSystem.createGranularityPath(asset: $0) }).sorted() - - for folder in folders { - let componentsDate = folder.split(separator: "/") - let year = componentsDate[0] - let serverUrl = autoUploadServerUrlBase - let fileName = String(year) - let targetPath = serverUrl + "/" + fileName - let metadata = metadatasFolder.first(where: { $0.serverUrl + "/" + $0.fileNameView == targetPath }) - - createMetadata(serverUrl: serverUrl, fileName: fileName, metadata: metadata) - - if autoUploadSubfolderGranularity >= NCGlobal.shared.subfolderGranularityMonthly { - let month = componentsDate[1] - let serverUrl = autoUploadServerUrlBase + "/" + year - let fileName = String(month) - let targetPath = serverUrl + "/" + fileName - let metadata = metadatasFolder.first(where: { $0.serverUrl + "/" + $0.fileNameView == targetPath }) - - createMetadata(serverUrl: serverUrl, fileName: fileName, metadata: metadata) - - if autoUploadSubfolderGranularity == NCGlobal.shared.subfolderGranularityDaily { - let day = componentsDate[2] - let serverUrl = autoUploadServerUrlBase + "/" + year + "/" + month - let fileName = String(day) - let targetPath = serverUrl + "/" + fileName - let metadata = metadatasFolder.first(where: { $0.serverUrl + "/" + $0.fileNameView == targetPath }) - - createMetadata(serverUrl: serverUrl, fileName: fileName, metadata: metadata) - } - - } - return results - } else { - let results = realm.objects(tableMetadata.self).filter(predicate) - if freeze { - return results.freeze() - } - return results - } - } catch let error as NSError { - NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] Could not access database: \(error)") - } - return nil - } - - func getCalculateCumulativeHash(for metadatas: [tableMetadata], account: String, serverUrl: String) -> String { - let concatenatedEtags = metadatas.map { $0.etag }.joined(separator: "-") - return sha256Hash(concatenatedEtags) - } - - func getMediaMetadatas(predicate: NSPredicate) -> ThreadSafeArray? { - - do { - let realm = try Realm() - let results = realm.objects(tableMetadata.self).filter(predicate).sorted(byKeyPath: "date", ascending: false) - return ThreadSafeArray(results.map { tableMetadata.init(value: $0) }) - } catch let error as NSError { - NextcloudKit.shared.nkCommonInstance.writeLog("Could not access database: \(error)") - } - - return nil - } - - func getMediaMetadatas(predicate: NSPredicate, sorted: String? = nil, ascending: Bool = false) -> ThreadSafeArray? { - - do { - let realm = try Realm() - if let sorted { - var results: [tableMetadata] = [] - switch NCKeychain().mediaSortDate { - case "date": - results = realm.objects(tableMetadata.self).filter(predicate).sorted { ($0.date as Date) > ($1.date as Date) } - case "creationDate": - results = realm.objects(tableMetadata.self).filter(predicate).sorted { ($0.creationDate as Date) > ($1.creationDate as Date) } - case "uploadDate": - results = realm.objects(tableMetadata.self).filter(predicate).sorted { ($0.uploadDate as Date) > ($1.uploadDate as Date) } - default: - let results = realm.objects(tableMetadata.self).filter(predicate) - return ThreadSafeArray(results.map { tableMetadata.init(value: $0) }) - } - return ThreadSafeArray(results.map { tableMetadata.init(value: $0) }) - } else { - let results = realm.objects(tableMetadata.self).filter(predicate) - return ThreadSafeArray(results.map { tableMetadata.init(value: $0) }) - } - } catch let error as NSError { - NextcloudKit.shared.nkCommonInstance.writeLog("Could not access database: \(error)") - } - return nil - } - - func getAdvancedMetadatas(predicate: NSPredicate, page: Int = 0, limit: Int = 0, sorted: String, ascending: Bool) -> [tableMetadata] { - - var metadatas: [tableMetadata] = [] - - do { - let realm = try Realm() - realm.refresh() - let results = realm.objects(tableMetadata.self).filter(predicate).sorted(byKeyPath: sorted, ascending: ascending) - if !results.isEmpty { - if page == 0 || limit == 0 { - return Array(results.map { tableMetadata.init(value: $0) }) - } else { - let nFrom = (page - 1) * limit - let nTo = nFrom + (limit - 1) - for n in nFrom...nTo { - if n == results.count { - break - } - metadatas.append(tableMetadata.init(value: results[n])) - } - } - } - } catch let error as NSError { - NextcloudKit.shared.nkCommonInstance.writeLog("Could not access database: \(error)") - } - - return metadatas - } - - func getResultMetadataFromFileId(_ fileId: String?) -> tableMetadata? { - guard let fileId else { return nil } - - do { - let realm = try Realm() - return realm.objects(tableMetadata.self).filter("fileId == %@", fileId).first - } catch let error as NSError { - NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] Could not access database: \(error)") - } - return nil - } } diff --git a/iOSClient/Main/Create/NCCreateFormUploadDocuments.swift b/iOSClient/Main/Create/NCCreateFormUploadDocuments.swift index c9aae962b1..01003052ec 100644 --- a/iOSClient/Main/Create/NCCreateFormUploadDocuments.swift +++ b/iOSClient/Main/Create/NCCreateFormUploadDocuments.swift @@ -28,6 +28,10 @@ import XLForm // MARK: - @objc class NCCreateFormUploadDocuments: XLFormViewController, NCSelectDelegate, UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout, NCCreateFormUploadConflictDelegate { + func dismissSelect(serverUrl: String?, metadata: tableMetadata?, type: String, items: [Any], overwrite: Bool, copy: Bool, move: Bool, session: NCSession.Session) { + + } + @IBOutlet weak var indicator: UIActivityIndicatorView! @IBOutlet weak var collectionView: UICollectionView! @@ -43,8 +47,8 @@ import XLForm var fileName = "" var fileNameExtension = "" var titleForm = "" - var listOfTemplate: [NKEditorTemplates] = [] - var selectTemplate: NKEditorTemplates? + var listOfTemplate: [NKEditorTemplate] = [] + var selectTemplate: NKEditorTemplate? let utilityFileSystem = NCUtilityFileSystem() let utility = NCUtility() @@ -112,7 +116,7 @@ import XLForm XLFormViewController.cellClassesForRowDescriptorTypes()["kNMCFolderCustomCellType"] = FolderPathCustomCell.self row = XLFormRowDescriptor(tag: "ButtonDestinationFolder", rowType: "kNMCFolderCustomCellType", title: "") row.action.formSelector = #selector(changeDestinationFolder(_:)) - row.cellConfig["folderImage.image"] = UIImage(named: "folder")!.imageColor(NCBrandColor.shared.customer) + row.cellConfig["folderImage.image"] = UIImage(named: "folder")!.withTintColor(NCBrandColor.shared.customer) row.cellConfig["photoLabel.textAlignment"] = NSTextAlignment.left.rawValue row.cellConfig["photoLabel.font"] = UIFont.systemFont(ofSize: 15.0) row.cellConfig["photoLabel.textColor"] = UIColor.label //photos @@ -275,58 +279,62 @@ import XLForm @objc func save() { - guard let selectTemplate = self.selectTemplate else { return } - templateIdentifier = selectTemplate.identifier - - let rowFileName: XLFormRowDescriptor = self.form.formRow(withTag: "fileName")! - var fileName = rowFileName.value as? String - if fileName?.isEmpty ?? false || fileName == nil { - fileName = NCUtilityFileSystem().createFileNameDate("Text", ext: getFileExtension()) - } else if fileName?.trimmingCharacters(in: .whitespaces).isEmpty ?? false { - let alert = UIAlertController(title: "", message: NSLocalizedString("_please_enter_file_name_", comment: ""), preferredStyle: .alert) - alert.addAction(UIAlertAction(title: NSLocalizedString("_ok_", comment: ""), style: .cancel, handler: nil)) - self.present(alert, animated: true) - return - } - - // Ensure fileName is not nil or empty - guard var fileNameForm: String = fileName, !fileNameForm.isEmpty else { return } - - // Trim whitespaces and newlines - fileNameForm = fileNameForm.trimmingCharacters(in: .whitespacesAndNewlines) - - fileName = FileAutoRenamer.rename(fileNameForm, account: session.account) - - let result = NextcloudKit.shared.nkCommonInstance.getInternalType(fileName: fileNameForm, mimeType: "", directory: false, account: session.account - ) - if utility.editorsDirectEditing(account: session.account, contentType: result.mimeType).isEmpty { - fileNameForm = (fileNameForm as NSString).deletingPathExtension + "." + fileNameExtension - } - - // verify if already exists - if NCManageDatabase.shared.getMetadata(predicate: NSPredicate(format: "account == %@ AND serverUrl == %@ AND fileNameView == %@", session.account, self.serverUrl, fileNameForm)) != nil { - NCContentPresenter().showError(error: NKError(errorCode: 0, errorDescription: "_rename_already_exists_")) - return -// } -// -// if NCManageDatabase.shared.getMetadataConflict(account: session.account, serverUrl: serverUrl, fileNameView: String(describing: fileNameForm), nativeFormat: false) != nil { -// -// let metadataForUpload = NCManageDatabase.shared.createMetadata(fileName: String(describing: fileNameForm), fileNameView: String(describing: fileNameForm), ocId: UUID().uuidString, serverUrl: serverUrl, url: "", contentType: "", session: session, sceneIdentifier: self.appDelegate.sceneIdentifier) -// -// guard let conflict = UIStoryboard(name: "NCCreateFormUploadConflict", bundle: nil).instantiateInitialViewController() as? NCCreateFormUploadConflict else { return } -// -// conflict.textLabelDetailNewFile = NSLocalizedString("_now_", comment: "") -// conflict.alwaysNewFileNameNumber = true -// conflict.serverUrl = serverUrl -// conflict.metadatasUploadInConflict = [metadataForUpload] -// conflict.delegate = self -// -// self.present(conflict, animated: true, completion: nil) - - } else { - - let fileNamePath = utilityFileSystem.getFileNamePath(String(describing: fileNameForm), serverUrl: serverUrl, session: session) - createDocument(fileNamePath: fileNamePath, fileName: String(describing: fileNameForm)) + Task { + guard let selectTemplate = self.selectTemplate else { return } + templateIdentifier = selectTemplate.identifier + + let rowFileName: XLFormRowDescriptor = self.form.formRow(withTag: "fileName")! + var fileName = rowFileName.value as? String + if fileName?.isEmpty ?? false || fileName == nil { + fileName = NCUtilityFileSystem().createFileNameDate("Text", ext: getFileExtension()) + } else if fileName?.trimmingCharacters(in: .whitespaces).isEmpty ?? false { + let alert = UIAlertController(title: "", message: NSLocalizedString("_please_enter_file_name_", comment: ""), preferredStyle: .alert) + alert.addAction(UIAlertAction(title: NSLocalizedString("_ok_", comment: ""), style: .cancel, handler: nil)) + self.present(alert, animated: true) + return + } + + // Ensure fileName is not nil or empty + guard var fileNameForm: String = fileName, !fileNameForm.isEmpty else { return } + + // Trim whitespaces and newlines + fileNameForm = fileNameForm.trimmingCharacters(in: .whitespacesAndNewlines) + + let fileAutoRenamer = FileAutoRenamer() + fileName = fileAutoRenamer.rename(filename: fileNameForm, isFolderPath: true) + + let result = await NKTypeIdentifiers.shared.getInternalType(fileName: fileNameForm, mimeType: "", directory: false, account: session.account) + + if utility.editorsDirectEditing(account: session.account, contentType: result.mimeType).isEmpty { + fileNameForm = (fileNameForm as NSString).deletingPathExtension + "." + fileNameExtension + } + + // verify if already exists + if NCManageDatabase.shared.getMetadata(predicate: NSPredicate(format: "account == %@ AND serverUrl == %@ AND fileNameView == %@", session.account, self.serverUrl, fileNameForm)) != nil { + NCContentPresenter().showError(error: NKError(errorCode: 0, errorDescription: "_rename_already_exists_")) + return + // } + // + // if NCManageDatabase.shared.getMetadataConflict(account: session.account, serverUrl: serverUrl, fileNameView: String(describing: fileNameForm), nativeFormat: false) != nil { + // + // let metadataForUpload = NCManageDatabase.shared.createMetadata(fileName: String(describing: fileNameForm), fileNameView: String(describing: fileNameForm), ocId: UUID().uuidString, serverUrl: serverUrl, url: "", contentType: "", session: session, sceneIdentifier: self.appDelegate.sceneIdentifier) + // + // guard let conflict = UIStoryboard(name: "NCCreateFormUploadConflict", bundle: nil).instantiateInitialViewController() as? NCCreateFormUploadConflict else { return } + // + // conflict.textLabelDetailNewFile = NSLocalizedString("_now_", comment: "") + // conflict.alwaysNewFileNameNumber = true + // conflict.serverUrl = serverUrl + // conflict.metadatasUploadInConflict = [metadataForUpload] + // conflict.delegate = self + // + // self.present(conflict, animated: true, completion: nil) + + } else { + + let fileNamePath = utilityFileSystem.getFileNamePath(String(describing: fileNameForm), serverUrl: serverUrl, session: session) + await NCCreateDocument().createDocument(controller: controller, fileNamePath: fileNamePath, fileName: String(describing: fileNameForm), fileNameExtension: self.fileNameExtension, editorId: editorId, creatorId: creatorId, templateId: templateIdentifier, account: session.account) + + } } } @@ -335,7 +343,11 @@ import XLForm if let metadatas, metadatas.count > 0 { let fileName = metadatas[0].fileName let fileNamePath = utilityFileSystem.getFileNamePath(fileName, serverUrl: serverUrl, session: session) - createDocument(fileNamePath: fileNamePath, fileName: fileName) +// createDocument(fileNamePath: fileNamePath, fileName: fileName) + Task { + await NCCreateDocument().createDocument(controller: controller, fileNamePath: fileNamePath, fileName: String(describing: fileName), editorId: editorId, creatorId: creatorId, templateId: templateIdentifier, account: session.account) + } + } else { DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) { self.cancel() @@ -351,54 +363,62 @@ import XLForm if self.editorId == NCGlobal.shared.editorText || self.editorId == NCGlobal.shared.editorOnlyoffice { - var options = NKRequestOptions() - if self.editorId == NCGlobal.shared.editorOnlyoffice { - options = NKRequestOptions(customUserAgent: utility.getCustomUserAgentOnlyOffice()) - } else if editorId == NCGlobal.shared.editorText { - options = NKRequestOptions(customUserAgent: utility.getCustomUserAgentNCText()) - } - - NextcloudKit.shared.textCreateFile(fileNamePath: fileNamePath, editorId: editorId, creatorId: creatorId, templateId: templateIdentifier, account: session.account, options: options) { account, url, _, error in - guard error == .success, account == self.session.account, let url = url else { - self.navigationItem.rightBarButtonItem?.isEnabled = true - NCContentPresenter().showError(error: error) - return - } - - var results = NextcloudKit.shared.nkCommonInstance.getInternalType(fileName: fileName, mimeType: "", directory: false, account: self.session.account) - // FIXME: iOS 12.0,* don't detect UTI text/markdown, text/x-markdown - if results.mimeType.isEmpty { - results.mimeType = "text/x-markdown" + Task { + var options = NKRequestOptions() + if self.editorId == NCGlobal.shared.editorOnlyoffice { + options = NKRequestOptions(customUserAgent: utility.getCustomUserAgentOnlyOffice()) + } else if editorId == NCGlobal.shared.editorText { + options = NKRequestOptions(customUserAgent: utility.getCustomUserAgentNCText()) } - - self.dismiss(animated: true, completion: { - let metadata = NCManageDatabase.shared.createMetadata(fileName: fileName, fileNameView: fileName, ocId: UUID, serverUrl: self.serverUrl, url: url, contentType: results.mimeType, session: self.session, sceneIdentifier: self.appDelegate.sceneIdentifier) - if let viewController = self.appDelegate.activeViewController { -// NCViewer().view(viewController: viewController, metadata: metadata) - NCViewer().view(viewController: viewController, metadata: metadata, metadatas: [metadata]) + + let results = await NextcloudKit.shared.textCreateFileAsync(fileNamePath: fileNamePath, editorId: editorId, creatorId: creatorId, templateId: templateIdentifier, account: session.account, options: options) { task in + Task { + let identifier = await NCNetworking.shared.networkingTasks.createIdentifier(account: self.session.account, + path: fileNamePath, + name: "textCreateFile") + await NCNetworking.shared.networkingTasks.track(identifier: identifier, task: task) } - }) + } + guard results.error == .success, let url = results.url else { + return NCContentPresenter().showError(error: results.error) + } + let metadata = await NCManageDatabase.shared.createMetadataAsync(fileName: fileName, + ocId: UUID, + serverUrl: serverUrl, + url: url, + session: session, + sceneIdentifier: controller.sceneIdentifier) + if let vc = await NCViewer().getViewerController(metadata: metadata, delegate: controller) { + controller.navigationController?.pushViewController(vc, animated: true) + } } } if self.editorId == NCGlobal.shared.editorCollabora { - - NextcloudKit.shared.createRichdocuments(path: fileNamePath, templateId: templateIdentifier, account: session.account) { account, url, _, error in - guard error == .success, account == self.session.account, let url = url else { - self.navigationItem.rightBarButtonItem?.isEnabled = true - NCContentPresenter().showError(error: error) - return - } - - self.dismiss(animated: true, completion: { - let createFileName = (fileName as NSString).deletingPathExtension + "." + self.fileNameExtension - let metadata = NCManageDatabase.shared.createMetadata(fileName: createFileName, fileNameView: createFileName, ocId: UUID, serverUrl: self.serverUrl, url: url, contentType: "", session: self.session, sceneIdentifier: self.appDelegate.sceneIdentifier) - AnalyticsHelper.shared.trackCreateFile(metadata: metadata) - if let viewController = self.appDelegate.activeViewController { -// NCViewer().view(viewController: viewController, metadata: metadata) - NCViewer().view(viewController: viewController, metadata: metadata, metadatas: [metadata]) + Task { + + let results = await NextcloudKit.shared.createRichdocumentsAsync(path: fileNamePath, templateId: templateIdentifier, account: session.account) { task in + Task { + let identifier = await NCNetworking.shared.networkingTasks.createIdentifier(account: self.session.account, + path: fileNamePath, + name: "CreateRichdocuments") + await NCNetworking.shared.networkingTasks.track(identifier: identifier, task: task) } - }) + } + guard results.error == .success, let url = results.url else { + return NCContentPresenter().showError(error: results.error) + } + + let metadata = await NCManageDatabase.shared.createMetadataAsync(fileName: fileName, + ocId: UUID, + serverUrl: serverUrl, + url: url, + session: session, + sceneIdentifier: controller.sceneIdentifier) + + if let vc = await NCViewer().getViewerController(metadata: metadata, delegate: controller) { + controller.navigationController?.pushViewController(vc, animated: true) + } } } } @@ -432,7 +452,7 @@ import XLForm for template in templates { - let temp = NKEditorTemplates() + var temp = NKEditorTemplate() temp.identifier = template.identifier temp.ext = template.ext @@ -452,7 +472,7 @@ import XLForm if self.listOfTemplate.isEmpty { - let temp = NKEditorTemplates() + var temp = NKEditorTemplate() temp.identifier = "" if self.editorId == NCGlobal.shared.editorText { @@ -489,14 +509,14 @@ import XLForm for template in templates! { - let temp = NKEditorTemplates() + var temp = NKEditorTemplate() temp.identifier = "\(template.templateId)" - temp.delete = template.delete +// temp.delete = template.delete temp.ext = template.ext temp.name = template.name temp.preview = template.preview - temp.type = template.type +// temp.type = template.type self.listOfTemplate.append(temp) @@ -511,7 +531,7 @@ import XLForm if self.listOfTemplate.isEmpty { - let temp = NKEditorTemplates() + var temp = NKEditorTemplate() temp.identifier = "" if self.typeTemplate == NCGlobal.shared.templateDocument {