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/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/Data/NCManageDatabase+Metadata.swift b/iOSClient/Data/NCManageDatabase+Metadata.swift index f03ef6cf8d..39ab3d0da6 100644 --- a/iOSClient/Data/NCManageDatabase+Metadata.swift +++ b/iOSClient/Data/NCManageDatabase+Metadata.swift @@ -124,6 +124,7 @@ class tableMetadata: Object { @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" @@ -139,10 +140,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 == NKTypeClassFile.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 == NKTypeClassFile.document.rawValue + } + var isAudioOrVideo: Bool { return classFile == NKTypeClassFile.audio.rawValue || classFile == NKTypeClassFile.video.rawValue } @@ -171,11 +199,6 @@ extension tableMetadata { !directory } -#if !EXTENSION_FILE_PROVIDER_EXTENSION - @objc var isDirectoryE2EE: Bool { - return NCUtilityFileSystem().isDirectoryE2EE(serverUrl: serverUrl, urlBase: urlBase, userId: userId, account: account) - } - var isCopyableMovable: Bool { !isDirectoryE2EE && !e2eEncrypted } @@ -187,21 +210,12 @@ extension tableMetadata { return isPDF || isImage } - var isRenameable: Bool { - if !NCMetadataPermissions.canRename(self) { - return false - } - if lock { - return false - } - if !isDirectoryE2EE && e2eEncrypted { - return false + var isCreatable: Bool { + if isDirectory { + return NCMetadataPermissions.canCreateFolder(self) + } else { + return NCMetadataPermissions.canCreateFile(self) } - return true - } - - var canUnsetDirectoryAsE2EE: Bool { - return !isDirectoryE2EE && directory && size == 0 && e2eEncrypted && NCPreferences().isEndToEndEnabled(account: account) } var isDeletable: Bool { @@ -215,15 +229,44 @@ extension tableMetadata { 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 && !directory && !NCBrandOptions.shared.disable_openin_file + } + + var canSetDirectoryAsE2EE: Bool { + return directory && size == 0 && !e2eEncrypted && NCPreferences().isEndToEndEnabled(account: account) + } + + var canUnsetDirectoryAsE2EE: Bool { + return !isDirectoryE2EE && directory && size == 0 && e2eEncrypted && NCPreferences().isEndToEndEnabled(account: account) + } + + 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 { @@ -282,57 +325,22 @@ 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) } + // Return if is sharable + func isSharable() -> Bool { + 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 @@ -353,7 +361,6 @@ extension tableMetadata { } extension NCManageDatabase { -#if !EXTENSION func isMetadataShareOrMounted(metadata: tableMetadata, metadataFolder: tableMetadata?) -> Bool { var isShare = false var isMounted = false @@ -373,35 +380,26 @@ extension NCManageDatabase { } } - 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 + // MARK: - Realm Write - let results = realm.objects(tableMetadata.self) - .filter(predicate) - .sorted(by: sortDescriptors) + func addMetadataIfNeededAsync(_ metadata: tableMetadata, sync: Bool = true) { + let detached = metadata.detachedCopy() - let sliced = results.prefix(limit) - return sliced.map { $0.detachedCopy() } - } ?? [] + performRealmWrite(sync: sync) { realm in + if realm.object(ofType: tableMetadata.self, forPrimaryKey: metadata.ocId) == nil { + realm.add(detached) + } + } } -#endif - - // MARK: - Realm Write func addAndReturnMetadata(_ metadata: tableMetadata) -> tableMetadata? { let detached = metadata.detachedCopy() - core.performRealmWrite { realm in + performRealmWrite { realm in realm.add(detached, update: .all) } - return core.performRealmRead { realm in + return performRealmRead { realm in realm.objects(tableMetadata.self) .filter("ocId == %@", metadata.ocId) .first @@ -412,11 +410,11 @@ extension NCManageDatabase { func addAndReturnMetadataAsync(_ metadata: tableMetadata) async -> tableMetadata? { let detached = metadata.detachedCopy() - await core.performRealmWriteAsync { realm in + await performRealmWriteAsync { realm in realm.add(detached, update: .all) } - return await core.performRealmReadAsync { realm in + return await performRealmReadAsync { realm in realm.objects(tableMetadata.self) .filter("ocId == %@", metadata.ocId) .first? @@ -427,7 +425,7 @@ extension NCManageDatabase { func addMetadata(_ metadata: tableMetadata, sync: Bool = true) { let detached = metadata.detachedCopy() - core.performRealmWrite(sync: sync) { realm in + performRealmWrite(sync: sync) { realm in realm.add(detached, update: .all) } } @@ -435,7 +433,7 @@ extension NCManageDatabase { func addMetadataAsync(_ metadata: tableMetadata) async { let detached = metadata.detachedCopy() - await core.performRealmWriteAsync { realm in + await performRealmWriteAsync { realm in realm.add(detached, update: .all) } } @@ -443,7 +441,7 @@ extension NCManageDatabase { func addMetadatas(_ metadatas: [tableMetadata], sync: Bool = true) { let detached = metadatas.map { $0.detachedCopy() } - core.performRealmWrite(sync: sync) { realm in + performRealmWrite(sync: sync) { realm in realm.add(detached, update: .all) } } @@ -451,23 +449,13 @@ extension NCManageDatabase { func addMetadatasAsync(_ metadatas: [tableMetadata]) async { let detached = metadatas.map { $0.detachedCopy() } - await core.performRealmWriteAsync { realm in + await performRealmWriteAsync { realm in realm.add(detached, update: .all) } } - func addMetadataIfNotExistsAsync(_ metadata: tableMetadata) async { - let detached = metadata.detachedCopy() - - await core.performRealmWriteAsync { realm in - if realm.object(ofType: tableMetadata.self, forPrimaryKey: metadata.ocId) == nil { - realm.add(detached) - } - } - } - func deleteMetadataAsync(predicate: NSPredicate) async { - await core.performRealmWriteAsync { realm in + await performRealmWriteAsync { realm in let result = realm.objects(tableMetadata.self) .filter(predicate) realm.delete(result) @@ -477,47 +465,29 @@ extension NCManageDatabase { func deleteMetadataAsync(id: String?) async { guard let id else { return } - await core.performRealmWriteAsync { realm in + await performRealmWriteAsync { realm in let result = realm.objects(tableMetadata.self) .filter("ocId == %@ OR fileId == %@", id, id) realm.delete(result) } } - func deleteMetadataAsync(ocId: String) async { - await core.performRealmWriteAsync { realm in - if let object = realm.object(ofType: tableMetadata.self, forPrimaryKey: ocId) { - realm.delete(object) - } + 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 replaceMetadataAsync(ocId: String, metadata: tableMetadata) async { + func replaceMetadataAsync(id: 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) - } - realm.add(detached, update: .modified) - } - } - - 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()) - } - - await core.performRealmWriteAsync { realm in - let results = realm.objects(tableMetadata.self) - .filter("ocId IN %@", ocId) - realm.delete(results) - realm.add(detacheds, update: .all) + await performRealmWriteAsync { realm in + let result = realm.objects(tableMetadata.self) + .filter("ocId == %@ OR ocIdTransfer == %@", id, id) + realm.delete(result) + realm.add(detached, update: .all) } } @@ -529,7 +499,7 @@ extension NCManageDatabase { } let detached = metadatas.map { $0.detachedCopy() } - await core.performRealmWriteAsync { realm in + await performRealmWriteAsync { realm in for detached in detached { if let managed = realm.object(ofType: tableMetadata.self, forPrimaryKey: detached.ocId) { realm.delete(managed) @@ -539,14 +509,13 @@ extension NCManageDatabase { } func renameMetadata(fileNameNew: String, ocId: String, status: Int = NCGlobal.shared.metadataStatusNormal) async { - await core.performRealmWriteAsync { realm in + await performRealmWriteAsync { realm in guard let metadata = realm.objects(tableMetadata.self) .filter("ocId == %@", ocId) .first else { return } - let utilityFileSystem = NCUtilityFileSystem() let oldFileNameView = metadata.fileNameView let account = metadata.account let originalServerUrl = metadata.serverUrl @@ -557,8 +526,8 @@ extension NCManageDatabase { metadata.sessionDate = (status == NCGlobal.shared.metadataStatusNormal) ? nil : Date() if metadata.directory { - let oldDirUrl = utilityFileSystem.createServerUrl(serverUrl: originalServerUrl, fileName: oldFileNameView) - let newDirUrl = utilityFileSystem.createServerUrl(serverUrl: originalServerUrl, fileName: fileNameNew) + 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) @@ -566,9 +535,56 @@ extension NCManageDatabase { dir.serverUrl = newDirUrl } } 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) + 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) + } + } + } + + 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 + + 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 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, 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) + } } } } @@ -576,7 +592,7 @@ extension NCManageDatabase { /// 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 + await performRealmWriteAsync { realm in guard let result = realm.objects(tableMetadata.self) .filter("ocId == %@", ocId) .first, @@ -586,9 +602,8 @@ extension NCManageDatabase { return } - let utilityFileSystem = NCUtilityFileSystem() let fileIdMOV = result.livePhotoFile - let directoryServerUrl = utilityFileSystem.createServerUrl(serverUrl: result.serverUrl, fileName: result.fileNameView) + let directoryServerUrl = self.utilityFileSystem.createServerUrl(serverUrl: result.serverUrl, fileName: result.fileNameView) let lastPathComponent = url.lastPathComponent let fileName = lastPathComponent.removingPercentEncoding ?? lastPathComponent let fileNameView = result.fileNameView @@ -602,12 +617,12 @@ extension NCManageDatabase { let resultDirectory = realm.objects(tableDirectory.self) .filter("account == %@ AND serverUrl == %@", result.account, directoryServerUrl) .first { - let serverUrlTo = utilityFileSystem.createServerUrl(serverUrl: result.serverUrl, fileName: fileName) + let serverUrlTo = self.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) + 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, @@ -622,19 +637,31 @@ extension NCManageDatabase { 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, 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) + } + } + } + + 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 } } } func setMetadataServerUrlFileNameStatusNormalAsync(ocId: String) async { - await core.performRealmWriteAsync { realm in + await 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.serverUrlFileName = self.utilityFileSystem.createServerUrl(serverUrl: result.serverUrl, fileName: result.fileName) result.status = NCGlobal.shared.metadataStatusNormal result.sessionDate = nil } @@ -644,7 +671,7 @@ extension NCManageDatabase { func setMetadataLivePhotoByServerAsync(account: String, ocId: String, livePhotoFile: String) async { - await core.performRealmWriteAsync { realm in + await performRealmWriteAsync { realm in if let result = realm.objects(tableMetadata.self) .filter("account == %@ AND ocId == %@", account, ocId) .first { @@ -657,7 +684,7 @@ extension NCManageDatabase { func updateMetadatasFavoriteAsync(account: String, metadatas: [tableMetadata]) async { guard !metadatas.isEmpty else { return } - await core.performRealmWriteAsync { realm in + await performRealmWriteAsync { realm in let oldFavorites = realm.objects(tableMetadata.self) .filter("account == %@ AND favorite == true", account) for item in oldFavorites { @@ -680,7 +707,7 @@ extension NCManageDatabase { /// - 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 + await performRealmWriteAsync { realm in let ocIdsToSkip = Set( realm.objects(tableMetadata.self) .filter("status != %d", NCGlobal.shared.metadataStatusNormal) @@ -690,13 +717,7 @@ extension NCManageDatabase { 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)) - } - ) + let metadatasCopy = Array(resultsToDelete).map { tableMetadata(value: $0) } realm.delete(resultsToDelete) @@ -704,17 +725,25 @@ extension NCManageDatabase { guard !ocIdsToSkip.contains(metadata.ocId) else { continue } - if let previous = metadatasByOcId[metadata.ocId] { - metadata.mediaSearch = previous.mediaSearch + if let match = metadatasCopy.first(where: { $0.ocId == metadata.ocId }) { + metadata.mediaSearch = match.mediaSearch } - realm.add(metadata.detachedCopy(), update: .all) } } } + 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 core.performRealmWriteAsync { realm in + await performRealmWriteAsync { realm in let result = realm.objects(tableMetadata.self) .filter("ocId == %@", ocId) .first @@ -722,8 +751,17 @@ extension NCManageDatabase { } } + 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 core.performRealmWriteAsync { realm in + await performRealmWriteAsync { realm in let result = realm.objects(tableMetadata.self) .filter("account == %@ AND serverUrl == %@ AND fileName == %@", account, serverUrl, fileName) .first @@ -731,8 +769,18 @@ extension NCManageDatabase { } } + 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 + } + } + } + func moveMetadataAsync(ocId: String, serverUrlTo: String) async { - await core.performRealmWriteAsync { realm in + await performRealmWriteAsync { realm in if let result = realm.objects(tableMetadata.self) .filter("ocId == %@", ocId) .first { @@ -742,7 +790,7 @@ extension NCManageDatabase { } func setLivePhotoFile(fileId: String, livePhotoFile: String) async { - await core.performRealmWriteAsync { realm in + await performRealmWriteAsync { realm in let result = realm.objects(tableMetadata.self) .filter("fileId == %@", fileId) .first @@ -751,7 +799,7 @@ extension NCManageDatabase { } func clearAssetLocalIdentifiersAsync(_ assetLocalIdentifiers: [String]) async { - await core.performRealmWriteAsync { realm in + await performRealmWriteAsync { realm in let results = realm.objects(tableMetadata.self) .filter("assetLocalIdentifier IN %@", assetLocalIdentifiers) for result in results { @@ -760,10 +808,29 @@ extension NCManageDatabase { } } + 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() + } + } + } + /// 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 + await performRealmWriteAsync { realm in guard let result = realm.objects(tableMetadata.self) .filter("ocId == %@", ocId) .first else { @@ -780,9 +847,27 @@ extension NCManageDatabase { } } + 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 + } else { + result.sessionDate = Date() + } + } + } + } + /// 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 + await performRealmWriteAsync { realm in guard let result = realm.objects(tableMetadata.self) .filter("ocId == %@", ocId) .first else { @@ -796,8 +881,16 @@ extension NCManageDatabase { } } + 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) + } + } + func clearMetadatasUploadAsync(account: String) async { - await core.performRealmWriteAsync { realm in + 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) @@ -821,7 +914,7 @@ extension NCManageDatabase { let toDeleteKeys = Array(toDeleteOcIds) - await core.performRealmWriteAsync { realm in + await performRealmWriteAsync { realm in let toAdd = remoteMetadatas.filter { toAddOcIds.contains($0.ocId) } let toDelete = toDeleteKeys.compactMap { realm.object(ofType: tableMetadata.self, forPrimaryKey: $0) @@ -837,13 +930,13 @@ extension NCManageDatabase { // MARK: - Realm Read func getAllTableMetadataAsync() async -> [tableMetadata] { - return await core.performRealmReadAsync { realm in + return await performRealmReadAsync { realm in realm.objects(tableMetadata.self).map { tableMetadata(value: $0) } } ?? [] } func getMetadata(predicate: NSPredicate) -> tableMetadata? { - return core.performRealmRead { realm in + return performRealmRead { realm in realm.objects(tableMetadata.self) .filter(predicate) .first @@ -852,7 +945,7 @@ extension NCManageDatabase { } func getMetadataAsync(predicate: NSPredicate) async -> tableMetadata? { - return await core.performRealmReadAsync { realm in + return await performRealmReadAsync { realm in realm.objects(tableMetadata.self) .filter(predicate) .first @@ -860,26 +953,46 @@ extension NCManageDatabase { } } - func getResultsMetadatasAsync(predicate: NSPredicate) async -> Results? { - await core.performRealmReadAsync { realm in - let results = realm.objects(tableMetadata.self) - .filter(predicate) - return results.freeze() - } - } - func getMetadatas(predicate: NSPredicate) -> [tableMetadata] { - core.performRealmRead { realm in + 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() + 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("Could not access database: \(error)") + } + return nil + } func getMetadatas(predicate: NSPredicate, sortedByKeyPath: String, ascending: Bool = false) -> [tableMetadata]? { - return core.performRealmRead { realm in + return performRealmRead { realm in realm.objects(tableMetadata.self) .filter(predicate) .sorted(byKeyPath: sortedByKeyPath, ascending: ascending) @@ -891,7 +1004,7 @@ extension NCManageDatabase { sortedByKeyPath: String, ascending: Bool = false, limit: Int? = nil) async -> [tableMetadata]? { - return await core.performRealmReadAsync { realm in + return await performRealmReadAsync { realm in let results = realm.objects(tableMetadata.self) .filter(predicate) .sorted(byKeyPath: sortedByKeyPath, @@ -910,7 +1023,7 @@ extension NCManageDatabase { numItems: Int, sorted: String, ascending: Bool) -> [tableMetadata] { - return core.performRealmRead { realm in + return performRealmRead { realm in let results = realm.objects(tableMetadata.self) .filter(predicate) .sorted(byKeyPath: sorted, ascending: ascending) @@ -922,7 +1035,7 @@ extension NCManageDatabase { func getMetadataFromOcId(_ ocId: String?) -> tableMetadata? { guard let ocId else { return nil } - return core.performRealmRead { realm in + return performRealmRead { realm in realm.objects(tableMetadata.self) .filter("ocId == %@", ocId) .first @@ -933,7 +1046,7 @@ extension NCManageDatabase { func getMetadataFromOcIdAsync(_ ocId: String?) async -> tableMetadata? { guard let ocId else { return nil } - return await core.performRealmReadAsync { realm in + return await performRealmReadAsync { realm in realm.objects(tableMetadata.self) .filter("ocId == %@", ocId) .first @@ -946,7 +1059,7 @@ extension NCManageDatabase { return nil } - return await core.performRealmReadAsync { realm in + return await performRealmReadAsync { realm in realm.objects(tableMetadata.self) .filter("ocId == %@ OR ocIdTransfer == %@", ocId, ocId) .first @@ -959,18 +1072,18 @@ extension NCManageDatabase { func getMetadataFolderAsync(session: NCSession.Session, serverUrl: String) async -> tableMetadata? { var serverUrl = serverUrl var fileName = "" - let home = NCUtilityFileSystem().getHomeServer(session: session) + let home = utilityFileSystem.getHomeServer(session: session) if home == serverUrl { fileName = NextcloudKit.shared.nkCommonInstance.rootFileName } else { fileName = (serverUrl as NSString).lastPathComponent - if let serverDirectoryUp = NCUtilityFileSystem().serverDirectoryUp(serverUrl: serverUrl, home: home) { + if let serverDirectoryUp = utilityFileSystem.serverDirectoryUp(serverUrl: serverUrl, home: home) { serverUrl = serverDirectoryUp } } - return await core.performRealmReadAsync { realm in + return await performRealmReadAsync { realm in realm.objects(tableMetadata.self) .filter("account == %@ AND serverUrl == %@ AND fileName == %@", session.account, serverUrl, fileName) .first @@ -984,7 +1097,7 @@ extension NCManageDatabase { } let detached = metadata.detachedCopy() - return core.performRealmRead { realm in + return performRealmRead { realm in realm.objects(tableMetadata.self) .filter(NSPredicate(format: "account == %@ AND serverUrl == %@ AND fileId == %@", detached.account, @@ -1001,7 +1114,7 @@ extension NCManageDatabase { } let detached = metadata.detachedCopy() - return await core.performRealmReadAsync { realm in + return await performRealmReadAsync { realm in realm.objects(tableMetadata.self) .filter(NSPredicate(format: "account == %@ AND serverUrl == %@ AND fileId == %@", detached.account, @@ -1026,35 +1139,15 @@ 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) - - 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 ?? [:] - } - -#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) + let homeServerUrl = utilityFileSystem.getHomeServer(session: session) - let detachedMetadatas: [tableMetadata] = await core.performRealmReadAsync { realm in + let detachedMetadatas: [tableMetadata] = await performRealmReadAsync { realm in var ocIds: [String] = [] // Safely fetch and detach groupfolders @@ -1086,10 +1179,18 @@ extension NCManageDatabase { let sorted = await self.sortedMetadata(layoutForView: layoutForView, account: session.account, metadatas: detachedMetadatas) return sorted } -#endif + + 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() } + } + } func getRootContainerMetadataAsync(accout: String) async -> tableMetadata? { - return await core.performRealmReadAsync { realm in + return await performRealmReadAsync { realm in realm.objects(tableMetadata.self) .filter("fileName == %@ AND account == %@", NextcloudKit.shared.nkCommonInstance.rootFileName, accout) .first @@ -1098,15 +1199,34 @@ extension NCManageDatabase { } func getMetadatasAsync(predicate: NSPredicate) async -> [tableMetadata] { - await core.performRealmReadAsync { realm in + await performRealmReadAsync { realm in realm.objects(tableMetadata.self) .filter(predicate) .map { $0.detachedCopy() } } ?? [] } + 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 + return await performRealmReadAsync { realm in let results = realm.objects(tableMetadata.self).filter("assetLocalIdentifier != ''") return results.map { $0.assetLocalIdentifier } } @@ -1117,7 +1237,7 @@ extension NCManageDatabase { return nil } - return core.performRealmRead { realm in + return performRealmRead { realm in realm.objects(tableMetadata.self) .filter("fileId == %@", fileId) .first @@ -1133,7 +1253,7 @@ extension NCManageDatabase { return nil } - return await core.performRealmReadAsync { realm in + return await performRealmReadAsync { realm in let object = realm.objects(tableMetadata.self) .filter("fileId == %@", fileId) .first @@ -1141,12 +1261,11 @@ extension NCManageDatabase { } } -#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 + let detachedMetadatas = await performRealmReadAsync { realm in realm.objects(tableMetadata.self) .filter(predicate) .map { $0.detachedCopy() } @@ -1172,7 +1291,7 @@ extension NCManageDatabase { predicate = predicateSource } - let detachedMetadatas = await core.performRealmReadAsync { realm in + let detachedMetadatas = await performRealmReadAsync { realm in realm.objects(tableMetadata.self) .filter(predicate) .map { $0.detachedCopy() } @@ -1183,12 +1302,11 @@ extension NCManageDatabase { return sorted } -#endif func getMetadatasAsync(predicate: NSPredicate, withSort sortDescriptors: [RealmSwift.SortDescriptor] = [], withLimit limit: Int? = nil) async -> [tableMetadata]? { - await core.performRealmReadAsync { realm in + await performRealmReadAsync { realm in var results = realm.objects(tableMetadata.self) .filter(predicate) @@ -1206,7 +1324,7 @@ extension NCManageDatabase { } func hasUploadingMetadataWithChunksOrE2EE() -> Bool { - return core.performRealmRead { realm in + return performRealmRead { realm in realm.objects(tableMetadata.self) .filter("status == %d AND (chunk > 0 OR e2eEncrypted == true)", NCGlobal.shared.metadataStatusUploading) .first != nil @@ -1226,7 +1344,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 @@ -1247,46 +1365,11 @@ extension NCManageDatabase { 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 return object?.detachedCopy() } } - - 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) - ] - - let results = realm.objects(tableMetadata.self) - .filter(predicate) - .sorted(by: sortDescriptors) - - let excludedIds = Set(tranfersSuccess.compactMap { $0.ocIdTransfer }) - let filtered = results.filter { !excludedIds.contains($0.ocIdTransfer) } - - return filtered.map { $0.detachedCopy() } - } ?? [] - } - - func getMetadatasInWaitingCountAsync() async -> Int { - await core.performRealmReadAsync { realm in - realm.objects(tableMetadata.self) - .filter("status IN %@", NCGlobal.shared.metadatasStatusInWaiting) - .count - } ?? 0 - } - - func metadataExistsAsync(predicate: NSPredicate) async -> Bool { - await core.performRealmReadAsync { realm in - realm.objects(tableMetadata.self) - .filter(predicate) - .first != nil - } ?? 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..01003052ec --- /dev/null +++ b/iOSClient/Main/Create/NCCreateFormUploadDocuments.swift @@ -0,0 +1,595 @@ +// +// 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 { + 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! + @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: [NKEditorTemplate] = [] + var selectTemplate: NKEditorTemplate? + let utilityFileSystem = NCUtilityFileSystem() + let utility = NCUtility() + + // Layout + let numItems = 2 + 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(session: session) { + fileNameFolder = "/" + } else { + fileNameFolder = utilityFileSystem.getTextServerUrl(session: session, serverUrl: serverUrl)//(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")!.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 + 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(session: session) { + 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() { + + 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) + + } + } + } + + func dismissCreateFormUploadConflict(metadatas: [tableMetadata]?) { + + 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) + 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() + } + } + } + + 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 { + + 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()) + } + + 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 { + 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) + } + } + } + } + + @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.textGetListOfTemplates(account: session.account, options: options) { account, templates, _, error in + + self.indicator.stopAnimating() + + if error == .success && account == self.session.account, let templates = templates { + + for template in templates { + + var temp = NKEditorTemplate() + + 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 { + + var temp = NKEditorTemplate() + + 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: session.account) { account, templates, _, error in + + self.indicator.stopAnimating() + + if error == .success && account == self.session.account { + + for template in templates! { + + var temp = NKEditorTemplate() + + 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 { + + var temp = NKEditorTemplate() + + 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, account: session.account, requestHandler: { _ in + + }, taskHandler: { _ in + + }, progressHandler: { _ in + + }) { account, _, _, _, _, _, error in + + if error == .success && account == self.session.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..cb7553eda7 --- /dev/null +++ b/iOSClient/NMC Custom Views/FolderPathCustomCell.swift @@ -0,0 +1,34 @@ +// +// FolderPathCustomCell.swift +// Nextcloud +// +// Created by A200073704 on 04/05/23. +// Copyright © 2023 Marino Faggiana. All rights reserved. +// + +import UIKit +import XLForm + +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..caca063abf --- /dev/null +++ b/iOSClient/NMC Custom Views/FolderPathCustomCell.xib @@ -0,0 +1,75 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/iOSClient/NMC Custom Views/NCCreateDocumentCustomTextField.swift b/iOSClient/NMC Custom Views/NCCreateDocumentCustomTextField.swift new file mode 100644 index 0000000000..3983e413a6 --- /dev/null +++ b/iOSClient/NMC Custom Views/NCCreateDocumentCustomTextField.swift @@ -0,0 +1,71 @@ +// +// NCCreateDocumentCustomTextField.swift +// Nextcloud +// +// Created by A200073704 on 04/05/23. +// Copyright © 2023 Marino Faggiana. All rights reserved. +// + +import UIKit +import XLForm + +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 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +