Browse Source

Make `ParallelWorker` and use it for copying and pasting the UIPasteboard

Refactor upload to only handle upload task, move setting the filename and writing the data into `pastePasteboard` for an earlier return.

Signed-off-by: Henrik Storch <henrik.storch@nextcloud.com>
Henrik Storch 3 years ago
parent
commit
a9155b4fb7

+ 4 - 0
Nextcloud.xcodeproj/project.pbxproj

@@ -27,6 +27,7 @@
 		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 */; };
 		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 +466,7 @@
 		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>"; };
 		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>"; };
@@ -1449,6 +1451,7 @@
 				F70BFC7320E0FA7C00C67599 /* NCUtility.swift */,
 				AF817EF0274BC781009ED85B /* NCUserBaseUrl.swift */,
 				F74AF3A3247FB6AE00AC767B /* NCUtilityFileSystem.swift */,
+				AF36077027BFA4E8001A243D /* ParallelWorker.swift */,
 				F702F2FC25EE5D2C008F8E80 /* NYMnemonic */,
 			);
 			path = Utility;
@@ -2385,6 +2388,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 */,

+ 33 - 71
iOSClient/Main/NCFunctionCenter.swift

@@ -416,10 +416,7 @@ import JGProgressHUD
 
     func copyPasteboard(pasteboardOcIds: [String], hudView: UIView) {
         var items = [[String: Any]]()
-        var isCancelled = false
         let hud = JGProgressHUD()
-        let copyDispatchGroup = DispatchGroup()
-        let seamphore = DispatchSemaphore(value: 5)
         hud.textLabel.text = NSLocalizedString("_wait_", comment: "")
         hud.show(in: hudView)
 
@@ -432,94 +429,59 @@ import JGProgressHUD
                 else { downloadMetadatas.append(metadata) }
             }
 
-            if !downloadMetadatas.isEmpty {
-                DispatchQueue.main.async {
-                    hud.textLabel.text = NSLocalizedString("_status_downloading_", comment: "")
-                    hud.detailTextLabel.text = NSLocalizedString("_tap_to_cancel_", comment: "")
-                    hud.tapOnHUDViewBlock = { hud in
-                        isCancelled = true
-                        hud.dismiss()
-                    }
-                }
-            }
+            DispatchQueue.main.async(execute: hud.dismiss)
 
             // do 5 downloads in paralell to optimize efficiancy
+            let parallelizer = ParallelWorker(n: 5, titleKey: "_downloading_", totalTasks: downloadMetadatas.count, hudView: hudView)
+
             for metadata in downloadMetadatas {
-                guard !isCancelled else { return }
-                copyDispatchGroup.enter()
-                NCNetworking.shared.download(metadata: metadata, selector: "") { _ in
-                    copyDispatchGroup.leave()
-                    seamphore.signal()
+                parallelizer.execute { completion in
+                    NCNetworking.shared.download(metadata: metadata, selector: "") { _ in completion() }
                 }
-                seamphore.wait()
             }
-
-            copyDispatchGroup.notify(queue: .main, execute: {
-                guard !isCancelled else { return }
-                hud.indicatorView = JGProgressHUDSuccessIndicatorView()
-                hud.textLabel.text = NSLocalizedString("_success_", comment: "")
-                hud.detailTextLabel.text = ""
-                hud.dismiss(afterDelay: 1)
+            parallelizer.completeWork {
                 items.append(contentsOf: downloadMetadatas.compactMap({ $0.toPasteBoardItem() }))
                 UIPasteboard.general.setItems(items, options: [:])
-            })
+            }
         }
     }
 
-    func upload(pasteboardType: String?, data: Data?, to serverUrl: String) -> Bool {
-
-        guard let data = data, let pasteboardType = pasteboardType else { return false }
-
-        let results = NCCommunicationCommon.shared.getFileProperties(inUTI: pasteboardType as CFString)
-        guard !results.ext.isEmpty else { return false }
-
-        do {
-            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)
+    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)
             }
-        } catch {
-            return false
+            completion()
         }
-        return true
     }
 
     func pastePasteboard(serverUrl: String) {
+        let parallelizer = ParallelWorker(n: 5, titleKey: "_uploading_", totalTasks: nil, hudView: appDelegate.window?.rootViewController?.view)
+
         for (index, items) in UIPasteboard.general.items.enumerated() {
             for item in items {
-                let data = UIPasteboard.general.data(forPasteboardType: item.key, inItemSet: IndexSet([index]))?.first
-                if upload(pasteboardType: item.key, data: data, to: serverUrl) {
-                    continue
+                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)!
+                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: -

+ 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) {
+        queue.async {
+            self.semaphore.wait()
+            guard !self.isCancelled else { return }
+            self.completionGroup.enter()
+            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.completionGroup.leave()
+                self.semaphore.signal()
+            }
+        }
+    }
+
+    /// 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?()
+        }
+    }
+}