Browse Source

Merge pull request #1885 from nextcloud/fix/copy-memory

Fix copy to pasteboard memory issue
Marino Faggiana 3 years ago
parent
commit
0c735ccc8d

+ 1 - 1
File Provider Extension/FileProviderExtension.swift

@@ -197,7 +197,7 @@ class FileProviderExtension: NSFileProviderExtension, NCNetworkingDelegate {
         }
 
         let tableLocalFile = NCManageDatabase.shared.getTableLocalFile(predicate: NSPredicate(format: "ocId == %@", metadata.ocId))
-        if tableLocalFile != nil && CCUtility.fileProviderStorageExists(metadata.ocId, fileNameView: metadata.fileNameView) && tableLocalFile?.etag == metadata.etag {
+        if tableLocalFile != nil && CCUtility.fileProviderStorageExists(metadata) && tableLocalFile?.etag == metadata.etag {
             completionHandler(nil)
             return
         }

+ 1 - 1
File Provider Extension/FileProviderItem.swift

@@ -103,7 +103,7 @@ class FileProviderItem: NSObject, NSFileProviderItem {
         if metadata.directory {
             return true
         }
-        if CCUtility.fileProviderStorageExists(metadata.ocId, fileNameView: metadata.fileNameView) {
+        if CCUtility.fileProviderStorageExists(metadata) {
             return true
         } else {
             return false

+ 14 - 0
Nextcloud.xcodeproj/project.pbxproj

@@ -27,6 +27,8 @@
 		AF22B218277D196700DAB0CC /* NCShareExtension+Files.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF22B216277D196700DAB0CC /* NCShareExtension+Files.swift */; };
 		AF2D7C7C2742556F00ADF566 /* NCShareLinkCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF2D7C7B2742556F00ADF566 /* NCShareLinkCell.swift */; };
 		AF2D7C7E2742559100ADF566 /* NCShareUserCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF2D7C7D2742559100ADF566 /* NCShareUserCell.swift */; };
+		AF36077127BFA4E8001A243D /* ParallelWorker.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF36077027BFA4E8001A243D /* ParallelWorker.swift */; };
+		AF36077627BFB019001A243D /* ParallelWorkerTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF36077527BFB019001A243D /* ParallelWorkerTest.swift */; };
 		AF3FDCC22796ECC300710F60 /* NCTrash+CollectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF3FDCC12796ECC300710F60 /* NCTrash+CollectionView.swift */; };
 		AF3FDCC32796F3FB00710F60 /* NCTrashListCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = F78ACD4821903F850088454D /* NCTrashListCell.swift */; };
 		AF4BF614275629E20081CEEF /* NCManageDatabase+Account.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF4BF613275629E20081CEEF /* NCManageDatabase+Account.swift */; };
@@ -465,6 +467,8 @@
 		AF22B216277D196700DAB0CC /* NCShareExtension+Files.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NCShareExtension+Files.swift"; sourceTree = "<group>"; };
 		AF2D7C7B2742556F00ADF566 /* NCShareLinkCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCShareLinkCell.swift; sourceTree = "<group>"; };
 		AF2D7C7D2742559100ADF566 /* NCShareUserCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCShareUserCell.swift; sourceTree = "<group>"; };
+		AF36077027BFA4E8001A243D /* ParallelWorker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParallelWorker.swift; sourceTree = "<group>"; };
+		AF36077527BFB019001A243D /* ParallelWorkerTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParallelWorkerTest.swift; sourceTree = "<group>"; };
 		AF3FDCC12796ECC300710F60 /* NCTrash+CollectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NCTrash+CollectionView.swift"; sourceTree = "<group>"; };
 		AF4BF613275629E20081CEEF /* NCManageDatabase+Account.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NCManageDatabase+Account.swift"; sourceTree = "<group>"; };
 		AF4BF61827562A4B0081CEEF /* NCManageDatabse+Metadata.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NCManageDatabse+Metadata.swift"; sourceTree = "<group>"; };
@@ -961,6 +965,7 @@
 			isa = PBXGroup;
 			children = (
 				AF8ED2022757822700B8DBC4 /* NCGlobalTests.swift */,
+				AF36077527BFB019001A243D /* ParallelWorkerTest.swift */,
 				AF8ED1FB2757821000B8DBC4 /* NextcloudTests.swift */,
 			);
 			path = NextcloudTests;
@@ -1449,6 +1454,7 @@
 				F70BFC7320E0FA7C00C67599 /* NCUtility.swift */,
 				AF817EF0274BC781009ED85B /* NCUserBaseUrl.swift */,
 				F74AF3A3247FB6AE00AC767B /* NCUtilityFileSystem.swift */,
+				AF36077027BFA4E8001A243D /* ParallelWorker.swift */,
 				F702F2FC25EE5D2C008F8E80 /* NYMnemonic */,
 			);
 			path = Utility;
@@ -2257,6 +2263,7 @@
 			isa = PBXSourcesBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
+				AF36077627BFB019001A243D /* ParallelWorkerTest.swift in Sources */,
 				AF8ED1FC2757821000B8DBC4 /* NextcloudTests.swift in Sources */,
 				AF8ED2032757822700B8DBC4 /* NCGlobalTests.swift in Sources */,
 			);
@@ -2385,6 +2392,7 @@
 				AF4BF61927562A4B0081CEEF /* NCManageDatabse+Metadata.swift in Sources */,
 				F78A18B623CDD07D00F681F3 /* NCViewerRichWorkspaceWebView.swift in Sources */,
 				F716B75F26F09DF600D37EFC /* NCKTVHTTPCache.swift in Sources */,
+				AF36077127BFA4E8001A243D /* ParallelWorker.swift in Sources */,
 				F75A9EE623796C6F0044CFCE /* NCNetworking.swift in Sources */,
 				F758B460212C56A400515F55 /* NCScanCollectionView.swift in Sources */,
 				F78ACD52219046DC0088454D /* NCSectionHeaderFooter.swift in Sources */,
