From bcdb595a49ee7a3b1ef9b4a0ca15decb5b52ce00 Mon Sep 17 00:00:00 2001 From: Marino Faggiana Date: Thu, 3 Oct 2024 16:00:08 +0200 Subject: [PATCH] Offline rename (#3086) * rename * rename e2ee --------- Signed-off-by: Marino Faggiana --- Brand/Database.swift | 2 +- .../FileProviderExtension+Actions.swift | 3 +- .../Data/NCManageDatabase+Metadata.swift | 111 ++++++++++- iOSClient/Data/NCManageDatabase.swift | 12 +- .../UIAlertController+Extension.swift | 34 ++-- iOSClient/Files/NCFiles.swift | 2 + .../NCCollectionViewCommon+SelectTabBar.swift | 5 +- .../NCCollectionViewCommon.swift | 9 +- iOSClient/Media/NCMedia+Command.swift | 5 +- .../Menu/NCCollectionViewCommon+Menu.swift | 3 +- iOSClient/Menu/NCContextMenu.swift | 2 +- iOSClient/NCGlobal.swift | 3 +- .../E2EE/NCNetworkingE2EERename.swift | 2 +- .../Networking/NCNetworking+AsyncAwait.swift | 2 +- iOSClient/Networking/NCNetworking+Task.swift | 12 +- .../Networking/NCNetworking+WebDAV.swift | 176 ++++++++---------- iOSClient/Networking/NCNetworking.swift | 2 +- .../Networking/NCNetworkingProcess.swift | 37 +++- .../en.lproj/Localizable.strings | 5 +- iOSClient/Transfers/NCTransfers.swift | 16 ++ iOSClient/Utility/NCUtility+Image.swift | 12 +- 21 files changed, 296 insertions(+), 159 deletions(-) diff --git a/Brand/Database.swift b/Brand/Database.swift index 7e7dba8c53..53a53f87ea 100644 --- a/Brand/Database.swift +++ b/Brand/Database.swift @@ -26,4 +26,4 @@ import Foundation // Database Realm // let databaseName = "nextcloud.realm" -let databaseSchemaVersion: UInt64 = 359 +let databaseSchemaVersion: UInt64 = 360 diff --git a/File Provider Extension/FileProviderExtension+Actions.swift b/File Provider Extension/FileProviderExtension+Actions.swift index 4cea0984e4..322fb9ff9e 100644 --- a/File Provider Extension/FileProviderExtension+Actions.swift +++ b/File Provider Extension/FileProviderExtension+Actions.swift @@ -147,7 +147,8 @@ extension FileProviderExtension { NextcloudKit.shared.moveFileOrFolder(serverUrlFileNameSource: fileNamePathFrom, serverUrlFileNameDestination: fileNamePathTo, overwrite: false, account: metadata.account) { account, error in if error == .success { // Rename metadata - self.database.renameMetadata(fileNameTo: itemName, ocId: ocId, account: account) + self.database.renameMetadata(fileNameNew: itemName, ocId: ocId) + self.database.setMetadataServeUrlFileNameStatusNormal(ocId: ocId) guard let metadata = self.database.getMetadataFromOcId(ocId) else { return completionHandler(nil, NSFileProviderError(.noSuchItem)) diff --git a/iOSClient/Data/NCManageDatabase+Metadata.swift b/iOSClient/Data/NCManageDatabase+Metadata.swift index cc84c82589..129a862b65 100644 --- a/iOSClient/Data/NCManageDatabase+Metadata.swift +++ b/iOSClient/Data/NCManageDatabase+Metadata.swift @@ -110,6 +110,7 @@ class tableMetadata: Object { @objc dynamic var richWorkspace: String? @objc dynamic var sceneIdentifier: String? @objc dynamic var serverUrl = "" + @objc dynamic var serveUrlFileName = "" @objc dynamic var session = "" @objc dynamic var sessionDate: Date? @objc dynamic var sessionError = "" @@ -392,6 +393,7 @@ extension NCManageDatabase { 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) @@ -514,6 +516,7 @@ extension NCManageDatabase { metadata.ocIdTransfer = ocId metadata.permissions = "RGDNVW" metadata.serverUrl = serverUrl + metadata.serveUrlFileName = serverUrl + "/" + fileName metadata.subline = subline metadata.uploadDate = Date() as NSDate metadata.url = url @@ -552,18 +555,16 @@ extension NCManageDatabase { @discardableResult func addMetadata(_ metadata: tableMetadata) -> tableMetadata? { - let result = tableMetadata(value: metadata) - do { let realm = try Realm() try realm.write { - realm.add(result, update: .all) + realm.add(tableMetadata(value: metadata), update: .all) } } catch let error { NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] Could not write to database: \(error)") return nil } - return result + return tableMetadata(value: metadata) } func addMetadatas(_ metadatas: [tableMetadata]) { @@ -614,17 +615,111 @@ extension NCManageDatabase { } } - func renameMetadata(fileNameTo: String, ocId: String, account: String) { + 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 resultsType = NextcloudKit.shared.nkCommonInstance.getInternalType(fileName: fileNameTo, mimeType: "", directory: result.directory, account: account) - result.fileName = fileNameTo - result.fileNameView = fileNameTo + 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 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) + } + } + } + } 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 + + 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) + } + + 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 + + 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 } } } catch let error { diff --git a/iOSClient/Data/NCManageDatabase.swift b/iOSClient/Data/NCManageDatabase.swift index e8b359035f..cac9b29f83 100644 --- a/iOSClient/Data/NCManageDatabase.swift +++ b/iOSClient/Data/NCManageDatabase.swift @@ -42,7 +42,7 @@ class NCManageDatabase: NSObject { override init() { func migrationSchema(_ migration: Migration, _ oldSchemaVersion: UInt64) { - if oldSchemaVersion < 359 { + if oldSchemaVersion < 360 { migration.deleteData(forType: tableMetadata.className()) migration.enumerateObjects(ofType: tableDirectory.className()) { _, newObject in newObject?["etag"] = "" @@ -51,11 +51,11 @@ class NCManageDatabase: NSObject { } func compactDB(_ totalBytes: Int, _ usedBytes: Int) -> Bool { - // totalBytes refers to the size of the file on disk in bytes (data + free space) - // usedBytes refers to the number of bytes used by data in the file - // Compact if the file is over 100MB in size and less than 50% 'used' - let oneHundredMB = 100 * 1024 * 1024 - return (totalBytes > oneHundredMB) && (Double(usedBytes) / Double(totalBytes)) < 0.5 + let usedPercentage = (Double(usedBytes) / Double(totalBytes)) * 100 + /// Compact the database if more than 25% of the space is free + let shouldCompact = (usedPercentage < 75.0) && (totalBytes > 100 * 1024 * 1024) + + return shouldCompact } var realm: Realm? let dirGroup = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: NCBrandOptions.shared.capabilitiesGroup) diff --git a/iOSClient/Extensions/UIAlertController+Extension.swift b/iOSClient/Extensions/UIAlertController+Extension.swift index 9bc5e2d7f8..bc70ae6501 100644 --- a/iOSClient/Extensions/UIAlertController+Extension.swift +++ b/iOSClient/Extensions/UIAlertController+Extension.swift @@ -35,10 +35,14 @@ extension UIAlertController { static func createFolder(serverUrl: String, account: String, markE2ee: Bool = false, sceneIdentifier: String? = nil, completion: ((_ error: NKError) -> Void)? = nil) -> UIAlertController { let alertController = UIAlertController(title: NSLocalizedString("_create_folder_", comment: ""), message: nil, preferredStyle: .alert) let session = NCSession.shared.getSession(account: account) + let isDirectoryEncrypted = NCUtilityFileSystem().isDirectoryE2EE(session: session, serverUrl: serverUrl) let okAction = UIAlertAction(title: NSLocalizedString("_save_", comment: ""), style: .default, handler: { _ in guard let fileNameFolder = alertController.textFields?.first?.text else { return } if markE2ee { + if NCNetworking.shared.isOffline { + return NCContentPresenter().showInfo(error: NKError(errorCode: NCGlobal.shared.errorInternalError, errorDescription: "_offline_not_allowed_")) + } Task { let createFolderResults = await NCNetworking.shared.createFolder(serverUrlFileName: serverUrl + "/" + fileNameFolder, account: session.account) if createFolderResults.error == .success { @@ -50,6 +54,15 @@ extension UIAlertController { NCContentPresenter().showError(error: createFolderResults.error) } } + } else if isDirectoryEncrypted { + if NCNetworking.shared.isOffline { + return NCContentPresenter().showInfo(error: NKError(errorCode: NCGlobal.shared.errorInternalError, errorDescription: "_offline_not_allowed_")) + } + #if !EXTENSION + Task { + await NCNetworkingE2EECreateFolder().createFolder(fileName: fileNameFolder, serverUrl: serverUrl, withPush: true, sceneIdentifier: sceneIdentifier, session: session) + } + #endif } else { let metadataForCreateFolder = NCManageDatabase.shared.createMetadata(fileName: fileNameFolder, fileNameView: fileNameFolder, @@ -122,9 +135,7 @@ extension UIAlertController { preferredStyle: .alert) if canDeleteServer { alertController.addAction(UIAlertAction(title: NSLocalizedString("_yes_", comment: ""), style: .destructive) { (_: UIAlertAction) in - for metadata in selectedMetadatas { - NCNetworking.shared.deleteMetadata(metadata) - } + NCNetworking.shared.deleteMetadatas(selectedMetadatas, sceneIdentifier: sceneIdentifier) NotificationCenter.default.postOnMainThread(name: NCGlobal.shared.notificationCenterReloadDataSource) completion(false) }) @@ -202,24 +213,17 @@ extension UIAlertController { let alertController = UIAlertController(title: NSLocalizedString("_rename_", comment: ""), message: nil, preferredStyle: .alert) let okAction = UIAlertAction(title: NSLocalizedString("_save_", comment: ""), style: .default, handler: { _ in - guard let newFileName = alertController.textFields?.first?.text else { return } + guard let fileNameNew = alertController.textFields?.first?.text else { return } // verify if already exists - if NCManageDatabase.shared.getMetadata(predicate: NSPredicate(format: "account == %@ AND serverUrl == %@ AND fileName == %@", metadata.account, metadata.serverUrl, newFileName)) != nil { + if NCManageDatabase.shared.getMetadata(predicate: NSPredicate(format: "account == %@ AND serverUrl == %@ AND fileName == %@", metadata.account, metadata.serverUrl, fileNameNew)) != nil { NCContentPresenter().showError(error: NKError(errorCode: 0, errorDescription: "_rename_already_exists_")) return } - NCActivityIndicator.shared.start() - - NCNetworking.shared.renameMetadata(metadata, fileNameNew: newFileName) { error in - - NCActivityIndicator.shared.stop() + NCNetworking.shared.renameMetadata(metadata, fileNameNew: fileNameNew) - if error != .success { - NCContentPresenter().showError(error: error) - } - } + NotificationCenter.default.postOnMainThread(name: NCGlobal.shared.notificationCenterReloadDataSource, userInfo: ["serverUrl": metadata.serverUrl]) }) // text field is initially empty, no action @@ -227,7 +231,7 @@ extension UIAlertController { let cancelAction = UIAlertAction(title: NSLocalizedString("_cancel_", comment: ""), style: .cancel) alertController.addTextField { textField in - textField.text = metadata.fileName + textField.text = metadata.fileNameView textField.autocapitalizationType = .words } diff --git a/iOSClient/Files/NCFiles.swift b/iOSClient/Files/NCFiles.swift index ded1f74c9d..1146eb3bef 100644 --- a/iOSClient/Files/NCFiles.swift +++ b/iOSClient/Files/NCFiles.swift @@ -175,6 +175,8 @@ class NCFiles: NCCollectionViewCommon { } else if self.dataSource.isEmpty() { self.collectionView.reloadData() } + } else if self.dataSource.isEmpty() { + self.collectionView.reloadData() } } } diff --git a/iOSClient/Main/Collection Common/NCCollectionViewCommon+SelectTabBar.swift b/iOSClient/Main/Collection Common/NCCollectionViewCommon+SelectTabBar.swift index 7605d7f82c..d4ef2c6322 100644 --- a/iOSClient/Main/Collection Common/NCCollectionViewCommon+SelectTabBar.swift +++ b/iOSClient/Main/Collection Common/NCCollectionViewCommon+SelectTabBar.swift @@ -44,11 +44,8 @@ extension NCCollectionViewCommon: NCCollectionViewCommonSelectTabBarDelegate { let canDeleteServer = metadatas.allSatisfy { !$0.lock } if canDeleteServer { - let copyMetadatas = metadatas alertController.addAction(UIAlertAction(title: NSLocalizedString("_yes_", comment: ""), style: .destructive) { _ in - for metadata in copyMetadatas { - NCNetworking.shared.deleteMetadata(metadata) - } + NCNetworking.shared.deleteMetadatas(metadatas, sceneIdentifier: self.controller?.sceneIdentifier) NotificationCenter.default.postOnMainThread(name: NCGlobal.shared.notificationCenterReloadDataSource) self.setEditMode(false) }) diff --git a/iOSClient/Main/Collection Common/NCCollectionViewCommon.swift b/iOSClient/Main/Collection Common/NCCollectionViewCommon.swift index 1b600a0a45..0f39268e5e 100644 --- a/iOSClient/Main/Collection Common/NCCollectionViewCommon.swift +++ b/iOSClient/Main/Collection Common/NCCollectionViewCommon.swift @@ -415,9 +415,16 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS @objc func renameFile(_ notification: NSNotification) { guard let userInfo = notification.userInfo as NSDictionary?, let account = userInfo["account"] as? String, - account == session.account + let serverUrl = userInfo["serverUrl"] as? String, + let error = userInfo["error"] as? NKError, + account == session.account, + serverUrl == self.serverUrl else { return } + if error != .success { + NCContentPresenter().showError(error: error) + } + reloadDataSource() } diff --git a/iOSClient/Media/NCMedia+Command.swift b/iOSClient/Media/NCMedia+Command.swift index 5a9eb9a6d8..ea709c3815 100644 --- a/iOSClient/Media/NCMedia+Command.swift +++ b/iOSClient/Media/NCMedia+Command.swift @@ -190,6 +190,7 @@ extension NCMedia: NCMediaSelectTabBarDelegate { let ocIds = self.fileSelect.map { $0 } var alertStyle = UIAlertController.Style.actionSheet var indexPaths: [IndexPath] = [] + var metadatas: [tableMetadata] = [] if UIDevice.current.userInterfaceIdiom == .pad { alertStyle = .alert } @@ -203,10 +204,12 @@ extension NCMedia: NCMediaSelectTabBarDelegate { for ocId in ocIds { if let metadata = self.database.getMetadataFromOcId(ocId) { - NCNetworking.shared.deleteMetadata(metadata) + metadatas.append(metadata) } } + NCNetworking.shared.deleteMetadatas(metadatas, sceneIdentifier: self.controller?.sceneIdentifier) + for index in indices { let indexPath = IndexPath(row: index, section: 0) if let cell = self.collectionView.cellForItem(at: indexPath) as? NCMediaCell, diff --git a/iOSClient/Menu/NCCollectionViewCommon+Menu.swift b/iOSClient/Menu/NCCollectionViewCommon+Menu.swift index 971dc5ec8d..6e912507c5 100644 --- a/iOSClient/Menu/NCCollectionViewCommon+Menu.swift +++ b/iOSClient/Menu/NCCollectionViewCommon+Menu.swift @@ -290,8 +290,7 @@ extension NCCollectionViewCommon { // // RENAME // - if NCNetworking.shared.isOnline, - metadata.isRenameable { + if metadata.isRenameable { actions.append( NCMenuAction( title: NSLocalizedString("_rename_", comment: ""), diff --git a/iOSClient/Menu/NCContextMenu.swift b/iOSClient/Menu/NCContextMenu.swift index f2e9ef1153..d494b27aba 100644 --- a/iOSClient/Menu/NCContextMenu.swift +++ b/iOSClient/Menu/NCContextMenu.swift @@ -157,7 +157,7 @@ class NCContextMenu: NSObject { } let alertController = UIAlertController(title: nil, message: nil, preferredStyle: alertStyle) alertController.addAction(UIAlertAction(title: NSLocalizedString("_delete_file_", comment: ""), style: .destructive) { _ in - NCNetworking.shared.deleteMetadata(metadata) + NCNetworking.shared.deleteMetadatas([metadata], sceneIdentifier: sceneIdentifier) NotificationCenter.default.postOnMainThread(name: NCGlobal.shared.notificationCenterReloadDataSource) }) alertController.addAction(UIAlertAction(title: NSLocalizedString("_cancel_", comment: ""), style: .cancel) { _ in }) diff --git a/iOSClient/NCGlobal.swift b/iOSClient/NCGlobal.swift index af15865e8a..8914a82851 100644 --- a/iOSClient/NCGlobal.swift +++ b/iOSClient/NCGlobal.swift @@ -272,6 +272,7 @@ class NCGlobal: NSObject { let metadataStatusWaitCreateFolder: Int = 10 let metadataStatusWaitDelete: Int = 11 + let metadataStatusWaitRename: Int = 12 let metadataStatusInTransfer = [-1, -2, 1, 2] let metadataStatusFileDown = [-1, -2, -3] @@ -315,7 +316,7 @@ class NCGlobal: NSObject { let notificationCenterDeleteFile = "deleteFile" // userInfo: [ocId], error let notificationCenterMoveFile = "moveFile" // userInfo: [ocId], error, dragdrop let notificationCenterCopyFile = "copyFile" // userInfo: [ocId], error, dragdrop - let notificationCenterRenameFile = "renameFile" // userInfo: ocId, account + let notificationCenterRenameFile = "renameFile" // userInfo: serverUrl, account, error let notificationCenterFavoriteFile = "favoriteFile" // userInfo: ocId, serverUrl let notificationCenterFileExists = "fileExists" // userInfo: ocId, fileExists diff --git a/iOSClient/Networking/E2EE/NCNetworkingE2EERename.swift b/iOSClient/Networking/E2EE/NCNetworkingE2EERename.swift index 48906ebe06..98aea3d44c 100644 --- a/iOSClient/Networking/E2EE/NCNetworkingE2EERename.swift +++ b/iOSClient/Networking/E2EE/NCNetworkingE2EERename.swift @@ -91,7 +91,7 @@ class NCNetworkingE2EERename: NSObject { // await networkingE2EE.unlock(account: metadata.account, serverUrl: metadata.serverUrl) - NotificationCenter.default.postOnMainThread(name: NCGlobal.shared.notificationCenterRenameFile, userInfo: ["ocId": metadata.ocId, "account": metadata.account]) + NotificationCenter.default.postOnMainThread(name: NCGlobal.shared.notificationCenterRenameFile, userInfo: ["serverUrl": metadata.serverUrl, "account": metadata.account, "error": uploadMetadataError]) return NKError() } diff --git a/iOSClient/Networking/NCNetworking+AsyncAwait.swift b/iOSClient/Networking/NCNetworking+AsyncAwait.swift index 0aaa3bff30..cdb8dc3058 100644 --- a/iOSClient/Networking/NCNetworking+AsyncAwait.swift +++ b/iOSClient/Networking/NCNetworking+AsyncAwait.swift @@ -131,7 +131,7 @@ extension NCNetworking { }) } - func createFolderOffline(metadata: tableMetadata) async -> NKError { + func createFolder(metadata: tableMetadata) async -> NKError { await withUnsafeContinuation({ continuation in self.createFolder(fileName: metadata.fileName, serverUrl: metadata.serverUrl, overwrite: true, withPush: false, metadata: metadata, sceneIdentifier: nil, session: NCSession.shared.getSession(account: metadata.account)) { error in continuation.resume(returning: error) diff --git a/iOSClient/Networking/NCNetworking+Task.swift b/iOSClient/Networking/NCNetworking+Task.swift index aabdb1c289..c33ad75e76 100644 --- a/iOSClient/Networking/NCNetworking+Task.swift +++ b/iOSClient/Networking/NCNetworking+Task.swift @@ -62,6 +62,14 @@ extension NCNetworking { return } + /// RENAME + /// + if metadata.status == global.metadataStatusWaitRename { + database.restoreMetadataFileName(ocId: metadata.ocId) + NotificationCenter.default.postOnMainThread(name: self.global.notificationCenterReloadDataSource) + return + } + /// DIRECTORY /// if metadata.status == global.metadataStatusWaitCreateFolder { @@ -125,7 +133,7 @@ extension NCNetworking { } func cancelAllWaitTask() { - let metadatas = database.getMetadatas(predicate: NSPredicate(format: "status IN %@", [global.metadataStatusWaitCreateFolder, global.metadataStatusWaitDelete])) + let metadatas = database.getMetadatas(predicate: NSPredicate(format: "status IN %@", [global.metadataStatusWaitCreateFolder, global.metadataStatusWaitDelete, global.metadataStatusWaitRename])) for metadata in metadatas { if metadata.status == global.metadataStatusWaitDelete { database.setMetadataStatus(ocId: metadata.ocId, status: global.metadataStatusNormal) @@ -135,6 +143,8 @@ extension NCNetworking { database.deleteMetadataOcId(metadata.ocId) utilityFileSystem.removeFile(atPath: utilityFileSystem.getDirectoryProviderStorageOcId(metadata.ocId)) } + } else if metadata.status == global.metadataStatusWaitRename { + database.restoreMetadataFileName(ocId: metadata.ocId) } } NotificationCenter.default.postOnMainThread(name: self.global.notificationCenterReloadDataSource) diff --git a/iOSClient/Networking/NCNetworking+WebDAV.swift b/iOSClient/Networking/NCNetworking+WebDAV.swift index 38266ccc08..a66c9cb660 100644 --- a/iOSClient/Networking/NCNetworking+WebDAV.swift +++ b/iOSClient/Networking/NCNetworking+WebDAV.swift @@ -308,13 +308,12 @@ extension NCNetworking { // MARK: - Delete - func tapHudDeleteCache() { - tapHudStopDeleteCache = true + func tapHudDelete() { + tapHudStopDelete = true } func deleteCache(_ metadata: tableMetadata, sceneIdentifier: String?) async -> (NKError) { let ncHud = NCHud() - var num: Float = 0 func numIncrement() -> Float { @@ -335,24 +334,24 @@ extension NCNetworking { #endif } - self.tapHudStopDeleteCache = false + self.tapHudStopDelete = false if metadata.directory { #if !EXTENSION if let controller = SceneManager.shared.getController(sceneIdentifier: sceneIdentifier) { await MainActor.run { - ncHud.initHudRing(view: controller.view, tapToCancelDetailText: true, tapOperation: tapHudDeleteCache) + ncHud.initHudRing(view: controller.view, tapToCancelDetailText: true, tapOperation: tapHudDelete) } } #endif let serverUrl = metadata.serverUrl + "/" + metadata.fileName let metadatas = self.database.getMetadatas(predicate: NSPredicate(format: "account == %@ AND serverUrl BEGINSWITH %@ AND directory == false", metadata.account, serverUrl)) - let numMetadatas = Float(metadatas.count) + let total = Float(metadatas.count) for metadata in metadatas { deleteLocalFile(metadata: metadata) let num = numIncrement() - ncHud.progress(num: num, total: numMetadatas) - if tapHudStopDeleteCache { break } + ncHud.progress(num: num, total: total) + if tapHudStopDelete { break } } #if !EXTENSION ncHud.dismiss() @@ -364,118 +363,101 @@ extension NCNetworking { return .success } - func deleteMetadata(_ metadata: tableMetadata) { - let permission = NCUtility().permissionsContainsString(metadata.permissions, permissions: NCPermissions().permissionCanDelete) - if !metadata.permissions.isEmpty && permission == false { - NCContentPresenter().showInfo(error: NKError(errorCode: NCGlobal.shared.errorInternalError, errorDescription: "_no_permission_delete_file_")) - return + func deleteMetadatas(_ metadatas: [tableMetadata], sceneIdentifier: String?) { + var metadatasPlain: [tableMetadata] = [] + var metadatasE2EE: [tableMetadata] = [] + let ncHud = NCHud() + var num: Float = 0 + + func numIncrement() -> Float { + num += 1 + return num } - if metadata.status == global.metadataStatusWaitCreateFolder { - let metadatas = database.getMetadatas(predicate: NSPredicate(format: "account == %@ AND serverUrl BEGINSWITH %@", metadata.account, metadata.serverUrl)) - for metadata in metadatas { - database.deleteMetadataOcId(metadata.ocId) - utilityFileSystem.removeFile(atPath: utilityFileSystem.getDirectoryProviderStorageOcId(metadata.ocId)) + for metadata in metadatas { + if metadata.isDirectoryE2EE { + metadatasE2EE.append(metadata) + } else { + metadatasPlain.append(metadata) } - return } - self.database.setMetadataStatus(ocId: metadata.ocId, status: NCGlobal.shared.metadataStatusWaitDelete) - } - // MARK: - Rename +#if !EXTENSION + if !metadatasE2EE.isEmpty { + if isOffline { + return NCContentPresenter().showInfo(error: NKError(errorCode: NCGlobal.shared.errorInternalError, errorDescription: "_offline_not_allowed_")) + } - func renameMetadata(_ metadata: tableMetadata, - fileNameNew: String, - completion: @escaping (_ error: NKError) -> Void) { - let metadataLive = self.database.getMetadataLivePhoto(metadata: metadata) - let fileNameNew = fileNameNew.trimmingCharacters(in: .whitespacesAndNewlines) - let fileNameNewLive = (fileNameNew as NSString).deletingPathExtension + ".mov" + self.tapHudStopDelete = false + let total = Float(metadatasE2EE.count) - if metadata.status == NCGlobal.shared.metadataStatusWaitCreateFolder { - metadata.fileName = fileNameNew - metadata.fileNameView = fileNameNew - self.database.addMetadata(metadata) - NotificationCenter.default.postOnMainThread(name: self.global.notificationCenterRenameFile, userInfo: ["ocId": metadata.ocId, "account": metadata.account]) - completion(.success) - } else if metadata.isDirectoryE2EE { -#if !EXTENSION Task { - if let metadataLive = metadataLive { - let error = await NCNetworkingE2EERename().rename(metadata: metadataLive, fileNameNew: fileNameNew) + if let controller = SceneManager.shared.getController(sceneIdentifier: sceneIdentifier) { + await MainActor.run { + ncHud.initHudRing(view: controller.view, tapToCancelDetailText: true, tapOperation: tapHudDelete) + } + } + + var ocIdDeleted: [String] = [] + var error = NKError() + for metadata in metadatasE2EE where error == .success { + error = await NCNetworkingE2EEDelete().delete(metadata: metadata) if error == .success { - let error = await NCNetworkingE2EERename().rename(metadata: metadata, fileNameNew: fileNameNew) - DispatchQueue.main.async { completion(error) } - } else { - DispatchQueue.main.async { completion(error) } + ocIdDeleted.append(metadata.ocId) } - } else { - let error = await NCNetworkingE2EERename().rename(metadata: metadata, fileNameNew: fileNameNew) - DispatchQueue.main.async { completion(error) } + let num = numIncrement() + ncHud.progress(num: num, total: total) + if tapHudStopDelete { break } } + + ncHud.dismiss() + NotificationCenter.default.postOnMainThread(name: NCGlobal.shared.notificationCenterDeleteFile, userInfo: ["ocId": ocIdDeleted, "error": error]) } + } #endif - } else { - if let metadataLive, metadata.isNotFlaggedAsLivePhotoByServer { - renameMetadataPlain(metadataLive, fileNameNew: fileNameNewLive) { error in - if error == .success { - self.renameMetadataPlain(metadata, fileNameNew: fileNameNew, completion: completion) - } else { - completion(error) - } + + for metadata in metadatasPlain { + let permission = NCUtility().permissionsContainsString(metadata.permissions, permissions: NCPermissions().permissionCanDelete) + if !metadata.permissions.isEmpty && permission == false { + NCContentPresenter().showInfo(error: NKError(errorCode: NCGlobal.shared.errorInternalError, errorDescription: "_no_permission_delete_file_")) + return + } + + if metadata.status == global.metadataStatusWaitCreateFolder { + let metadatas = database.getMetadatas(predicate: NSPredicate(format: "account == %@ AND serverUrl BEGINSWITH %@", metadata.account, metadata.serverUrl)) + for metadata in metadatas { + database.deleteMetadataOcId(metadata.ocId) + utilityFileSystem.removeFile(atPath: utilityFileSystem.getDirectoryProviderStorageOcId(metadata.ocId)) } - } else { - renameMetadataPlain(metadata, fileNameNew: fileNameNew, completion: completion) + return } + self.database.setMetadataStatus(ocId: metadata.ocId, status: NCGlobal.shared.metadataStatusWaitDelete) } } - private func renameMetadataPlain(_ metadata: tableMetadata, - fileNameNew: String, - completion: @escaping (_ error: NKError) -> Void) { + // MARK: - Rename + + func renameMetadata(_ metadata: tableMetadata, fileNameNew: String) { let permission = utility.permissionsContainsString(metadata.permissions, permissions: NCPermissions().permissionCanRename) - if !metadata.permissions.isEmpty && !permission { - return completion(NKError(errorCode: self.global.errorInternalError, errorDescription: "_no_permission_modify_file_")) - } - let fileName = utility.removeForbiddenCharacters(fileNameNew) - if fileName != fileNameNew { - let errorDescription = String(format: NSLocalizedString("_forbidden_characters_", comment: ""), self.global.forbiddenCharacters.joined(separator: " ")) - let error = NKError(errorCode: self.global.errorConflict, errorDescription: errorDescription) - return completion(error) - } - let fileNameNew = fileName - if fileNameNew.isEmpty || fileNameNew == metadata.fileNameView { - return completion(NKError()) + if !metadata.permissions.isEmpty && permission == false { + NCContentPresenter().showInfo(error: NKError(errorCode: NCGlobal.shared.errorInternalError, errorDescription: "_no_permission_modify_file_")) + return } - let fileNamePath = metadata.serverUrl + "/" + metadata.fileName - let fileNameToPath = metadata.serverUrl + "/" + fileNameNew - NextcloudKit.shared.moveFileOrFolder(serverUrlFileNameSource: fileNamePath, serverUrlFileNameDestination: fileNameToPath, overwrite: false, account: metadata.account) { account, error in - if error == .success { - self.database.renameMetadata(fileNameTo: fileNameNew, ocId: metadata.ocId, account: account) - if metadata.directory { - let serverUrl = self.utilityFileSystem.stringAppendServerUrl(metadata.serverUrl, addFileName: metadata.fileName) - let serverUrlTo = self.utilityFileSystem.stringAppendServerUrl(metadata.serverUrl, addFileName: fileNameNew) - if let directory = self.database.getTableDirectory(predicate: NSPredicate(format: "account == %@ AND serverUrl == %@", metadata.account, metadata.serverUrl)) { - self.database.setDirectory(serverUrl: serverUrl, - serverUrlTo: serverUrlTo, - etag: "", - encrypted: directory.e2eEncrypted, - account: metadata.account) - } - } else { - if (metadata.fileName as NSString).pathExtension != (fileNameNew as NSString).pathExtension { - let path = self.utilityFileSystem.getDirectoryProviderStorageOcId(metadata.ocId) - self.utilityFileSystem.removeFile(atPath: path) - } else { - self.database.setLocalFile(ocId: metadata.ocId, fileName: fileNameNew) - let atPath = self.utilityFileSystem.getDirectoryProviderStorageOcId(metadata.ocId) + "/" + metadata.fileName - let toPath = self.utilityFileSystem.getDirectoryProviderStorageOcId(metadata.ocId) + "/" + fileNameNew - self.utilityFileSystem.moveFile(atPath: atPath, toPath: toPath) - } + if metadata.isDirectoryE2EE { +#if !EXTENSION + if isOffline { + return NCContentPresenter().showInfo(error: NKError(errorCode: NCGlobal.shared.errorInternalError, errorDescription: "_offline_not_allowed_")) + } + Task { + let error = await NCNetworkingE2EERename().rename(metadata: metadata, fileNameNew: fileNameNew) + if error != .success { + NCContentPresenter().showError(error: error) } - NotificationCenter.default.postOnMainThread(name: self.global.notificationCenterRenameFile, userInfo: ["ocId": metadata.ocId, "account": metadata.account]) } - completion(error) +#endif + } else { + self.database.renameMetadata(fileNameNew: fileNameNew, ocId: metadata.ocId, status: NCGlobal.shared.metadataStatusWaitRename) } } diff --git a/iOSClient/Networking/NCNetworking.swift b/iOSClient/Networking/NCNetworking.swift index dd1feb7735..e22b68e36f 100644 --- a/iOSClient/Networking/NCNetworking.swift +++ b/iOSClient/Networking/NCNetworking.swift @@ -75,7 +75,7 @@ class NCNetworking: NSObject, NextcloudKitDelegate { weak var certificateDelegate: ClientCertificateDelegate? var p12Data: Data? var p12Password: String? - var tapHudStopDeleteCache = false + var tapHudStopDelete = false var isOffline: Bool { return networkReachability == NKCommon.TypeReachability.notReachable || networkReachability == NKCommon.TypeReachability.unknown diff --git a/iOSClient/Networking/NCNetworkingProcess.swift b/iOSClient/Networking/NCNetworkingProcess.swift index 7a87d0a3d9..6622f6400f 100644 --- a/iOSClient/Networking/NCNetworkingProcess.swift +++ b/iOSClient/Networking/NCNetworkingProcess.swift @@ -128,7 +128,7 @@ class NCNetworkingProcess { } @discardableResult - private func start() async -> (counterDownloading: Int, counterUploading: Int) { + private func start() async -> (counterDownloading: Int, counterUploading: Int, counterWebDAV: Int) { let applicationState = await checkApplicationState() let maxConcurrentOperationDownload = NCBrandOptions.shared.maxConcurrentOperationDownload var maxConcurrentOperationUpload = NCBrandOptions.shared.maxConcurrentOperationUpload @@ -148,25 +148,42 @@ class NCNetworkingProcess { NCNetworking.shared.deleteFileOrFolderQueue.addOperation(NCOperationDeleteFileOrFolder(metadata: metadata)) } } - return (counterDownloading, counterUploading) + return (counterDownloading, counterUploading, metadatasWaitDelete.count) } - /// ------------------------ FOLDER + /// ------------------------ CREATE FOLDER /// if let metadatasWaitCreateFolder = self.database.getMetadatas(predicate: NSPredicate(format: "status == %d", global.metadataStatusWaitCreateFolder), sortedByKeyPath: "serverUrl", ascending: true), !metadatasWaitCreateFolder.isEmpty { for metadata in metadatasWaitCreateFolder { - let error = await NCNetworking.shared.createFolderOffline(metadata: metadata) + let error = await NCNetworking.shared.createFolder(metadata: metadata) if error != .success { if metadata.sessionError.isEmpty { let serverUrlFileName = metadata.serverUrl + "/" + metadata.fileName - let message = String(format: NSLocalizedString("_offlinefolder_error_", comment: ""), serverUrlFileName) + let message = String(format: NSLocalizedString("_create_folder_error_", comment: ""), serverUrlFileName) NCContentPresenter().messageNotification(message, error: error, delay: NCGlobal.shared.dismissAfterSecond, type: NCContentPresenter.messageType.error, priority: .max) } - return (counterDownloading, counterUploading) + return (counterDownloading, counterUploading, metadatasWaitCreateFolder.count) } } } + /// ------------------------ RENAME + /// + if let metadatasWaitRename = self.database.getMetadatas(predicate: NSPredicate(format: "status == %d", global.metadataStatusWaitRename), sortedByKeyPath: "serverUrl", ascending: true), !metadatasWaitRename.isEmpty { + for metadata in metadatasWaitRename { + let serverUrlFileNameSource = metadata.serveUrlFileName + let serverUrlFileNameDestination = metadata.serverUrl + "/" + metadata.fileName + let result = await NCNetworking.shared.moveFileOrFolder(serverUrlFileNameSource: serverUrlFileNameSource, serverUrlFileNameDestination: serverUrlFileNameDestination, overwrite: false, account: metadata.account) + if result.error == .success { + database.setMetadataServeUrlFileNameStatusNormal(ocId: metadata.ocId) + } else { + database.restoreMetadataFileName(ocId: metadata.ocId) + } + NotificationCenter.default.postOnMainThread(name: NCGlobal.shared.notificationCenterRenameFile, userInfo: ["serverUrl": metadata.serverUrl, "account": metadata.account, "error": result.error]) + } + return (counterDownloading, counterUploading, metadatasWaitRename.count) + } + /// ------------------------ DOWNLOAD /// let limitDownload = maxConcurrentOperationDownload - counterDownloading @@ -195,13 +212,13 @@ class NCNetworkingProcess { /// E2EE - only one for time for metadata in metadatasUploading.unique(map: { $0.serverUrl }) { if metadata.isDirectoryE2EE { - return (counterDownloading, counterUploading) + return (counterDownloading, counterUploading, 0) } } /// CHUNK - only one for time if !metadatasUploading.filter({ $0.chunk > 0 }).isEmpty { - return (counterDownloading, counterUploading) + return (counterDownloading, counterUploading, 0) } for sessionSelector in sessionUploadSelectors where counterUploading < maxConcurrentOperationUpload { @@ -287,7 +304,7 @@ class NCNetworkingProcess { } } - return (counterDownloading, counterUploading) + return (counterDownloading, counterUploading, 0) } private func checkApplicationState() async -> UIApplication.State { @@ -301,7 +318,7 @@ class NCNetworkingProcess { // MARK: - Public - func refreshProcessingTask() async -> (counterDownloading: Int, counterUploading: Int) { + func refreshProcessingTask() async -> (counterDownloading: Int, counterUploading: Int, counterWebDAV: Int) { await withCheckedContinuation { continuation in self.lockQueue.sync { guard !self.hasRun, NCNetworking.shared.isOnline else { return } diff --git a/iOSClient/Supporting Files/en.lproj/Localizable.strings b/iOSClient/Supporting Files/en.lproj/Localizable.strings index 32a55df232..656237fd5c 100644 --- a/iOSClient/Supporting Files/en.lproj/Localizable.strings +++ b/iOSClient/Supporting Files/en.lproj/Localizable.strings @@ -608,6 +608,7 @@ "_status_wait_upload_" = "Waiting to upload"; "_status_wait_create_folder_" = "Waiting to create the folder"; "_status_wait_delete_" = "Waiting to delete"; +"_status_wait_rename_" = "Waiting to rename"; "_status_in_upload_" = "In upload"; "_status_uploading_" = "Uploading"; "_status_upload_error_" = "Error, waiting to upload"; @@ -1025,11 +1026,13 @@ "_maintenance_mode_" = "Server is currently in maintenance mode"; "_upload_foreground_msg_" = "Do not close %@ to complete the transfer …"; "_upload_background_msg_" = "Files upload in progress …"; -"_offlinefolder_error_" = "An error has occurred while creating the folder:\n%@.\n\nPlease resolve the issue as soon as possible.\n\nAll uploads are suspended until the problem is resolved.\n"; +"_create_folder_error_" = "An error has occurred while creating the folder:\n%@.\n\nPlease resolve the issue as soon as possible.\n\nAll uploads are suspended until the problem is resolved.\n"; +"_rename_file_error_" = "An error has occurred while renaming the file:\n%@."; "_creating_dir_progress_" = "Creating directories in progress … keep the application active"; "_creating_db_photo_progress" = "Creating photo archive in progress … keep the application active"; "_account_unauthorized_" = "Warning, %@, you are not authorized, your account has been deleted, if you have changed your password, re-authenticate."; "_folder_offline_desc_" = "Even without an internet connection, you can organize your folders, create files. Once you're back online, your pending actions will automatically sync."; +"_offline_not_allowed_" = "This operation is not allowed in offline mode"; // Tip "_tip_pdf_thumbnails_" = "Swipe left from the right edge of the screen to show the thumbnails."; diff --git a/iOSClient/Transfers/NCTransfers.swift b/iOSClient/Transfers/NCTransfers.swift index 598ad2d883..cf24f8c582 100644 --- a/iOSClient/Transfers/NCTransfers.swift +++ b/iOSClient/Transfers/NCTransfers.swift @@ -63,6 +63,18 @@ class NCTransfers: NCCollectionViewCommon, NCTransferCellDelegate { // MARK: - NotificationCenter + override func deleteFile(_ notification: NSNotification) { + reloadDataSource() + } + + override func renameFile(_ notification: NSNotification) { + reloadDataSource() + } + + override func createFolder(_ notification: NSNotification) { + reloadDataSource() + } + override func downloadStartFile(_ notification: NSNotification) { reloadDataSource() } @@ -234,6 +246,10 @@ class NCTransfers: NCCollectionViewCommon, NCTransferCellDelegate { cell.fileStatusImage?.image = utility.loadImage(named: "trash.circle", colors: NCBrandColor.shared.iconImageMultiColors) cell.labelStatus.text = NSLocalizedString("_status_wait_delete_", comment: "") + user cell.labelInfo.text = "" + case NCGlobal.shared.metadataStatusWaitRename: + cell.fileStatusImage?.image = utility.loadImage(named: "a.circle", colors: NCBrandColor.shared.iconImageMultiColors) + cell.labelStatus.text = NSLocalizedString("_status_wait_rename_", comment: "") + user + cell.labelInfo.text = "" case NCGlobal.shared.metadataStatusDownloading: if #available(iOS 17.0, *) { cell.fileStatusImage?.image = utility.loadImage(named: "arrowshape.down.circle", colors: NCBrandColor.shared.iconImageMultiColors) diff --git a/iOSClient/Utility/NCUtility+Image.swift b/iOSClient/Utility/NCUtility+Image.swift index f43b438ee3..a54c273abd 100644 --- a/iOSClient/Utility/NCUtility+Image.swift +++ b/iOSClient/Utility/NCUtility+Image.swift @@ -125,14 +125,14 @@ extension NCUtility { func createImageFileFrom(metadata: tableMetadata) { if metadata.classFile != NKCommon.TypeClassFile.image.rawValue, metadata.classFile != NKCommon.TypeClassFile.video.rawValue { return } var image: UIImage? - let fileNamePath1024 = utilityFileSystem.getDirectoryProviderStorageOcId(metadata.ocId, fileNameView: metadata.fileNameView) + let fileNamePath = utilityFileSystem.getDirectoryProviderStorageOcId(metadata.ocId, fileNameView: metadata.fileNameView) if image == nil { if metadata.classFile == NKCommon.TypeClassFile.image.rawValue { - image = UIImage(contentsOfFile: fileNamePath1024) + image = UIImage(contentsOfFile: fileNamePath) } else if metadata.classFile == NKCommon.TypeClassFile.video.rawValue { let videoPath = NSTemporaryDirectory() + "tempvideo.mp4" - utilityFileSystem.linkItem(atPath: fileNamePath1024, toPath: videoPath) + utilityFileSystem.linkItem(atPath: fileNamePath, toPath: videoPath) image = imageFromVideo(url: URL(fileURLWithPath: videoPath), at: 0) } } @@ -158,9 +158,9 @@ extension NCUtility { } private func createImageStandard(ocId: String, etag: String, image: UIImage) { - let ext = [global.previewExt512, global.previewExt256] - let size = [global.size512, global.size256] - let compressionQuality = [0.6, 0.7] + let ext = [global.previewExt1024, global.previewExt512, global.previewExt256] + let size = [global.size1024, global.size512, global.size256] + let compressionQuality = [0.5, 0.6, 0.7] for i in 0..