@@ -2612,6 +2620,9 @@
 			isa = XCBuildConfiguration;
 			buildSettings = {
 				BUNDLE_LOADER = "$(TEST_HOST)";
+				"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
+				ENABLE_HARDENED_RUNTIME = YES;
+				GENERATE_INFOPLIST_FILE = YES;
 				PRODUCT_BUNDLE_IDENTIFIER = it.twsweb.NextcloudTests;
 				PRODUCT_NAME = "$(TARGET_NAME)";
 				TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Nextcloud.app/Nextcloud";
@@ -2622,6 +2633,9 @@
 			isa = XCBuildConfiguration;
 			buildSettings = {
 				BUNDLE_LOADER = "$(TEST_HOST)";
+				"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
+				ENABLE_HARDENED_RUNTIME = YES;
+				GENERATE_INFOPLIST_FILE = YES;
 				PRODUCT_BUNDLE_IDENTIFIER = it.twsweb.NextcloudTests;
 				PRODUCT_NAME = "$(TARGET_NAME)";
 				TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Nextcloud.app/Nextcloud";

+ 85 - 0
NextcloudTests/ParallelWorkerTest.swift

@@ -0,0 +1,85 @@
+//
+//  ParallelWorkerTest.swift
+//  Nextcloud
+//
+//  Created by Henrik Storch on 18.02.22.
+//  Copyright © 2021 Henrik Storch. All rights reserved.
+//
+//  Author Henrik Storch <henrik.storch@nextcloud.com>
+//
+//  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 <http://www.gnu.org/licenses/>.
+//
+
+@testable import Nextcloud
+import XCTest
+
+class ParallelWorkerTest: XCTestCase {
+
+    func testWorkerComplete() throws {
+        let expectation = XCTestExpectation(description: "Worker executes all tasks")
+        let taskCount = 20
+        var tasksComplete = 0
+        let worker = ParallelWorker(n: 5, titleKey: nil, totalTasks: nil, hudView: nil)
+        for _ in 0..<taskCount {
+            worker.execute { completion in
+                tasksComplete += 1
+                completion()
+            }
+        }
+        worker.completeWork {
+            XCTAssertEqual(tasksComplete, taskCount)
+            if tasksComplete == taskCount {
+                expectation.fulfill()
+            }
+        }
+
+        let result = XCTWaiter.wait(for: [expectation], timeout: 5)
+        XCTAssertEqual(result, .completed)
+    }
+
+    func testWorkerOrder() throws {
+        let expectation = XCTestExpectation(description: "Worker executes work in sequence for n = 1")
+        let sortedArray = Array(0..<20)
+        var array: [Int] = []
+        let worker = ParallelWorker(n: 1, titleKey: nil, totalTasks: nil, hudView: nil)
+        for i in sortedArray {
+            worker.execute { completion in
+                DispatchQueue.main.asyncAfter(deadline: .now() + Double.random(in: 0...0.2)) {
+                    array.append(i)
+                    completion()
+                }
+            }
+        }
+        worker.completeWork {
+            XCTAssertEqual(sortedArray, array)
+            if sortedArray == array {
+                expectation.fulfill()
+            }
+        }
+        let result = XCTWaiter.wait(for: [expectation], timeout: 5)
+        XCTAssertEqual(result, .completed)
+    }
+
+    func testWorkerFailsWithoutCompletion() throws {
+        let expectation = XCTestExpectation(description: "Worker fails if completion isn't called")
+        expectation.isInverted = true
+        let worker = ParallelWorker(n: 5, titleKey: nil, totalTasks: nil, hudView: nil)
+        for _ in 0..<20 {
+            worker.execute { _ in }
+        }
+        worker.completeWork { expectation.fulfill() }
+        let result = XCTWaiter.wait(for: [expectation], timeout: 5)
+        XCTAssertEqual(result, .completed)
+    }
+}

+ 4 - 4
Share/NCShareExtension.swift

@@ -104,7 +104,7 @@ class NCShareExtension: UIViewController {
         createFolderView.addGestureRecognizer(createFolderGesture)
 
         uploadView.layer.cornerRadius = 10
-        
+
         // uploadImage.image = NCUtility.shared.loadImage(named: "square.and.arrow.up", color: NCBrandColor.shared.label)
         uploadLabel.text = NSLocalizedString("_upload_", comment: "")
         uploadLabel.textColor = .systemBlue
@@ -130,7 +130,7 @@ class NCShareExtension: UIViewController {
         if let indicatorView = hud.indicatorView as? JGProgressHUDRingIndicatorView {
             indicatorView.ringWidth = 1.5
         }
-        
+
         NotificationCenter.default.addObserver(self, selector: #selector(triggerProgressTask(_:)), name: NSNotification.Name(rawValue: NCGlobal.shared.notificationCenterProgressTask), object: nil)
     }
 
@@ -188,7 +188,7 @@ class NCShareExtension: UIViewController {
         guard let progress = notification.userInfo?["progress"] as? Float else { return }
         hud.progress = progress
     }
-    
+
     func setNavigationBar(navigationTitle: String) {
 
         navigationItem.title = navigationTitle
@@ -357,7 +357,7 @@ extension NCShareExtension {
         hud.textLabel.text = NSLocalizedString("_upload_file_", comment: "") + " \(counterUploaded + 1) " + NSLocalizedString("_of_", comment: "") + " \(filesName.count)"
         hud.progress = 0
         hud.show(in: self.view)
-        
+
         NCNetworking.shared.upload(metadata: metadata) { } completion: { errorCode, _ in
             if errorCode == 0 {
                 self.counterUploaded += 1

+ 0 - 1
iOSClient/AppDelegate.swift

@@ -59,7 +59,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
     var disableSharesView: Bool = false
     var documentPickerViewController: NCDocumentPickerViewController?
     var networkingProcessUpload: NCNetworkingProcessUpload?
-    var pasteboardOcIds: [String] = []
     var shares: [tableShare] = []
     var timerErrorNetworking: Timer?
     

+ 4 - 0
iOSClient/Data/NCDatabase.swift

@@ -413,6 +413,10 @@ class tableMetadata: Object, NCUserBaseUrl {
     }
 }
 
+extension tableMetadata {
+    var fileExtension: String { (fileNameView as NSString).pathExtension }
+}
+
 class tablePhotoLibrary: Object {
 
     @objc dynamic var account = ""

+ 3 - 3
iOSClient/Main/Collection Common/NCCollectionViewCommon.swift

@@ -1202,7 +1202,7 @@ extension NCCollectionViewCommon: UICollectionViewDelegate {
                 return
             }
 
-            if CCUtility.fileProviderStorageExists(metadata.ocId, fileNameView: metadata.fileNameView) {
+            if CCUtility.fileProviderStorageExists(metadata) {
                 NCViewer.shared.view(viewController: self, metadata: metadata, metadatas: [metadata], imageIcon: imageIcon)
             } else if NCCommunication.shared.isNetworkReachable() {
                 NCNetworking.shared.download(metadata: metadata, selector: NCGlobal.shared.selectorLoadFileView) { _ in }
@@ -1427,7 +1427,7 @@ extension NCCollectionViewCommon: UICollectionViewDataSource {
                 // image local
                 if dataSource.metadataOffLine.contains(metadata.ocId) {
                     cell.imageLocal.image = NCBrandColor.cacheImages.offlineFlag
-                } else if CCUtility.fileProviderStorageExists(metadata.ocId, fileNameView: metadata.fileNameView) {
+                } else if CCUtility.fileProviderStorageExists(metadata) {
                     cell.imageLocal.image = NCBrandColor.cacheImages.local
                 }
             }
@@ -1595,7 +1595,7 @@ extension NCCollectionViewCommon: UICollectionViewDataSource {
                 // image Local
                 if dataSource.metadataOffLine.contains(metadata.ocId) {
                     cell.imageLocal.image = NCBrandColor.cacheImages.offlineFlag
-                } else if CCUtility.fileProviderStorageExists(metadata.ocId, fileNameView: metadata.fileNameView) {
+                } else if CCUtility.fileProviderStorageExists(metadata) {
                     cell.imageLocal.image = NCBrandColor.cacheImages.local
                 }
             }

+ 2 - 3
iOSClient/Main/Collection Common/NCSelectableNavigationView.swift

@@ -152,7 +152,7 @@ extension NCSelectableNavigationView where Self: UIViewController {
                             if let metadataMOV = NCManageDatabase.shared.getMetadataLivePhoto(metadata: metadata) {
                                 NCFunctionCenter.shared.saveLivePhoto(metadata: metadata, metadataMOV: metadataMOV)
                             } else {
-                                if CCUtility.fileProviderStorageExists(metadata.ocId, fileNameView: metadata.fileNameView) {
+                                if CCUtility.fileProviderStorageExists(metadata) {
                                     NCFunctionCenter.shared.saveAlbum(metadata: metadata)
                                 } else {
                                     NCOperationQueue.shared.download(metadata: metadata, selector: NCGlobal.shared.selectorSaveAlbum)
@@ -189,8 +189,7 @@ extension NCSelectableNavigationView where Self: UIViewController {
                 title: NSLocalizedString("_copy_file_", comment: ""),
                 icon: NCUtility.shared.loadImage(named: "doc.on.doc"),
                 action: { _ in
-                    self.appDelegate.pasteboardOcIds = self.selectOcId
-                    NCFunctionCenter.shared.copyPasteboard()
+                    NCFunctionCenter.shared.copyPasteboard(pasteboardOcIds: self.selectOcId, hudView: self.view)
                     self.tapSelect()
                 }
             )

+ 72 - 89
iOSClient/Main/NCFunctionCenter.swift

@@ -96,10 +96,6 @@ import JGProgressHUD
                             self.openDocumentController(metadata: metadata)
                         }
 
-                    case NCGlobal.shared.selectorLoadCopy:
-
-                        copyPasteboard()
-
                     case NCGlobal.shared.selectorLoadOffline:
 
                         NCManageDatabase.shared.setLocalFile(ocId: metadata.ocId, offline: true)
@@ -130,7 +126,7 @@ import JGProgressHUD
                             metadata = metadataTMP
                         }
 
-                        if CCUtility.fileProviderStorageExists(metadata.ocId, fileNameView: metadata.fileNameView) && CCUtility.fileProviderStorageExists(metadataMOV.ocId, fileNameView: metadataMOV.fileNameView) {
+                        if CCUtility.fileProviderStorageExists(metadata) && CCUtility.fileProviderStorageExists(metadataMOV) {
                             saveLivePhotoToDisk(metadata: metadata, metadataMov: metadataMOV)
                         }
 
@@ -204,7 +200,7 @@ import JGProgressHUD
 
     func openDownload(metadata: tableMetadata, selector: String) {
 
-        if CCUtility.fileProviderStorageExists(metadata.ocId, fileNameView: metadata.fileNameView) {
+        if CCUtility.fileProviderStorageExists(metadata) {
 
             NotificationCenter.default.postOnMainThread(name: NCGlobal.shared.notificationCenterDownloadedFile, userInfo: ["ocId": metadata.ocId, "selector": selector, "errorCode": 0, "errorDescription": "" ])
 
@@ -237,7 +233,7 @@ import JGProgressHUD
                 if metadata.directory {
                     continue
                 }
-                if !CCUtility.fileProviderStorageExists(metadata.ocId, fileNameView: metadata.fileNameView) {
+                if !CCUtility.fileProviderStorageExists(metadata) {
                     let semaphore = Semaphore()
                     NCNetworking.shared.download(metadata: metadata, selector: "") { errorCode in
                         error = errorCode
@@ -344,15 +340,15 @@ import JGProgressHUD
 
     func saveLivePhoto(metadata: tableMetadata, metadataMOV: tableMetadata) {
 
-        if !CCUtility.fileProviderStorageExists(metadata.ocId, fileNameView: metadata.fileNameView) {
+        if !CCUtility.fileProviderStorageExists(metadata) {
             NCOperationQueue.shared.download(metadata: metadata, selector: NCGlobal.shared.selectorSaveAlbumLivePhotoIMG)
         }
 
-        if !CCUtility.fileProviderStorageExists(metadataMOV.ocId, fileNameView: metadataMOV.fileNameView) {
+        if !CCUtility.fileProviderStorageExists(metadataMOV) {
             NCOperationQueue.shared.download(metadata: metadataMOV, selector: NCGlobal.shared.selectorSaveAlbumLivePhotoMOV)
         }
 
-        if CCUtility.fileProviderStorageExists(metadata.ocId, fileNameView: metadata.fileNameView) && CCUtility.fileProviderStorageExists(metadataMOV.ocId, fileNameView: metadataMOV.fileNameView) {
+        if CCUtility.fileProviderStorageExists(metadata) && CCUtility.fileProviderStorageExists(metadataMOV) {
             saveLivePhotoToDisk(metadata: metadata, metadataMov: metadataMOV)
         }
     }
@@ -418,100 +414,74 @@ import JGProgressHUD
 
     // MARK: - Copy & Paste
 
-    func copyPasteboard() {
-
-        var metadatas: [tableMetadata] = []
+    func copyPasteboard(pasteboardOcIds: [String], hudView: UIView) {
         var items = [[String: Any]]()
-
-        for ocId in appDelegate.pasteboardOcIds {
-            if let metadata = NCManageDatabase.shared.getMetadataFromOcId(ocId) {
-                metadatas.append(metadata)
+        let hud = JGProgressHUD()
+        hud.textLabel.text = NSLocalizedString("_wait_", comment: "")
+        hud.show(in: hudView)
+
+        // getting file data can take some time and block the main queue
+        DispatchQueue.global(qos: .userInitiated).async {
+            var downloadMetadatas: [tableMetadata] = []
+            for ocid in pasteboardOcIds {
+                guard let metadata = NCManageDatabase.shared.getMetadataFromOcId(ocid) else { continue }
+                if let pasteboardItem = metadata.toPasteBoardItem() { items.append(pasteboardItem) }
+                else { downloadMetadatas.append(metadata) }
             }
-        }
 
-        for metadata in metadatas {
+            DispatchQueue.main.async(execute: hud.dismiss)
 
-            if CCUtility.fileProviderStorageExists(metadata.ocId, fileNameView: metadata.fileNameView) {
-                do {
-                    // Get Data
-                    let data = try Data(contentsOf: URL(fileURLWithPath: CCUtility.getDirectoryProviderStorageOcId(metadata.ocId, fileNameView: metadata.fileNameView)))
-                    // Pasteboard item
-                    if let unmanagedFileUTI = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, (metadata.fileNameView as NSString).pathExtension as CFString, nil) {
-                        let fileUTI = unmanagedFileUTI.takeRetainedValue() as String
-                        items.append([fileUTI: data])
-                    }
-                } catch {
-                    print("error")
+            // do 5 downloads in paralell to optimize efficiancy
+            let parallelizer = ParallelWorker(n: 5, titleKey: "_downloading_", totalTasks: downloadMetadatas.count, hudView: hudView)
+
+            for metadata in downloadMetadatas {
+                parallelizer.execute { completion in
+                    NCNetworking.shared.download(metadata: metadata, selector: "") { _ in completion() }
                 }
-            } else {
-                NCNetworking.shared.download(metadata: metadata, selector: NCGlobal.shared.selectorLoadCopy) { _ in }
+            }
+            parallelizer.completeWork {
+                items.append(contentsOf: downloadMetadatas.compactMap({ $0.toPasteBoardItem() }))
+                UIPasteboard.general.setItems(items, options: [:])
             }
         }
+    }
 
-        UIPasteboard.general.setItems(items, options: [:])
+    func upload(fileName: String, serverUrlFileName: String, fileNameLocalPath: String, serverUrl: String, completion: @escaping () -> Void) {
+        NCCommunication.shared.upload(serverUrlFileName: serverUrlFileName, fileNameLocalPath: fileNameLocalPath) { _ in
+        } progressHandler: { progress in
+        } completionHandler: { account, ocId, etag, _, _, _, errorCode, errorDescription in
+            if errorCode == 0 && etag != nil && ocId != nil {
+                let toPath = CCUtility.getDirectoryProviderStorageOcId(ocId!, fileNameView: fileName)!
+                NCUtilityFileSystem.shared.moveFile(atPath: fileNameLocalPath, toPath: toPath)
+                NCManageDatabase.shared.addLocalFile(account: account, etag: etag!, ocId: ocId!, fileName: fileName)
+                NotificationCenter.default.postOnMainThread(name: NCGlobal.shared.notificationCenterReloadDataSourceNetworkForced, userInfo: ["serverUrl": serverUrl])
+            } else {
+                NCContentPresenter.shared.showError(description: errorDescription, errorCode: errorCode)
+            }
+            completion()
+        }
     }
 
     func pastePasteboard(serverUrl: String) {
+        let parallelizer = ParallelWorker(n: 5, titleKey: "_uploading_", totalTasks: nil, hudView: appDelegate.window?.rootViewController?.view)
 
-        var pasteboardTypes: [String] = []
-
-        func upload(pasteboardType: String?, data: Data?) -> Bool {
-
-            guard let data = data else { return false}
-            guard let pasteboardType = pasteboardType else { return false }
-
-            let results = NCCommunicationCommon.shared.getFileProperties(inUTI: pasteboardType as CFString)
-            if results.ext == "" { return false }
-
-            do {
+        for (index, items) in UIPasteboard.general.items.enumerated() {
+            for item in items {
+                let results = NCCommunicationCommon.shared.getFileProperties(inUTI: item.key as CFString)
+                guard !results.ext.isEmpty,
+                      let data = UIPasteboard.general.data(forPasteboardType: item.key, inItemSet: IndexSet([index]))?.first
+                else { continue }
                 let fileName = results.name + "_" + CCUtility.getIncrementalNumber() + "." + results.ext
                 let serverUrlFileName = serverUrl + "/" + fileName
                 let ocIdUpload = UUID().uuidString
                 let fileNameLocalPath = CCUtility.getDirectoryProviderStorageOcId(ocIdUpload, fileNameView: fileName)!
-                try data.write(to: URL(fileURLWithPath: fileNameLocalPath))
-                let hud = JGProgressHUD()
-                
-                hud.indicatorView = JGProgressHUDRingIndicatorView()
-                if let indicatorView = hud.indicatorView as? JGProgressHUDRingIndicatorView {
-                    indicatorView.ringWidth = 1.5
-                }
-                hud.show(in: (appDelegate.window?.rootViewController?.view)!)
-                hud.textLabel.text = fileName
-
-                NCCommunication.shared.upload(serverUrlFileName: serverUrlFileName, fileNameLocalPath: fileNameLocalPath) { _ in
-                } progressHandler: { progress in
-                    hud.progress = Float(progress.fractionCompleted)
-                } completionHandler: { account, ocId, etag, _, _, _, errorCode, errorDescription in
-                    if errorCode == 0 && etag != nil && ocId != nil {
-                        let toPath = CCUtility.getDirectoryProviderStorageOcId(ocId!, fileNameView: fileName)!
-                        NCUtilityFileSystem.shared.moveFile(atPath: fileNameLocalPath, toPath: toPath)
-                        NCManageDatabase.shared.addLocalFile(account: account, etag: etag!, ocId: ocId!, fileName: fileName)
-                        NotificationCenter.default.postOnMainThread(name: NCGlobal.shared.notificationCenterReloadDataSourceNetworkForced, userInfo: ["serverUrl": serverUrl])
-                        hud.indicatorView = JGProgressHUDSuccessIndicatorView()
-                        hud.textLabel.text = NSLocalizedString("_success_", comment: "")
-                    } else {
-                        hud.indicatorView = JGProgressHUDErrorIndicatorView()
-                        hud.textLabel.text = NSLocalizedString(errorDescription, comment: "")
-                    }
-                    hud.dismiss(afterDelay: 1)
-                }
-            } catch {
-                return false
-            }
-            return true
-        }
-
-        for (index, items) in UIPasteboard.general.items.enumerated() {
-
-            for item in items { pasteboardTypes.append(item.key) }
-
-            for typeIdentifier in pasteboardTypes {
-                let data = UIPasteboard.general.data(forPasteboardType: typeIdentifier, inItemSet: IndexSet([index]))?.first
-                if upload(pasteboardType: typeIdentifier, data: data) {
-                    continue
+                do { try data.write(to: URL(fileURLWithPath: fileNameLocalPath)) } catch { continue }
+                parallelizer.execute { completion in
+                    self.upload(fileName: fileName, serverUrlFileName: serverUrlFileName, fileNameLocalPath: fileNameLocalPath, serverUrl: serverUrl, completion: completion)
                 }
             }
         }
+        parallelizer.completeWork()
     }
 
     // MARK: -
@@ -658,8 +628,7 @@ import JGProgressHUD
         let titleOffline = isOffline ? NSLocalizedString("_remove_available_offline_", comment: "") :  NSLocalizedString("_set_available_offline_", comment: "")
 
         let copy = UIAction(title: NSLocalizedString("_copy_file_", comment: ""), image: UIImage(systemName: "doc.on.doc")) { _ in
-            self.appDelegate.pasteboardOcIds = [metadata.ocId]
-            self.copyPasteboard()
+            self.copyPasteboard(pasteboardOcIds: [metadata.ocId], hudView: viewController.view)
         }
 
         let copyPath = UIAction(title: NSLocalizedString("_copy_path_", comment: ""), image: UIImage(systemName: "doc.on.clipboard")) { _ in
@@ -700,7 +669,7 @@ import JGProgressHUD
             if metadataMOV != nil {
                 self.saveLivePhoto(metadata: metadata, metadataMOV: metadataMOV!)
             } else {
-                if CCUtility.fileProviderStorageExists(metadata.ocId, fileNameView: metadata.fileNameView) {
+                if CCUtility.fileProviderStorageExists(metadata) {
                     self.saveAlbum(metadata: metadata)
                 } else {
                     NCOperationQueue.shared.download(metadata: metadata, selector: NCGlobal.shared.selectorSaveAlbum)
@@ -709,7 +678,7 @@ import JGProgressHUD
         }
 
         let saveBackground = UIAction(title: NSLocalizedString("_use_as_background_", comment: ""), image: UIImage(systemName: "text.below.photo")) { _ in
-            if CCUtility.fileProviderStorageExists(metadata.ocId, fileNameView: metadata.fileNameView) {
+            if CCUtility.fileProviderStorageExists(metadata) {
                 self.saveBackground(metadata: metadata)
             } else {
                 NCOperationQueue.shared.download(metadata: metadata, selector: NCGlobal.shared.selectorSaveBackground)
@@ -833,3 +802,17 @@ import JGProgressHUD
         return UIMenu(title: "", children: [detail, submenu])
     }
 }
+
+fileprivate extension tableMetadata {
+    func toPasteBoardItem() -> [String: Any]? {
+        // Get Data
+        let fileUrl = URL(fileURLWithPath: CCUtility.getDirectoryProviderStorageOcId(ocId, fileNameView: fileNameView))
+        guard CCUtility.fileProviderStorageExists(self),
+              let data = try? Data(contentsOf: fileUrl),
+              let unmanagedFileUTI = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, fileExtension as CFString, nil)
+        else { return nil }
+        // Pasteboard item
+        let fileUTI = unmanagedFileUTI.takeRetainedValue() as String
+        return [fileUTI: data]
+    }
+}

+ 2 - 3
iOSClient/Menu/NCCollectionViewCommon+Menu.swift

@@ -244,7 +244,7 @@ extension NCCollectionViewCommon {
                         if metadataMOV != nil {
                             NCFunctionCenter.shared.saveLivePhoto(metadata: metadata, metadataMOV: metadataMOV!)
                         } else {
-                            if CCUtility.fileProviderStorageExists(metadata.ocId, fileNameView: metadata.fileNameView) {
+                            if CCUtility.fileProviderStorageExists(metadata) {
                                 NCFunctionCenter.shared.saveAlbum(metadata: metadata)
                             } else {
                                 NCOperationQueue.shared.download(metadata: metadata, selector: NCGlobal.shared.selectorSaveAlbum)
@@ -320,8 +320,7 @@ extension NCCollectionViewCommon {
                     title: NSLocalizedString("_copy_file_", comment: ""),
                     icon: NCUtility.shared.loadImage(named: "doc.on.doc"),
                     action: { _ in
-                        self.appDelegate.pasteboardOcIds = [metadata.ocId]
-                        NCFunctionCenter.shared.copyPasteboard()
+                        NCFunctionCenter.shared.copyPasteboard(pasteboardOcIds: [metadata.ocId], hudView: self.view)
                     }
                 )
             )

+ 2 - 6
iOSClient/Menu/NCMedia+Menu.swift

@@ -176,7 +176,7 @@ extension NCMedia {
                                     if let metadataMOV = NCManageDatabase.shared.getMetadataLivePhoto(metadata: metadata) {
                                         NCFunctionCenter.shared.saveLivePhoto(metadata: metadata, metadataMOV: metadataMOV)
                                     } else {
-                                        if CCUtility.fileProviderStorageExists(metadata.ocId, fileNameView: metadata.fileNameView) {
+                                        if CCUtility.fileProviderStorageExists(metadata) {
                                             NCFunctionCenter.shared.saveAlbum(metadata: metadata)
                                         } else {
                                             NCOperationQueue.shared.download(metadata: metadata, selector: NCGlobal.shared.selectorSaveAlbum)
@@ -224,11 +224,7 @@ extension NCMedia {
                     icon: NCUtility.shared.loadImage(named: "doc.on.doc"),
                     action: { _ in
                         self.isEditMode = false
-                        self.appDelegate.pasteboardOcIds.removeAll()
-                        for ocId in self.selectOcId {
-                            self.appDelegate.pasteboardOcIds.append(ocId)
-                        }
-                        NCFunctionCenter.shared.copyPasteboard()
+                        NCFunctionCenter.shared.copyPasteboard(pasteboardOcIds: self.selectOcId, hudView: self.view)
                         self.selectOcId.removeAll()
                         self.reloadDataThenPerform { }
                     }

+ 3 - 4
iOSClient/Menu/NCViewer+Menu.swift

@@ -95,7 +95,7 @@ extension NCViewer {
                     title: titleOffline,
                     icon: NCUtility.shared.loadImage(named: "tray.and.arrow.down"),
                     action: { _ in
-                        if (localFile == nil || !CCUtility.fileProviderStorageExists(metadata.ocId, fileNameView: metadata.fileNameView)) && metadata.session == "" {
+                        if (localFile == nil || !CCUtility.fileProviderStorageExists(metadata)) && metadata.session == "" {
                             NCNetworking.shared.download(metadata: metadata, selector: NCGlobal.shared.selectorLoadOffline) { _ in }
                         } else {
                             NCManageDatabase.shared.setLocalFile(ocId: metadata.ocId, offline: !localFile!.offline)
@@ -257,8 +257,7 @@ extension NCViewer {
                 title: NSLocalizedString("_copy_file_", comment: ""),
                 icon: NCUtility.shared.loadImage(named: "doc.on.doc"),
                 action: { _ in
-                    self.appDelegate.pasteboardOcIds = [metadata.ocId]
-                    NCFunctionCenter.shared.copyPasteboard()
+                    NCFunctionCenter.shared.copyPasteboard(pasteboardOcIds: [metadata.ocId], hudView: viewController.view)
                 }
             )
         )
@@ -282,7 +281,7 @@ extension NCViewer {
         // DOWNLOAD IMAGE MAX RESOLUTION
         //
         if metadata.session == "" {
-            if metadata.classFile == NCCommunicationCommon.typeClassFile.image.rawValue && !CCUtility.fileProviderStorageExists(metadata.ocId, fileNameView: metadata.fileNameView) && metadata.session == "" {
+            if metadata.classFile == NCCommunicationCommon.typeClassFile.image.rawValue && !CCUtility.fileProviderStorageExists(metadata) && metadata.session == "" {
                 actions.append(
                     NCMenuAction(
                         title: NSLocalizedString("_download_image_max_", comment: ""),

+ 0 - 1
iOSClient/NCGlobal.swift

@@ -272,7 +272,6 @@ class NCGlobal: NSObject {
     let selectorListingFavorite                     = "listingFavorite"
     let selectorLoadFileView                        = "loadFileView"
     let selectorLoadFileQuickLook                   = "loadFileQuickLook"
-    let selectorLoadCopy                            = "loadCopy"
     let selectorLoadOffline                         = "loadOffline"
     let selectorOpenIn                              = "openIn"
     let selectorPrint                               = "print"

+ 4 - 4
iOSClient/Networking/NCNetworking.swift

@@ -327,7 +327,7 @@ import Queuer
         }
     }
     
-    @objc func download(metadata: tableMetadata, selector: String, notificationCenterProgressTask: Bool = true, progressHandler: @escaping (_ progress: Progress) -> () = { _ in }, completion: @escaping (_ errorCode: Int)->()) {
+    @objc func download(metadata: tableMetadata, selector: String, notificationCenterProgressTask: Bool = true, progressHandler: @escaping (_ progress: Progress) -> Void = { _ in }, completion: @escaping (_ errorCode: Int) -> Void) {
         
         let serverUrlFileName = metadata.serverUrl + "/" + metadata.fileName
         let fileNameLocalPath = CCUtility.getDirectoryProviderStorageOcId(metadata.ocId, fileNameView: metadata.fileName)!
@@ -363,7 +363,7 @@ import Queuer
             
             progressHandler(progress)
                                         
-        }) { (account, etag, date, length, allHeaderFields, error, errorCode, errorDescription) in
+        }) { (account, etag, date, _, allHeaderFields, error, errorCode, errorDescription) in
               
             if error?.isExplicitlyCancelledError ?? false {
 
@@ -448,7 +448,7 @@ import Queuer
         
         let metadata = tableMetadata.init(value: metadata)
 
-        if CCUtility.fileProviderStorageExists(metadata.ocId, fileNameView: metadata.fileNameView) {
+        if CCUtility.fileProviderStorageExists(metadata) {
 
             let fileNameLocalPath = CCUtility.getDirectoryProviderStorageOcId(metadata.ocId, fileNameView: metadata.fileNameView)!
             let results = NCCommunicationCommon.shared.getInternalType(fileName: metadata.fileNameView, mimeType: metadata.contentType, directory: false)
@@ -1367,7 +1367,7 @@ import Queuer
 
     func getVideoUrl(metadata: tableMetadata, completition: @escaping (_ url: URL?) -> Void) {
 
-        if CCUtility.fileProviderStorageExists(metadata.ocId, fileNameView: metadata.fileNameView) {
+        if CCUtility.fileProviderStorageExists(metadata) {
 
             completition(URL(fileURLWithPath: CCUtility.getDirectoryProviderStorageOcId(metadata.ocId, fileNameView: metadata.fileNameView)))
 

+ 3 - 3
iOSClient/Rename file/NCRenameFile.swift

@@ -70,7 +70,7 @@ class NCRenameFile: UIViewController, UITextFieldDelegate {
             fileNameWithoutExt.delegate = self
             fileNameWithoutExt.becomeFirstResponder()
 
-            ext.text = (metadata.fileNameView as NSString).pathExtension
+            ext.text = metadata.fileExtension
             ext.delegate = self
             if disableChangeExt {
                 ext.isEnabled = false
@@ -176,7 +176,7 @@ class NCRenameFile: UIViewController, UITextFieldDelegate {
             } else {
 
                 if ext.text == nil || ext.text?.count == 0 {
-                    self.ext.text = (metadata.fileNameView as NSString).pathExtension
+                    self.ext.text = metadata.fileExtension
                     return
                 } else {
                     extNew = ext.text!
@@ -196,7 +196,7 @@ class NCRenameFile: UIViewController, UITextFieldDelegate {
 
                     title = NSLocalizedString("_keep_", comment: "") + " ." + metadata.ext
                     alertController.addAction(UIAlertAction(title: title, style: .default, handler: { _ in
-                        self.ext.text = (metadata.fileNameView as NSString).pathExtension
+                        self.ext.text = metadata.fileExtension
                     }))
 
                     self.present(alertController, animated: true)

+ 2 - 2
iOSClient/Select/NCSelect.swift

@@ -520,7 +520,7 @@ extension NCSelect: UICollectionViewDataSource {
                 // image local
                 if dataSource.metadataOffLine.contains(metadata.ocId) {
                     cell.imageLocal.image = NCBrandColor.cacheImages.offlineFlag
-                } else if CCUtility.fileProviderStorageExists(metadata.ocId, fileNameView: metadata.fileNameView) {
+                } else if CCUtility.fileProviderStorageExists(metadata) {
                     cell.imageLocal.image = NCBrandColor.cacheImages.local
                 }
             }
@@ -617,7 +617,7 @@ extension NCSelect: UICollectionViewDataSource {
                 // image Local
                 if dataSource.metadataOffLine.contains(metadata.ocId) {
                     cell.imageLocal.image = NCBrandColor.cacheImages.offlineFlag
-                } else if CCUtility.fileProviderStorageExists(metadata.ocId, fileNameView: metadata.fileNameView) {
+                } else if CCUtility.fileProviderStorageExists(metadata) {
                     cell.imageLocal.image = NCBrandColor.cacheImages.local
                 }
             }

+ 1 - 0
iOSClient/Supporting Files/en.lproj/Localizable.strings

@@ -21,6 +21,7 @@
 //
 
 "_cancel_"                  = "Cancel";
+"_tap_to_cancel_"           = "Tap to cancel";
 "_upload_file_"             = "Upload file";
 "_accessibility_add_upload_" = "Add and upload";
 "_download_file_"           = "Download file";

+ 1 - 1
iOSClient/Utility/CCUtility.h

@@ -217,7 +217,7 @@
 + (NSString *)getDirectoryProviderStorageOcId:(NSString *)ocId fileNameView:(NSString *)fileNameView;
 + (NSString *)getDirectoryProviderStorageIconOcId:(NSString *)ocId etag:(NSString *)etag;
 + (NSString *)getDirectoryProviderStoragePreviewOcId:(NSString *)ocId etag:(NSString *)etag;
-+ (BOOL)fileProviderStorageExists:(NSString *)ocId fileNameView:(NSString *)fileNameView;
++ (BOOL)fileProviderStorageExists:(tableMetadata *)metadata;
 + (int64_t)fileProviderStorageSize:(NSString *)ocId fileNameView:(NSString *)fileNameView;
 + (BOOL)fileProviderStoragePreviewIconExists:(NSString *)ocId etag:(NSString *)etag;
 

+ 10 - 7
iOSClient/Utility/CCUtility.m

@@ -1135,6 +1135,8 @@
     NSString *fileNamePath = [NSString stringWithFormat:@"%@/%@", [self getDirectoryProviderStorageOcId:ocId], fileNameView];
     
     // if do not exists create file 0 length
+    // causes files with lenth 0 to never be downloaded, because already exist
+    // also makes it impossible to delete any file with length 0 (from cache)
     if ([[NSFileManager defaultManager] fileExistsAtPath:fileNamePath] == NO) {
         [[NSFileManager defaultManager] createFileAtPath:fileNamePath contents:nil attributes:nil];
     }
@@ -1152,14 +1154,15 @@
     return [NSString stringWithFormat:@"%@/%@.preview.%@", [self getDirectoryProviderStorageOcId:ocId], etag, [NCGlobal shared].extensionPreview];
 }
 
-+ (BOOL)fileProviderStorageExists:(NSString *)ocId fileNameView:(NSString *)fileNameView
++ (BOOL)fileProviderStorageExists:(tableMetadata *)metadata
 {
-    NSString *fileNamePath = [self getDirectoryProviderStorageOcId:ocId fileNameView:fileNameView];
-    
+    NSString *fileNamePath = [self getDirectoryProviderStorageOcId:metadata.ocId fileNameView:metadata.fileNameView];
+    if (![[NSFileManager defaultManager] fileExistsAtPath:fileNamePath]) {
+        return false;
+    }
+
     unsigned long long fileSize = [[[NSFileManager defaultManager] attributesOfItemAtPath:fileNamePath error:nil] fileSize];
-    
-    if (fileSize > 0) return true;
-    else return false;
+    return fileSize == metadata.size;
 }
 
 + (int64_t)fileProviderStorageSize:(NSString *)ocId fileNameView:(NSString *)fileNameView
@@ -1654,7 +1657,7 @@
     int pixelX = 0;
     NSString *lensModel = @"";
 
-    if (![metadata.classFile isEqualToString:@"image"] || ![CCUtility fileProviderStorageExists:metadata.ocId fileNameView:metadata.fileNameView]) {
+    if (![metadata.classFile isEqualToString:@"image"] || ![CCUtility fileProviderStorageExists:metadata]) {
         completition(latitude, longitude, location, date, lensModel);
         return;
     }

+ 1 - 1
iOSClient/Utility/NCUtility.swift

@@ -440,7 +440,7 @@ class NCUtility: NSObject {
         let fileNamePathIcon = CCUtility.getDirectoryProviderStorageIconOcId(ocId, etag: etag)!
 
         if FileManager().fileExists(atPath: fileNamePathPreview) && FileManager().fileExists(atPath: fileNamePathIcon) { return }
-        if !CCUtility.fileProviderStorageExists(ocId, fileNameView: fileName) { return }
+        if CCUtility.fileProviderStorageSize(ocId, fileNameView: fileName) != 0 { return }
         if classFile != NCCommunicationCommon.typeClassFile.image.rawValue && classFile != NCCommunicationCommon.typeClassFile.video.rawValue { return }
 
         if classFile == NCCommunicationCommon.typeClassFile.image.rawValue {

+ 100 - 0
iOSClient/Utility/ParallelWorker.swift

@@ -0,0 +1,100 @@
+//
+//  ParallelWorker.swift
+//  Nextcloud
+//
+//  Created by Henrik Storch on 18.02.22.
+//  Copyright © 2022 Henrik Storch. All rights reserved.
+//
+//  Author Henrik Storch <henrik.storch@nextcloud.com>
+//
+//  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 <http://www.gnu.org/licenses/>.
+//
+
+import UIKit
+import JGProgressHUD
+
+/// Object to execute multiple tasks in parallel like uploading or downloading.
+/// - Can display a progress indicator with status message
+/// - Can be canceled by user
+class ParallelWorker {
+    let completionGroup = DispatchGroup()
+    let queue = DispatchQueue(label: "ParallelWorker")
+    let semaphore: DispatchSemaphore
+    let titleKey: String
+    var hud: JGProgressHUD?
+    var totalTasks: Int?
+    var completedTasks = 0
+    var isCancelled = false
+
+    /// Creates a ParallelWorker
+    /// - Parameters:
+    ///   - n: Amount of tasks to be executed in parallel
+    ///   - titleKey: Localized String key, used for the status. Default: *Please Wait...*
+    ///   - totalTasks: Number of total tasks, if known
+    ///   - hudView: The parent view or current view which should present the progress indicator. If `nil`, no progress indicator will be shown.
+    init(n: Int, titleKey: String?, totalTasks: Int?, hudView: UIView?) {
+        semaphore = DispatchSemaphore(value: n)
+        self.totalTasks = totalTasks
+        self.titleKey = titleKey ?? "_wait_"
+        guard let hudView = hudView else { return }
+
+        DispatchQueue.main.async {
+            let hud = JGProgressHUD()
+            hud.show(in: hudView)
+            hud.textLabel.text = NSLocalizedString(self.titleKey, comment: "")
+            hud.detailTextLabel.text = NSLocalizedString("_tap_to_cancel_", comment: "")
+            hud.tapOnHUDViewBlock = { hud in
+                self.isCancelled = true
+                hud.dismiss()
+            }
+            self.hud = hud
+        }
+    }
+
+    /// Execute
+    /// - Parameter task: The task to execute. Needs to call `completion()` when done so the next task can be executed.
+    func execute(task: @escaping (_ completion: @escaping () -> Void) -> Void) {
+        completionGroup.enter()
+        queue.async {
+            self.semaphore.wait()
+            guard !self.isCancelled else { return self.completionGroup.leave() }
+            task {
+                self.completedTasks += 1
+                DispatchQueue.main.async {
+                    self.hud?.textLabel.text = "\(NSLocalizedString(self.titleKey, comment: "")) \(self.completedTasks) "
+                    if let totalTasks = self.totalTasks {
+                        self.hud?.textLabel.text?.append("\(NSLocalizedString("_of_", comment: "")) \(totalTasks)")
+                    } else {
+                        self.hud?.textLabel.text?.append(NSLocalizedString("_files_", comment: ""))
+                    }
+                }
+                self.semaphore.signal()
+                self.completionGroup.leave()
+            }
+        }
+    }
+
+    /// Indicates that all tasks have been scheduled. Some tasks might still be in progress.
+    /// - Parameter completion: Will be called after all tasks have finished
+    func completeWork(completion: (() -> Void)? = nil) {
+        completionGroup.notify(queue: .main) {
+            guard !self.isCancelled else { return }
+            self.hud?.indicatorView = JGProgressHUDSuccessIndicatorView()
+            self.hud?.textLabel.text = NSLocalizedString("_done_", comment: "")
+            self.hud?.detailTextLabel.text = ""
+            self.hud?.dismiss(afterDelay: 1)
+            completion?()
+        }
+    }
+}

+ 2 - 2
iOSClient/Viewer/NCViewerMedia/NCPlayer/NCKTVHTTPCache.swift

@@ -33,7 +33,7 @@ class NCKTVHTTPCache: NSObject {
 
     func getVideoURL(metadata: tableMetadata) -> URL? {
 
-        if CCUtility.fileProviderStorageExists(metadata.ocId, fileNameView: metadata.fileNameView) {
+        if CCUtility.fileProviderStorageExists(metadata) {
 
             return URL(fileURLWithPath: CCUtility.getDirectoryProviderStorageOcId(metadata.ocId, fileNameView: metadata.fileNameView))
 
@@ -94,7 +94,7 @@ class NCKTVHTTPCache: NSObject {
 
     func saveCache(metadata: tableMetadata) {
 
-        if !CCUtility.fileProviderStorageExists(metadata.ocId, fileNameView: metadata.fileNameView) {
+        if !CCUtility.fileProviderStorageExists(metadata) {
 
             guard let stringURL = (metadata.serverUrl + "/" + metadata.fileName).addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) else { return }
 

+ 3 - 3
iOSClient/Viewer/NCViewerMedia/NCViewerMedia.swift

@@ -241,7 +241,7 @@ class NCViewerMedia: UIViewController {
             let isFolderEncrypted = CCUtility.isFolderEncrypted(metadata.serverUrl, e2eEncrypted: metadata.e2eEncrypted, account: metadata.account, urlBase: metadata.urlBase)
             let ext = CCUtility.getExtension(metadata.fileNameView)
 
-            if (CCUtility.getAutomaticDownloadImage() || (metadata.contentType == "image/heic" &&  metadata.hasPreview == false) || ext == "GIF" || ext == "SVG" || isFolderEncrypted) && (metadata.classFile == NCCommunicationCommon.typeClassFile.image.rawValue && !CCUtility.fileProviderStorageExists(metadata.ocId, fileNameView: metadata.fileNameView) && metadata.session == "") {
+            if (CCUtility.getAutomaticDownloadImage() || (metadata.contentType == "image/heic" &&  metadata.hasPreview == false) || ext == "GIF" || ext == "SVG" || isFolderEncrypted) && (metadata.classFile == NCCommunicationCommon.typeClassFile.image.rawValue && !CCUtility.fileProviderStorageExists(metadata) && metadata.session == "") {
 
                 NCNetworking.shared.download(metadata: metadata, selector: "") { _ in
 
@@ -265,7 +265,7 @@ class NCViewerMedia: UIViewController {
 
             let fileName = (metadata.fileNameView as NSString).deletingPathExtension + ".mov"
 
-            if let metadata = NCManageDatabase.shared.getMetadata(predicate: NSPredicate(format: "account == %@ AND serverUrl == %@ AND fileNameView LIKE[c] %@", metadata.account, metadata.serverUrl, fileName)), !CCUtility.fileProviderStorageExists(metadata.ocId, fileNameView: metadata.fileNameView) {
+            if let metadata = NCManageDatabase.shared.getMetadata(predicate: NSPredicate(format: "account == %@ AND serverUrl == %@ AND fileNameView LIKE[c] %@", metadata.account, metadata.serverUrl, fileName)), !CCUtility.fileProviderStorageExists(metadata) {
 
                 NCNetworking.shared.download(metadata: metadata, selector: "") { _ in }
             }
@@ -301,7 +301,7 @@ class NCViewerMedia: UIViewController {
             let ext = CCUtility.getExtension(metadata.fileNameView)
             var image: UIImage?
 
-            if CCUtility.fileProviderStorageExists(metadata.ocId, fileNameView: metadata.fileNameView) && metadata.classFile == NCCommunicationCommon.typeClassFile.image.rawValue {
+            if CCUtility.fileProviderStorageExists(metadata) && metadata.classFile == NCCommunicationCommon.typeClassFile.image.rawValue {
 
                 let previewPath = CCUtility.getDirectoryProviderStoragePreviewOcId(metadata.ocId, etag: metadata.etag)!
                 let imagePath = CCUtility.getDirectoryProviderStorageOcId(metadata.ocId, fileNameView: metadata.fileNameView)!

+ 1 - 1
iOSClient/Viewer/NCViewerMedia/NCViewerMediaDetailView.swift

@@ -156,7 +156,7 @@ class NCViewerMediaDetailView: UIView {
         }
 
         // Message
-        if metadata.classFile == NCCommunicationCommon.typeClassFile.image.rawValue && !CCUtility.fileProviderStorageExists(metadata.ocId, fileNameView: metadata.fileNameView) && metadata.session == "" {
+        if metadata.classFile == NCCommunicationCommon.typeClassFile.image.rawValue && !CCUtility.fileProviderStorageExists(metadata) && metadata.session == "" {
             messageButton.setTitle(NSLocalizedString("_try_download_full_resolution_", comment: ""), for: .normal)
             messageButton.isHidden = false
         } else {

+ 1 - 1
iOSClient/Viewer/NCViewerMedia/NCViewerMediaPage.swift

@@ -583,7 +583,7 @@ extension NCViewerMediaPage: UIGestureRecognizerDelegate {
             currentViewController.statusLabel.isHidden = true
 
             let fileName = (currentViewController.metadata.fileNameView as NSString).deletingPathExtension + ".mov"
-            if let metadata = NCManageDatabase.shared.getMetadata(predicate: NSPredicate(format: "account == %@ AND serverUrl == %@ AND fileNameView LIKE[c] %@", currentViewController.metadata.account, currentViewController.metadata.serverUrl, fileName)), CCUtility.fileProviderStorageExists(metadata.ocId, fileNameView: metadata.fileNameView) {
+            if let metadata = NCManageDatabase.shared.getMetadata(predicate: NSPredicate(format: "account == %@ AND serverUrl == %@ AND fileNameView LIKE[c] %@", currentViewController.metadata.account, currentViewController.metadata.serverUrl, fileName)), CCUtility.fileProviderStorageExists(metadata) {
 
                 AudioServicesPlaySystemSound(1519) // peek feedback
 

+ 9 - 11
iOSClient/Viewer/NCViewerProviderContextMenu.swift

@@ -80,30 +80,28 @@ class NCViewerProviderContextMenu: UIViewController {
             }
 
             // VIEW IMAGE
-            if metadata.classFile == NCCommunicationCommon.typeClassFile.image.rawValue && CCUtility.fileProviderStorageExists(metadata.ocId, fileNameView: metadata.fileNameView) {
-
+            if metadata.classFile == NCCommunicationCommon.typeClassFile.image.rawValue && CCUtility.fileProviderStorageExists(metadata) {
                 viewImage(metadata: metadata)
             }
 
             // VIEW LIVE PHOTO
-            if metadataLivePhoto != nil && CCUtility.fileProviderStorageExists(metadataLivePhoto!.ocId, fileNameView: metadataLivePhoto!.fileNameView) {
-
-                viewVideo(metadata: metadataLivePhoto!)
+            if let metadataLivePhoto = metadataLivePhoto, CCUtility.fileProviderStorageExists(metadataLivePhoto) {
+                viewVideo(metadata: metadataLivePhoto)
             }
 
             // VIEW VIDEO
-            if metadata.classFile == NCCommunicationCommon.typeClassFile.video.rawValue && CCUtility.fileProviderStorageExists(metadata.ocId, fileNameView: metadata.fileNameView) {
+            if metadata.classFile == NCCommunicationCommon.typeClassFile.video.rawValue && CCUtility.fileProviderStorageExists(metadata) {
                 viewVideo(metadata: metadata)
             }
 
             // PLAY SOUND
-            if metadata.classFile == NCCommunicationCommon.typeClassFile.audio.rawValue && CCUtility.fileProviderStorageExists(metadata.ocId, fileNameView: metadata.fileNameView) {
+            if metadata.classFile == NCCommunicationCommon.typeClassFile.audio.rawValue && CCUtility.fileProviderStorageExists(metadata) {
                 playSound(metadata: metadata)
             }
 
             // AUTO DOWNLOAD VIDEO / AUDIO
             // if !CCUtility.fileProviderStorageExists(metadata.ocId, fileNameView: metadata.fileNameView) && (metadata.classFile == NCCommunicationCommon.typeClassFile.video.rawValue || metadata.classFile == NCCommunicationCommon.typeClassFile.audio.rawValue || metadata.contentType == "application/pdf") {
-            if !CCUtility.fileProviderStorageExists(metadata.ocId, fileNameView: metadata.fileNameView) && (metadata.classFile == NCCommunicationCommon.typeClassFile.video.rawValue || metadata.classFile == NCCommunicationCommon.typeClassFile.audio.rawValue) {
+            if !CCUtility.fileProviderStorageExists(metadata) && (metadata.classFile == NCCommunicationCommon.typeClassFile.video.rawValue || metadata.classFile == NCCommunicationCommon.typeClassFile.audio.rawValue) {
 
                 var maxDownload: UInt64 = 0
 
@@ -119,18 +117,18 @@ class NCViewerProviderContextMenu: UIViewController {
             }
 
             // AUTO DOWNLOAD IMAGE GIF
-            if !CCUtility.fileProviderStorageExists(metadata.ocId, fileNameView: metadata.fileNameView) && metadata.contentType == "image/gif" {
+            if !CCUtility.fileProviderStorageExists(metadata) && metadata.contentType == "image/gif" {
                 NCOperationQueue.shared.download(metadata: metadata, selector: "")
             }
 
             // AUTO DOWNLOAD IMAGE SVG
-            if !CCUtility.fileProviderStorageExists(metadata.ocId, fileNameView: metadata.fileNameView) && metadata.contentType == "image/svg+xml" {
+            if !CCUtility.fileProviderStorageExists(metadata) && metadata.contentType == "image/svg+xml" {
                 NCOperationQueue.shared.download(metadata: metadata, selector: "")
             }
 
             // AUTO DOWNLOAD LIVE PHOTO
             if let metadataLivePhoto = self.metadataLivePhoto {
-                if !CCUtility.fileProviderStorageExists(metadataLivePhoto.ocId, fileNameView: metadataLivePhoto.fileNameView) {
+                if !CCUtility.fileProviderStorageExists(metadataLivePhoto) {
                     NCOperationQueue.shared.download(metadata: metadataLivePhoto, selector: "")
                 }
             }