Эх сурвалжийг харах

Merge pull request #2642 from nextcloud/queue

Queue
Marino Faggiana 1 жил өмнө
parent
commit
2ccb45e282

+ 32 - 4
Nextcloud.xcodeproj/project.pbxproj

@@ -161,6 +161,8 @@
 		F70753EB2542A99800972D44 /* NCViewerMediaPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = F70753EA2542A99800972D44 /* NCViewerMediaPage.swift */; };
 		F70753F12542A9A200972D44 /* NCViewerMedia.swift in Sources */ = {isa = PBXBuildFile; fileRef = F70753F02542A9A200972D44 /* NCViewerMedia.swift */; };
 		F70753F72542A9C000972D44 /* NCViewerMediaPage.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = F70753F62542A9C000972D44 /* NCViewerMediaPage.storyboard */; };
+		F7075B672AE15F6200512300 /* NCCellProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 370D26AE248A3D7A00121797 /* NCCellProtocol.swift */; };
+		F7075B682AE15F8100512300 /* NCCellProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 370D26AE248A3D7A00121797 /* NCCellProtocol.swift */; };
 		F707C26521A2DC5200F6181E /* NCStoreReview.swift in Sources */ = {isa = PBXBuildFile; fileRef = F707C26421A2DC5200F6181E /* NCStoreReview.swift */; };
 		F70821D829E59E6D001CA2D7 /* TagListView in Frameworks */ = {isa = PBXBuildFile; productRef = F70821D729E59E6D001CA2D7 /* TagListView */; };
 		F70968A424212C4E00ED60E5 /* NCLivePhoto.swift in Sources */ = {isa = PBXBuildFile; fileRef = F70968A324212C4E00ED60E5 /* NCLivePhoto.swift */; };
@@ -214,7 +216,6 @@
 		F72944F52A8424F800246839 /* NCEndToEndMetadataV1.swift in Sources */ = {isa = PBXBuildFile; fileRef = F72944F42A8424F800246839 /* NCEndToEndMetadataV1.swift */; };
 		F72944F62A8424F800246839 /* NCEndToEndMetadataV1.swift in Sources */ = {isa = PBXBuildFile; fileRef = F72944F42A8424F800246839 /* NCEndToEndMetadataV1.swift */; };
 		F72A17D828B221E300F3F159 /* DashboardWidgetView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F72A17D728B221E300F3F159 /* DashboardWidgetView.swift */; };
-		F72A47EC2487B06B005AD489 /* NCOperationQueue.swift in Sources */ = {isa = PBXBuildFile; fileRef = F72A47EB2487B06B005AD489 /* NCOperationQueue.swift */; };
 		F72AD70D28C24B93006CB92D /* NextcloudKit in Frameworks */ = {isa = PBXBuildFile; productRef = F72AD70C28C24B93006CB92D /* NextcloudKit */; };
 		F72AD70F28C24BA1006CB92D /* NextcloudKit in Frameworks */ = {isa = PBXBuildFile; productRef = F72AD70E28C24BA1006CB92D /* NextcloudKit */; };
 		F72AD71128C24BBB006CB92D /* NextcloudKit in Frameworks */ = {isa = PBXBuildFile; productRef = F72AD71028C24BBB006CB92D /* NextcloudKit */; };
@@ -549,6 +550,10 @@
 		F7A321AD1E9E6AD50069AD1B /* CCAdvanced.m in Sources */ = {isa = PBXBuildFile; fileRef = F7A321AC1E9E6AD50069AD1B /* CCAdvanced.m */; };
 		F7A48413297022E000BD1B49 /* ViewerQuickLook.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7A48412297022E000BD1B49 /* ViewerQuickLook.swift */; };
 		F7A48415297028FC00BD1B49 /* Nextcloud Hub.png in Resources */ = {isa = PBXBuildFile; fileRef = F7A48414297028FC00BD1B49 /* Nextcloud Hub.png */; };
+		F7A560422AE1593700BE8FD6 /* NCOperationSaveLivePhoto.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7A560412AE1593700BE8FD6 /* NCOperationSaveLivePhoto.swift */; };
+		F7A560442AE15D2900BE8FD6 /* Queuer in Frameworks */ = {isa = PBXBuildFile; productRef = F7A560432AE15D2900BE8FD6 /* Queuer */; };
+		F7A560462AE15D3D00BE8FD6 /* Queuer in Frameworks */ = {isa = PBXBuildFile; productRef = F7A560452AE15D3D00BE8FD6 /* Queuer */; };
+		F7A560482AE15D5000BE8FD6 /* Queuer in Frameworks */ = {isa = PBXBuildFile; productRef = F7A560472AE15D5000BE8FD6 /* Queuer */; };
 		F7A60F86292D215000FCE1F2 /* NCShareAccounts.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7A60F84292D215000FCE1F2 /* NCShareAccounts.swift */; };
 		F7A60F87292D215000FCE1F2 /* NCShareAccounts.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = F7A60F85292D215000FCE1F2 /* NCShareAccounts.storyboard */; };
 		F7A76DC8256A71CD00119AB3 /* UIImage+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7B7504A2397D38E004E13EC /* UIImage+Extension.swift */; };
@@ -971,7 +976,6 @@
 		F72944F12A84246400246839 /* NCEndToEndMetadataV20.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCEndToEndMetadataV20.swift; sourceTree = "<group>"; };
 		F72944F42A8424F800246839 /* NCEndToEndMetadataV1.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCEndToEndMetadataV1.swift; sourceTree = "<group>"; };
 		F72A17D728B221E300F3F159 /* DashboardWidgetView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DashboardWidgetView.swift; sourceTree = "<group>"; };
-		F72A47EB2487B06B005AD489 /* NCOperationQueue.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCOperationQueue.swift; sourceTree = "<group>"; };
 		F72CD63925C19EBF00F46F9A /* NCAutoUpload.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCAutoUpload.swift; sourceTree = "<group>"; };
 		F72D1005210B6882009C96B7 /* NCPushNotificationEncryption.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NCPushNotificationEncryption.m; sourceTree = "<group>"; };
 		F72D1006210B6882009C96B7 /* NCPushNotificationEncryption.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NCPushNotificationEncryption.h; sourceTree = "<group>"; };
@@ -1171,6 +1175,7 @@
 		F7A321AC1E9E6AD50069AD1B /* CCAdvanced.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CCAdvanced.m; sourceTree = "<group>"; };
 		F7A48412297022E000BD1B49 /* ViewerQuickLook.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewerQuickLook.swift; sourceTree = "<group>"; };
 		F7A48414297028FC00BD1B49 /* Nextcloud Hub.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Nextcloud Hub.png"; sourceTree = SOURCE_ROOT; };
+		F7A560412AE1593700BE8FD6 /* NCOperationSaveLivePhoto.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCOperationSaveLivePhoto.swift; sourceTree = "<group>"; };
 		F7A60F84292D215000FCE1F2 /* NCShareAccounts.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NCShareAccounts.swift; sourceTree = "<group>"; };
 		F7A60F85292D215000FCE1F2 /* NCShareAccounts.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = NCShareAccounts.storyboard; sourceTree = "<group>"; };
 		F7A7FA6229265CF4000603EF /* NCManageE2EE.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCManageE2EE.swift; sourceTree = "<group>"; };
@@ -1444,6 +1449,7 @@
 				F72CD01227A7E92400E59476 /* JGProgressHUD in Frameworks */,
 				F77CB6A92AA08053000C3CA4 /* OpenSSL in Frameworks */,
 				F73ADD2126554F8E0069EA0D /* SwiftEntryKit in Frameworks */,
+				F7A560482AE15D5000BE8FD6 /* Queuer in Frameworks */,
 				F7EBCDCF277B81FF00A4EF67 /* UICKeyChainStore in Frameworks */,
 				F70821D829E59E6D001CA2D7 /* TagListView in Frameworks */,
 				F7F623B72A5EFA0C0022D3D4 /* Gzip in Frameworks */,
@@ -1460,6 +1466,7 @@
 				F783034428B5142B00B84583 /* NextcloudKit in Frameworks */,
 				F7346E1328B0EF5B006CE2D2 /* SwiftUI.framework in Frameworks */,
 				F7346E2928B0FFF2006CE2D2 /* RealmSwift in Frameworks */,
+				F7A560462AE15D3D00BE8FD6 /* Queuer in Frameworks */,
 				F783030D28B4C59A00B84583 /* SwiftEntryKit in Frameworks */,
 				F7346E1228B0EF5B006CE2D2 /* WidgetKit.framework in Frameworks */,
 			);
@@ -1469,6 +1476,7 @@
 			isa = PBXFrameworksBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
+				F7A560442AE15D2900BE8FD6 /* Queuer in Frameworks */,
 				F7EBCDD1277B820D00A4EF67 /* UICKeyChainStore in Frameworks */,
 				F73ADD2426554FE20069EA0D /* SwiftEntryKit in Frameworks */,
 				F710FC84277B7D3500AA9FBF /* RealmSwift in Frameworks */,
@@ -1552,6 +1560,7 @@
 				AF93471127E2341B002537EE /* NCShare+Menu.swift */,
 				F710D2012405826100A6033D /* NCViewer+Menu.swift */,
 				F78C6FDD296D677300C952C3 /* NCContextMenu.swift */,
+				F7A560412AE1593700BE8FD6 /* NCOperationSaveLivePhoto.swift */,
 			);
 			path = Menu;
 			sourceTree = "<group>";
@@ -1882,7 +1891,6 @@
 				F75A9EE523796C6F0044CFCE /* NCNetworking.swift */,
 				F7D96FCB246ED7E100536D73 /* NCNetworkingCheckRemoteUser.swift */,
 				F70D8D8024A4A9BF000A5756 /* NCNetworkingProcessUpload.swift */,
-				F72A47EB2487B06B005AD489 /* NCOperationQueue.swift */,
 				F755BD9A20594AC7008C5FBB /* NCService.swift */,
 				F77BC3EC293E528A005F2B08 /* NCConfigServer.swift */,
 			);
@@ -2793,6 +2801,7 @@
 				F70821D729E59E6D001CA2D7 /* TagListView */,
 				F7F623B62A5EFA0C0022D3D4 /* Gzip */,
 				F77CB6A82AA08053000C3CA4 /* OpenSSL */,
+				F7A560472AE15D5000BE8FD6 /* Queuer */,
 			);
 			productName = "Share Ext";
 			productReference = F7CE8AFB1DC1F8D8009CAE48 /* Share.appex */;
@@ -2817,6 +2826,7 @@
 				F783030C28B4C59A00B84583 /* SwiftEntryKit */,
 				F783034328B5142B00B84583 /* NextcloudKit */,
 				F787AC0A298BCB540001BB00 /* SVGKitSwift */,
+				F7A560452AE15D3D00BE8FD6 /* Queuer */,
 			);
 			productName = DashboardWidgetExtension;
 			productReference = F7346E1028B0EF5B006CE2D2 /* Widget.appex */;
@@ -2840,6 +2850,7 @@
 				F710FC83277B7D3500AA9FBF /* RealmSwift */,
 				F7EBCDD0277B820D00A4EF67 /* UICKeyChainStore */,
 				F72AD71228C24BCC006CB92D /* NextcloudKit */,
+				F7A560432AE15D2900BE8FD6 /* Queuer */,
 			);
 			productName = "File Provider Extension";
 			productReference = F771E3D020E2392D00AFB62D /* File Provider Extension.appex */;
@@ -3537,6 +3548,7 @@
 				F72EA95A28B7BD0D00C88F0C /* FilesWidgetView.swift in Sources */,
 				F78302FE28B4C44700B84583 /* NCBrand.swift in Sources */,
 				F749B64B297B0CBB00087535 /* NCManageDatabase+Share.swift in Sources */,
+				F7075B682AE15F8100512300 /* NCCellProtocol.swift in Sources */,
 				F7817CF929801A3500FFBC65 /* Data+Extension.swift in Sources */,
 				F7864ACD2A78FE73004870E0 /* NCManageDatabase+LocalFile.swift in Sources */,
 				F343A4B42A1E084100DDA874 /* PHAsset+Extension.swift in Sources */,
@@ -3619,6 +3631,7 @@
 				F343A4BF2A1E734600DDA874 /* Optional+Extension.swift in Sources */,
 				AF4BF62027562B3F0081CEEF /* NCManageDatabase+Activity.swift in Sources */,
 				F785EEA62461A4FB00B3F945 /* CCUtility.m in Sources */,
+				F7075B672AE15F6200512300 /* NCCellProtocol.swift in Sources */,
 				F73ADD2226554FD10069EA0D /* NCContentPresenter.swift in Sources */,
 				F72FD3B9297ED49A00075D28 /* NCManageDatabase+E2EE.swift in Sources */,
 			);
@@ -3661,7 +3674,6 @@
 				F79EDAA326B004980007D134 /* NCPlayerToolBar.swift in Sources */,
 				F77444F8222816D5000D5EB0 /* NCPickerViewController.swift in Sources */,
 				F77BB74A2899857B0090FC19 /* UINavigationController+Extension.swift in Sources */,
-				F72A47EC2487B06B005AD489 /* NCOperationQueue.swift in Sources */,
 				F769454622E9F1B0000A798A /* NCShareCommon.swift in Sources */,
 				F70753F12542A9A200972D44 /* NCViewerMedia.swift in Sources */,
 				F76B649C2ADFFAED00014640 /* NCMediaCache.swift in Sources */,
@@ -3776,6 +3788,7 @@
 				F70CAE3A1F8CF31A008125FD /* NCEndToEndEncryption.m in Sources */,
 				AF93471B27E2361E002537EE /* NCShareAdvancePermission.swift in Sources */,
 				F77BC3ED293E528A005F2B08 /* NCConfigServer.swift in Sources */,
+				F7A560422AE1593700BE8FD6 /* NCOperationSaveLivePhoto.swift in Sources */,
 				F70753EB2542A99800972D44 /* NCViewerMediaPage.swift in Sources */,
 				F7817CF829801A3500FFBC65 /* Data+Extension.swift in Sources */,
 				F749B651297B0F2400087535 /* NCManageDatabase+Avatar.swift in Sources */,
@@ -5427,6 +5440,21 @@
 			package = F7A1050C29E587AF00FFD92B /* XCRemoteSwiftPackageReference "TagListView" */;
 			productName = TagListView;
 		};
+		F7A560432AE15D2900BE8FD6 /* Queuer */ = {
+			isa = XCSwiftPackageProductDependency;
+			package = F76DA961277B760E0082465B /* XCRemoteSwiftPackageReference "Queuer" */;
+			productName = Queuer;
+		};
+		F7A560452AE15D3D00BE8FD6 /* Queuer */ = {
+			isa = XCSwiftPackageProductDependency;
+			package = F76DA961277B760E0082465B /* XCRemoteSwiftPackageReference "Queuer" */;
+			productName = Queuer;
+		};
+		F7A560472AE15D5000BE8FD6 /* Queuer */ = {
+			isa = XCSwiftPackageProductDependency;
+			package = F76DA961277B760E0082465B /* XCRemoteSwiftPackageReference "Queuer" */;
+			productName = Queuer;
+		};
 		F7A8D72328F1771B008BBE1C /* NextcloudKit */ = {
 			isa = XCSwiftPackageProductDependency;
 			package = F783034028B511D200B84583 /* XCRemoteSwiftPackageReference "NextcloudKit" */;

+ 2 - 2
iOSClient/Activity/NCActivity.swift

@@ -212,7 +212,7 @@ extension NCActivity: UITableViewDataSource {
 
         // Image
         let fileName = appDelegate.userBaseUrl + "-" + comment.actorId + ".png"
-        NCOperationQueue.shared.downloadAvatar(user: comment.actorId, dispalyName: comment.actorDisplayName, fileName: fileName, cell: cell, view: tableView, cellImageView: cell.fileAvatarImageView)
+        NCNetworking.shared.downloadAvatar(user: comment.actorId, dispalyName: comment.actorDisplayName, fileName: fileName, cell: cell, view: tableView, cellImageView: cell.fileAvatarImageView)
         // Username
         cell.labelUser.text = comment.actorDisplayName
         cell.labelUser.textColor = .label
@@ -279,7 +279,7 @@ extension NCActivity: UITableViewDataSource {
 
             let fileName = appDelegate.userBaseUrl + "-" + activity.user + ".png"
 
-            NCOperationQueue.shared.downloadAvatar(user: activity.user, dispalyName: nil, fileName: fileName, cell: cell, view: tableView, cellImageView: cell.fileAvatarImageView)
+            NCNetworking.shared.downloadAvatar(user: activity.user, dispalyName: nil, fileName: fileName, cell: cell, view: tableView, cellImageView: cell.fileAvatarImageView)
         }
 
         // subject

+ 54 - 1
iOSClient/Activity/NCActivityTableViewCell.swift

@@ -25,6 +25,7 @@ import Foundation
 import NextcloudKit
 import FloatingPanel
 import JGProgressHUD
+import Queuer
 
 class NCActivityCollectionViewCell: UICollectionViewCell {
 
@@ -194,7 +195,13 @@ extension NCActivityTableViewCell: UICollectionViewDataSource {
                     if FileManager.default.fileExists(atPath: fileNamePath), let image = UIImage(contentsOfFile: fileNamePath) {
                         cell.imageView.image = image
                     } else {
-                        NCOperationQueue.shared.downloadThumbnailActivity(fileNamePathOrFileId: activityPreview.source, fileNamePreviewLocalPath: fileNamePath, fileId: fileId, cell: cell, collectionView: collectionView)
+                        cell.imageView?.image = UIImage(named: "file_photo")
+                        cell.fileId = fileId
+                        if !FileManager.default.fileExists(atPath: fileNamePath) {
+                            if appDelegate.downloadThumbnailActivityQueue.operations.filter({ ($0 as? NCOperationDownloadThumbnailActivity)?.fileId == fileId }).isEmpty {
+                                appDelegate.downloadThumbnailActivityQueue.addOperation(NCOperationDownloadThumbnailActivity(fileNamePathOrFileId: activityPreview.source, fileNamePreviewLocalPath: fileNamePath, fileId: fileId, cell: cell, collectionView: collectionView))
+                            }
+                        }
                     }
                 }
             }
@@ -219,3 +226,49 @@ extension NCActivityTableViewCell: UICollectionViewDelegateFlowLayout {
         return UIEdgeInsets(top: 0, left: 0, bottom: 10, right: 0)
     }
 }
+
+class NCOperationDownloadThumbnailActivity: ConcurrentOperation {
+
+    var cell: NCActivityCollectionViewCell?
+    var collectionView: UICollectionView?
+    var fileNamePathOrFileId: String
+    var fileNamePreviewLocalPath: String
+    var fileId: String
+
+    init(fileNamePathOrFileId: String, fileNamePreviewLocalPath: String, fileId: String, cell: NCActivityCollectionViewCell?, collectionView: UICollectionView?) {
+        self.fileNamePathOrFileId = fileNamePathOrFileId
+        self.fileNamePreviewLocalPath = fileNamePreviewLocalPath
+        self.fileId = fileId
+        self.cell = cell
+        self.collectionView = collectionView
+    }
+
+    override func start() {
+
+        guard !isCancelled else { return self.finish() }
+
+        NextcloudKit.shared.downloadPreview(fileNamePathOrFileId: fileNamePathOrFileId,
+                                            fileNamePreviewLocalPath: fileNamePreviewLocalPath,
+                                            widthPreview: 0,
+                                            heightPreview: 0,
+                                            etag: nil,
+                                            useInternalEndpoint: false,
+                                            options: NKRequestOptions(queue: NextcloudKit.shared.nkCommonInstance.backgroundQueue)) { _, imagePreview, _, _, _, error in
+
+            if error == .success, let imagePreview = imagePreview {
+                DispatchQueue.main.async {
+                    if self.fileId == self.cell?.fileId, let imageView = self.cell?.imageView {
+                        UIView.transition(with: imageView,
+                                          duration: 0.75,
+                                          options: .transitionCrossDissolve,
+                                          animations: { imageView.image = imagePreview },
+                                          completion: nil)
+                    } else {
+                        self.collectionView?.reloadData()
+                    }
+                }
+            }
+            self.finish()
+        }
+    }
+}

+ 3 - 1
iOSClient/Files/NCFiles.swift

@@ -165,7 +165,9 @@ class NCFiles: NCCollectionViewCommon {
         networkReadFolder(isForced: isForced) { tableDirectory, metadatas, metadatasUpdate, metadatasDelete, error in
             if error == .success {
                 for metadata in metadatas ?? [] where !metadata.directory && NCManageDatabase.shared.isDownloadMetadata(metadata, download: false) {
-                    NCOperationQueue.shared.download(metadata: metadata, selector: NCGlobal.shared.selectorDownloadFile)
+                    if self.appDelegate.downloadQueue.operations.filter({ ($0 as? NCOperationDownload)?.metadata.ocId == metadata.ocId }).isEmpty {
+                        self.appDelegate.downloadQueue.addOperation(NCOperationDownload(metadata: metadata, selector: NCGlobal.shared.selectorDownloadFile))
+                    }
                 }
             }
 

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

@@ -941,7 +941,7 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS
                     searchResults: self.searchResults)
             } update: { _, _, searchResult, metadatas in
                 guard let metadatas, !metadatas.isEmpty, self.isSearchingMode, let searchResult else { return }
-                NCOperationQueue.shared.unifiedSearchAddSection(collectionViewCommon: self, metadatas: metadatas, searchResult: searchResult)
+                self.appDelegate.unifiedSearchQueue.addOperation(NCOperationUnifiedSearch(collectionViewCommon: self, metadatas: metadatas, searchResult: searchResult))
             } completion: { _, _ in
                 self.refreshControl.endRefreshing()
                 self.isReloadDataSourceNetworkInProgress = false
@@ -1238,7 +1238,7 @@ extension NCCollectionViewCommon: UICollectionViewDataSource {
                 if !metadata.iconUrl.isEmpty {
                     if let ownerId = NCUtility.shared.getAvatarFromIconUrl(metadata: metadata), let cell = cell as? NCCellProtocol {
                         let fileName = metadata.userBaseUrl + "-" + ownerId + ".png"
-                        NCOperationQueue.shared.downloadAvatar(user: ownerId, dispalyName: nil, fileName: fileName, cell: cell, view: collectionView, cellImageView: cell.filePreviewImageView)
+                        NCNetworking.shared.downloadAvatar(user: ownerId, dispalyName: nil, fileName: fileName, cell: cell, view: collectionView, cellImageView: cell.filePreviewImageView)
                     }
                 }
             }
@@ -1250,7 +1250,7 @@ extension NCCollectionViewCommon: UICollectionViewDataSource {
            appDelegate.account == metadata.account,
            let cell = cell as? NCCellProtocol {
             let fileName = metadata.userBaseUrl + "-" + metadata.ownerId + ".png"
-            NCOperationQueue.shared.downloadAvatar(user: metadata.ownerId, dispalyName: metadata.ownerDisplayName, fileName: fileName, cell: cell, view: collectionView, cellImageView: cell.fileAvatarImageView)
+            NCNetworking.shared.downloadAvatar(user: metadata.ownerId, dispalyName: metadata.ownerDisplayName, fileName: fileName, cell: cell, view: collectionView, cellImageView: cell.fileAvatarImageView)
         }
     }
 
@@ -1706,6 +1706,41 @@ extension NCCollectionViewCommon: EasyTipViewDelegate {
     }
 }
 
+// MARK: -
+
+class NCOperationUnifiedSearch: ConcurrentOperation {
+
+    var collectionViewCommon: NCCollectionViewCommon
+    var metadatas: [tableMetadata]
+    var searchResult: NKSearchResult
+
+    init(collectionViewCommon: NCCollectionViewCommon, metadatas: [tableMetadata], searchResult: NKSearchResult) {
+        self.collectionViewCommon = collectionViewCommon
+        self.metadatas = metadatas
+        self.searchResult = searchResult
+    }
+
+    func reloadDataThenPerform(_ closure: @escaping (() -> Void)) {
+        DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
+            CATransaction.begin()
+            CATransaction.setCompletionBlock(closure)
+            self.collectionViewCommon.collectionView.reloadData()
+            CATransaction.commit()
+        }
+    }
+
+    override func start() {
+
+        guard !isCancelled else { return self.finish() }
+
+        self.collectionViewCommon.dataSource.addSection(metadatas: metadatas, searchResult: searchResult)
+        self.collectionViewCommon.searchResults?.append(self.searchResult)
+        reloadDataThenPerform {
+            self.finish()
+        }
+    }
+}
+
 class NCCollectionViewDownloadThumbnail: ConcurrentOperation {
 
     var metadata: tableMetadata

+ 3 - 1
iOSClient/Menu/NCContextMenu.swift

@@ -98,7 +98,9 @@ class NCContextMenu: NSObject {
         let save = UIAction(title: titleSave,
                             image: UIImage(systemName: "square.and.arrow.down")) { _ in
             if let metadataMOV = metadataMOV {
-                NCOperationQueue.shared.saveLivePhoto(metadata: metadata, metadataMOV: metadataMOV)
+                if let appDelegate = (UIApplication.shared.delegate as? AppDelegate) {
+                    appDelegate.saveLivePhotoQueue.addOperation(NCOperationSaveLivePhoto(metadata: metadata, metadataMOV: metadataMOV))
+                }
             } else {
                 if CCUtility.fileProviderStorageExists(metadata) {
                     NCActionCenter.shared.saveAlbum(metadata: metadata)

+ 6 - 2
iOSClient/Menu/NCMenuAction.swift

@@ -222,12 +222,16 @@ extension NCMenuAction {
             action: { _ in
                 for metadata in selectedMediaMetadatas {
                     if let metadataMOV = NCManageDatabase.shared.getMetadataLivePhoto(metadata: metadata) {
-                        NCOperationQueue.shared.saveLivePhoto(metadata: metadata, metadataMOV: metadataMOV)
+                        if let appDelegate = (UIApplication.shared.delegate as? AppDelegate) {
+                            appDelegate.saveLivePhotoQueue.addOperation(NCOperationSaveLivePhoto(metadata: metadata, metadataMOV: metadataMOV))
+                        }
                     } else {
                         if CCUtility.fileProviderStorageExists(metadata) {
                             NCActionCenter.shared.saveAlbum(metadata: metadata)
                         } else {
-                            NCOperationQueue.shared.download(metadata: metadata, selector: NCGlobal.shared.selectorSaveAlbum)
+                            if let appDelegate = (UIApplication.shared.delegate as? AppDelegate), appDelegate.downloadQueue.operations.filter({ ($0 as? NCOperationDownload)?.metadata.ocId == metadata.ocId }).isEmpty {
+                                appDelegate.downloadQueue.addOperation(NCOperationDownload(metadata: metadata, selector: NCGlobal.shared.selectorSaveAlbum))
+                            }
                         }
                     }
                 }

+ 124 - 0
iOSClient/Menu/NCOperationSaveLivePhoto.swift

@@ -0,0 +1,124 @@
+//
+//  NCooo.swift
+//  Nextcloud
+//
+//  Created by Marino Faggiana on 19/10/23.
+//  Copyright © 2023 Marino Faggiana. All rights reserved.
+//
+//  Author Marino Faggiana <marino.faggiana@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 Queuer
+import JGProgressHUD
+
+class NCOperationSaveLivePhoto: ConcurrentOperation {
+
+    var metadata: tableMetadata
+    var metadataMOV: tableMetadata
+    let hud = JGProgressHUD()
+    let appDelegate = UIApplication.shared.delegate as? AppDelegate
+
+    init(metadata: tableMetadata, metadataMOV: tableMetadata) {
+        self.metadata = tableMetadata.init(value: metadata)
+        self.metadataMOV = tableMetadata.init(value: metadataMOV)
+    }
+
+    override func start() {
+        guard !isCancelled else { return self.finish() }
+
+        DispatchQueue.main.async {
+            self.hud.indicatorView = JGProgressHUDRingIndicatorView()
+            if let indicatorView = self.hud.indicatorView as? JGProgressHUDRingIndicatorView {
+                indicatorView.ringWidth = 1.5
+            }
+            self.hud.textLabel.text = NSLocalizedString("_download_image_", comment: "")
+            self.hud.detailTextLabel.text = self.metadata.fileName
+            self.hud.show(in: (self.appDelegate?.window?.rootViewController?.view)!)
+        }
+
+        NCNetworking.shared.download(metadata: metadata, selector: "", notificationCenterProgressTask: false, checkfileProviderStorageExists: true) { _ in
+        } progressHandler: { progress in
+            self.hud.progress = Float(progress.fractionCompleted)
+        } completion: { _, error in
+            guard error == .success else {
+                DispatchQueue.main.async {
+                    self.hud.indicatorView = JGProgressHUDErrorIndicatorView()
+                    self.hud.textLabel.text = NSLocalizedString("_livephoto_save_error_", comment: "")
+                    self.hud.dismiss()
+                }
+                return self.finish()
+            }
+            NCNetworking.shared.download(metadata: self.metadataMOV, selector: "", notificationCenterProgressTask: false, checkfileProviderStorageExists: true) { _ in
+                DispatchQueue.main.async {
+                    self.hud.textLabel.text = NSLocalizedString("_download_video_", comment: "")
+                    self.hud.detailTextLabel.text = self.metadataMOV.fileName
+                }
+            } progressHandler: { progress in
+                self.hud.progress = Float(progress.fractionCompleted)
+            } completion: { _, error in
+                guard error == .success else {
+                    DispatchQueue.main.async {
+                        self.hud.indicatorView = JGProgressHUDErrorIndicatorView()
+                        self.hud.textLabel.text = NSLocalizedString("_livephoto_save_error_", comment: "")
+                        self.hud.dismiss()
+                    }
+                    return self.finish()
+                }
+                self.saveLivePhotoToDisk(metadata: self.metadata, metadataMov: self.metadataMOV)
+            }
+        }
+    }
+
+    func saveLivePhotoToDisk(metadata: tableMetadata, metadataMov: tableMetadata) {
+
+        let fileNameImage = URL(fileURLWithPath: CCUtility.getDirectoryProviderStorageOcId(metadata.ocId, fileNameView: metadata.fileNameView)!)
+        let fileNameMov = URL(fileURLWithPath: CCUtility.getDirectoryProviderStorageOcId(metadataMov.ocId, fileNameView: metadataMov.fileNameView)!)
+
+        DispatchQueue.main.async {
+            self.hud.textLabel.text = NSLocalizedString("_livephoto_save_", comment: "")
+            self.hud.detailTextLabel.text = ""
+        }
+
+        NCLivePhoto.generate(from: fileNameImage, videoURL: fileNameMov, progress: { progress in
+            self.hud.progress = Float(progress)
+        }, completion: { _, resources in
+            if resources != nil {
+                NCLivePhoto.saveToLibrary(resources!) { result in
+                    DispatchQueue.main.async {
+                        if !result {
+                            self.hud.indicatorView = JGProgressHUDErrorIndicatorView()
+                            self.hud.textLabel.text = NSLocalizedString("_livephoto_save_error_", comment: "")
+                        } else {
+                            self.hud.indicatorView = JGProgressHUDSuccessIndicatorView()
+                            self.hud.textLabel.text = NSLocalizedString("_success_", comment: "")
+                        }
+                        self.hud.dismiss()
+                    }
+                    return self.finish()
+                }
+            } else {
+                DispatchQueue.main.async {
+                    self.hud.indicatorView = JGProgressHUDErrorIndicatorView()
+                    self.hud.textLabel.text = NSLocalizedString("_livephoto_save_error_", comment: "")
+                    self.hud.dismiss()
+                }
+                return self.finish()
+            }
+        })
+    }
+}

+ 104 - 2
iOSClient/Networking/NCNetworking.swift

@@ -26,6 +26,7 @@ import OpenSSL
 import NextcloudKit
 import Alamofire
 import Photos
+import Queuer
 
 @objc public protocol NCNetworkingDelegate {
     @objc optional func downloadProgress(_ progress: Float, totalBytes: Int64, totalBytesExpected: Int64, fileName: String, serverUrl: String, session: URLSession, task: URLSessionTask)
@@ -353,6 +354,26 @@ class NCNetworking: NSObject, NKCommonDelegate {
         }
     }
 
+#if !EXTENSION
+    func downloadAvatar(user: String, dispalyName: String?, fileName: String, cell: NCCellProtocol, view: UIView?, cellImageView: UIImageView?) {
+        guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else { return }
+        let fileNameLocalPath = String(CCUtility.getDirectoryUserData()) + "/" + fileName
+
+        if let image = NCManageDatabase.shared.getImageAvatarLoaded(fileName: fileName) {
+            cellImageView?.image = image
+            cell.fileAvatarImageView?.image = image
+            return
+        }
+
+        if let account = NCManageDatabase.shared.getActiveAccount() {
+            cellImageView?.image = NCUtility.shared.loadUserImage(for: user, displayName: dispalyName, userBaseUrl: account)
+        }
+
+        for case let operation as NCOperationDownloadAvatar in appDelegate.downloadAvatarQueue.operations where operation.fileName == fileName { return }
+        appDelegate.downloadAvatarQueue.addOperation(NCOperationDownloadAvatar(user: user, fileName: fileName, fileNameLocalPath: fileNameLocalPath, cell: cell, view: view, cellImageView: cellImageView))
+    }
+#endif
+
     // MARK: - Upload
 
     func upload(metadata: tableMetadata,
@@ -958,8 +979,11 @@ class NCNetworking: NSObject, NKCommonDelegate {
                         if metadata.directory {
                             let serverUrl = metadata.serverUrl + "/" + metadata.fileName
                             NCManageDatabase.shared.addDirectory(encrypted: metadata.e2eEncrypted, favorite: metadata.favorite, ocId: metadata.ocId, fileId: metadata.fileId, etag: metadata.etag, permissions: metadata.permissions, serverUrl: serverUrl, account: metadata.account)
-                        } else if selector == NCGlobal.shared.selectorSynchronizationOffline, NCManageDatabase.shared.isDownloadMetadata(metadata, download: true) {
-                            NCOperationQueue.shared.download(metadata: metadata, selector: selector)
+                        } else if selector == NCGlobal.shared.selectorSynchronizationOffline,
+                                  NCManageDatabase.shared.isDownloadMetadata(metadata, download: true),
+                                  let appDelegate = (UIApplication.shared.delegate as? AppDelegate),
+                                  appDelegate.downloadQueue.operations.filter({ ($0 as? NCOperationDownload)?.metadata.ocId == metadata.ocId }).isEmpty {
+                            appDelegate.downloadQueue.addOperation(NCOperationDownload(metadata: metadata, selector: selector))
                         }
                     }
                 }
@@ -1646,3 +1670,81 @@ extension Array where Element == URLQueryItem {
         first(where: { $0.name == name })
     }
 }
+
+// MARK: -
+
+class NCOperationDownload: ConcurrentOperation {
+
+    var metadata: tableMetadata
+    var selector: String
+
+    init(metadata: tableMetadata, selector: String) {
+        self.metadata = tableMetadata.init(value: metadata)
+        self.selector = selector
+    }
+
+    override func start() {
+
+        guard !isCancelled else { return self.finish() }
+
+        NCNetworking.shared.download(metadata: metadata, selector: self.selector) { _, _ in
+            self.finish()
+        }
+    }
+}
+
+class NCOperationDownloadAvatar: ConcurrentOperation {
+
+    var user: String
+    var fileName: String
+    var etag: String?
+    var fileNameLocalPath: String
+    var cell: NCCellProtocol!
+    var view: UIView?
+    var cellImageView: UIImageView?
+
+    init(user: String, fileName: String, fileNameLocalPath: String, cell: NCCellProtocol, view: UIView?, cellImageView: UIImageView?) {
+        self.user = user
+        self.fileName = fileName
+        self.fileNameLocalPath = fileNameLocalPath
+        self.cell = cell
+        self.view = view
+        self.etag = NCManageDatabase.shared.getTableAvatar(fileName: fileName)?.etag
+        self.cellImageView = cellImageView
+    }
+
+    override func start() {
+
+        guard !isCancelled else { return self.finish() }
+
+        NextcloudKit.shared.downloadAvatar(user: user,
+                                           fileNameLocalPath: fileNameLocalPath,
+                                           sizeImage: NCGlobal.shared.avatarSize,
+                                           avatarSizeRounded: NCGlobal.shared.avatarSizeRounded,
+                                           etag: self.etag,
+                                           options: NKRequestOptions(queue: NextcloudKit.shared.nkCommonInstance.backgroundQueue)) { _, imageAvatar, _, etag, error in
+
+            if error == .success, let imageAvatar = imageAvatar, let etag = etag {
+                NCManageDatabase.shared.addAvatar(fileName: self.fileName, etag: etag)
+                DispatchQueue.main.async {
+                    if self.user == self.cell.fileUser, let avatarImageView = self.cellImageView {
+                        UIView.transition(with: avatarImageView,
+                                          duration: 0.75,
+                                          options: .transitionCrossDissolve,
+                                          animations: { avatarImageView.image = imageAvatar },
+                                          completion: nil)
+                    } else {
+                        if self.view is UICollectionView {
+                            (self.view as? UICollectionView)?.reloadData()
+                        } else if self.view is UITableView {
+                            (self.view as? UITableView)?.reloadData()
+                        }
+                    }
+                }
+            } else if error.errorCode == NCGlobal.shared.errorNotModified {
+                NCManageDatabase.shared.setAvatarLoaded(fileName: self.fileName)
+            }
+            self.finish()
+        }
+    }
+}

+ 0 - 358
iOSClient/Networking/NCOperationQueue.swift

@@ -1,358 +0,0 @@
-//
-//  NCOperationQueue.swift
-//  Nextcloud
-//
-//  Created by Marino Faggiana on 03/06/2020.
-//  Copyright © 2020 Marino Faggiana. All rights reserved.
-//
-//  Author Marino Faggiana <marino.faggiana@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 Queuer
-import NextcloudKit
-import JGProgressHUD
-
-@objc class NCOperationQueue: NSObject {
-    @objc public static let shared: NCOperationQueue = {
-        let instance = NCOperationQueue()
-        return instance
-    }()
-
-    let appDelegate = (UIApplication.shared.delegate as? AppDelegate)!
-
-    // MARK: - Download file
-
-    func download(metadata: tableMetadata, selector: String) {
-
-        for case let operation as NCOperationDownload in appDelegate.downloadQueue.operations where operation.metadata.ocId == metadata.ocId { return }
-        appDelegate.downloadQueue.addOperation(NCOperationDownload(metadata: metadata, selector: selector))
-    }
-
-    // MARK: - Download Thumbnail Activity
-
-    func downloadThumbnailActivity(fileNamePathOrFileId: String, fileNamePreviewLocalPath: String, fileId: String, cell: NCActivityCollectionViewCell, collectionView: UICollectionView?) {
-
-        cell.imageView?.image = UIImage(named: "file_photo")
-        cell.fileId = fileId
-
-        if !FileManager.default.fileExists(atPath: fileNamePreviewLocalPath) {
-            for case let operation as NCOperationDownloadThumbnailActivity in appDelegate.downloadThumbnailActivityQueue.operations where operation.fileId == fileId {
-                return
-            }
-            appDelegate.downloadThumbnailActivityQueue.addOperation(NCOperationDownloadThumbnailActivity(fileNamePathOrFileId: fileNamePathOrFileId, fileNamePreviewLocalPath: fileNamePreviewLocalPath, fileId: fileId, cell: cell, collectionView: collectionView))
-        }
-    }
-
-    // MARK: - Download Avatar
-
-    func downloadAvatar(user: String, dispalyName: String?, fileName: String, cell: NCCellProtocol, view: UIView?, cellImageView: UIImageView?) {
-
-        let fileNameLocalPath = String(CCUtility.getDirectoryUserData()) + "/" + fileName
-
-        if let image = NCManageDatabase.shared.getImageAvatarLoaded(fileName: fileName) {
-            cellImageView?.image = image
-            cell.fileAvatarImageView?.image = image
-            return
-        }
-
-        if let account = NCManageDatabase.shared.getActiveAccount() {
-            cellImageView?.image = NCUtility.shared.loadUserImage(
-                for: user,
-                   displayName: dispalyName,
-                   userBaseUrl: account)
-        }
-
-        for case let operation as NCOperationDownloadAvatar in appDelegate.downloadAvatarQueue.operations where operation.fileName == fileName { return }
-        appDelegate.downloadAvatarQueue.addOperation(NCOperationDownloadAvatar(user: user, fileName: fileName, fileNameLocalPath: fileNameLocalPath, cell: cell, view: view, cellImageView: cellImageView))
-    }
-
-    // MARK: - Unified Search
-
-    func unifiedSearchAddSection(collectionViewCommon: NCCollectionViewCommon, metadatas: [tableMetadata], searchResult: NKSearchResult) {
-        appDelegate.unifiedSearchQueue.addOperation(NCOperationUnifiedSearch(collectionViewCommon: collectionViewCommon, metadatas: metadatas, searchResult: searchResult))
-    }
-
-    // MARK: - Save Live Photo
-
-    func saveLivePhoto(metadata: tableMetadata, metadataMOV: tableMetadata) {
-
-        for case let operation as NCOperationSaveLivePhoto in appDelegate.saveLivePhotoQueue.operations where operation.metadata.fileName == metadata.fileName { return }
-        appDelegate.saveLivePhotoQueue.addOperation(NCOperationSaveLivePhoto(metadata: metadata, metadataMOV: metadataMOV))
-    }
-}
-
-// MARK: -
-
-class NCOperationDownload: ConcurrentOperation {
-
-    var metadata: tableMetadata
-    var selector: String
-
-    init(metadata: tableMetadata, selector: String) {
-        self.metadata = tableMetadata.init(value: metadata)
-        self.selector = selector
-    }
-
-    override func start() {
-
-        guard !isCancelled else { return self.finish() }
-
-        NCNetworking.shared.download(metadata: metadata, selector: self.selector) { _, _ in
-            self.finish()
-        }
-    }
-}
-
-// MARK: -
-
-class NCOperationDownloadThumbnailActivity: ConcurrentOperation {
-
-    var cell: NCActivityCollectionViewCell?
-    var collectionView: UICollectionView?
-    var fileNamePathOrFileId: String
-    var fileNamePreviewLocalPath: String
-    var fileId: String
-
-    init(fileNamePathOrFileId: String, fileNamePreviewLocalPath: String, fileId: String, cell: NCActivityCollectionViewCell?, collectionView: UICollectionView?) {
-        self.fileNamePathOrFileId = fileNamePathOrFileId
-        self.fileNamePreviewLocalPath = fileNamePreviewLocalPath
-        self.fileId = fileId
-        self.cell = cell
-        self.collectionView = collectionView
-    }
-
-    override func start() {
-
-        guard !isCancelled else { return self.finish() }
-
-        NextcloudKit.shared.downloadPreview(fileNamePathOrFileId: fileNamePathOrFileId,
-                                            fileNamePreviewLocalPath: fileNamePreviewLocalPath,
-                                            widthPreview: 0,
-                                            heightPreview: 0,
-                                            etag: nil,
-                                            useInternalEndpoint: false,
-                                            options: NKRequestOptions(queue: NextcloudKit.shared.nkCommonInstance.backgroundQueue)) { _, imagePreview, _, _, _, error in
-
-            if error == .success, let imagePreview = imagePreview {
-                DispatchQueue.main.async {
-                    if self.fileId == self.cell?.fileId, let imageView = self.cell?.imageView {
-                        UIView.transition(with: imageView,
-                                          duration: 0.75,
-                                          options: .transitionCrossDissolve,
-                                          animations: { imageView.image = imagePreview },
-                                          completion: nil)
-                    } else {
-                        self.collectionView?.reloadData()
-                    }
-                }
-            }
-            self.finish()
-        }
-    }
-}
-
-// MARK: -
-
-class NCOperationDownloadAvatar: ConcurrentOperation {
-
-    var user: String
-    var fileName: String
-    var etag: String?
-    var fileNameLocalPath: String
-    var cell: NCCellProtocol!
-    var view: UIView?
-    var cellImageView: UIImageView?
-
-    init(user: String, fileName: String, fileNameLocalPath: String, cell: NCCellProtocol, view: UIView?, cellImageView: UIImageView?) {
-        self.user = user
-        self.fileName = fileName
-        self.fileNameLocalPath = fileNameLocalPath
-        self.cell = cell
-        self.view = view
-        self.etag = NCManageDatabase.shared.getTableAvatar(fileName: fileName)?.etag
-        self.cellImageView = cellImageView
-    }
-
-    override func start() {
-
-        guard !isCancelled else { return self.finish() }
-
-        NextcloudKit.shared.downloadAvatar(user: user,
-                                           fileNameLocalPath: fileNameLocalPath,
-                                           sizeImage: NCGlobal.shared.avatarSize,
-                                           avatarSizeRounded: NCGlobal.shared.avatarSizeRounded,
-                                           etag: self.etag,
-                                           options: NKRequestOptions(queue: NextcloudKit.shared.nkCommonInstance.backgroundQueue)) { _, imageAvatar, _, etag, error in
-
-            if error == .success, let imageAvatar = imageAvatar, let etag = etag {
-                NCManageDatabase.shared.addAvatar(fileName: self.fileName, etag: etag)
-                DispatchQueue.main.async {
-                    if self.user == self.cell.fileUser, let avatarImageView = self.cellImageView {
-                        UIView.transition(with: avatarImageView,
-                                          duration: 0.75,
-                                          options: .transitionCrossDissolve,
-                                          animations: { avatarImageView.image = imageAvatar },
-                                          completion: nil)
-                    } else {
-                        if self.view is UICollectionView {
-                            (self.view as? UICollectionView)?.reloadData()
-                        } else if self.view is UITableView {
-                            (self.view as? UITableView)?.reloadData()
-                        }
-                    }
-                }
-            } else if error.errorCode == NCGlobal.shared.errorNotModified {
-                NCManageDatabase.shared.setAvatarLoaded(fileName: self.fileName)
-            }
-            self.finish()
-        }
-    }
-}
-
-// MARK: -
-
-class NCOperationUnifiedSearch: ConcurrentOperation {
-
-    var collectionViewCommon: NCCollectionViewCommon
-    var metadatas: [tableMetadata]
-    var searchResult: NKSearchResult
-
-    init(collectionViewCommon: NCCollectionViewCommon, metadatas: [tableMetadata], searchResult: NKSearchResult) {
-        self.collectionViewCommon = collectionViewCommon
-        self.metadatas = metadatas
-        self.searchResult = searchResult
-    }
-
-    func reloadDataThenPerform(_ closure: @escaping (() -> Void)) {
-        DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
-            CATransaction.begin()
-            CATransaction.setCompletionBlock(closure)
-            self.collectionViewCommon.collectionView.reloadData()
-            CATransaction.commit()
-        }
-    }
-
-    override func start() {
-
-        guard !isCancelled else { return self.finish() }
-
-        self.collectionViewCommon.dataSource.addSection(metadatas: metadatas, searchResult: searchResult)
-        self.collectionViewCommon.searchResults?.append(self.searchResult)
-        reloadDataThenPerform {
-            self.finish()
-        }
-    }
-}
-
-// MARK: -
-
-class NCOperationSaveLivePhoto: ConcurrentOperation {
-
-    var metadata: tableMetadata
-    var metadataMOV: tableMetadata
-    let hud = JGProgressHUD()
-    let appDelegate = UIApplication.shared.delegate as? AppDelegate
-
-    init(metadata: tableMetadata, metadataMOV: tableMetadata) {
-        self.metadata = tableMetadata.init(value: metadata)
-        self.metadataMOV = tableMetadata.init(value: metadataMOV)
-    }
-
-    override func start() {
-        guard !isCancelled else { return self.finish() }
-
-        DispatchQueue.main.async {
-            self.hud.indicatorView = JGProgressHUDRingIndicatorView()
-            if let indicatorView = self.hud.indicatorView as? JGProgressHUDRingIndicatorView {
-                indicatorView.ringWidth = 1.5
-            }
-            self.hud.textLabel.text = NSLocalizedString("_download_image_", comment: "")
-            self.hud.detailTextLabel.text = self.metadata.fileName
-            self.hud.show(in: (self.appDelegate?.window?.rootViewController?.view)!)
-        }
-
-        NCNetworking.shared.download(metadata: metadata, selector: "", notificationCenterProgressTask: false, checkfileProviderStorageExists: true) { _ in
-        } progressHandler: { progress in
-            self.hud.progress = Float(progress.fractionCompleted)
-        } completion: { _, error in
-            guard error == .success else {
-                DispatchQueue.main.async {
-                    self.hud.indicatorView = JGProgressHUDErrorIndicatorView()
-                    self.hud.textLabel.text = NSLocalizedString("_livephoto_save_error_", comment: "")
-                    self.hud.dismiss()
-                }
-                return self.finish()
-            }
-            NCNetworking.shared.download(metadata: self.metadataMOV, selector: "", notificationCenterProgressTask: false, checkfileProviderStorageExists: true) { _ in
-                DispatchQueue.main.async {
-                    self.hud.textLabel.text = NSLocalizedString("_download_video_", comment: "")
-                    self.hud.detailTextLabel.text = self.metadataMOV.fileName
-                }
-            } progressHandler: { progress in
-                self.hud.progress = Float(progress.fractionCompleted)
-            } completion: { _, error in
-                guard error == .success else {
-                    DispatchQueue.main.async {
-                        self.hud.indicatorView = JGProgressHUDErrorIndicatorView()
-                        self.hud.textLabel.text = NSLocalizedString("_livephoto_save_error_", comment: "")
-                        self.hud.dismiss()
-                    }
-                    return self.finish()
-                }
-                self.saveLivePhotoToDisk(metadata: self.metadata, metadataMov: self.metadataMOV)
-            }
-        }
-    }
-
-    func saveLivePhotoToDisk(metadata: tableMetadata, metadataMov: tableMetadata) {
-
-        let fileNameImage = URL(fileURLWithPath: CCUtility.getDirectoryProviderStorageOcId(metadata.ocId, fileNameView: metadata.fileNameView)!)
-        let fileNameMov = URL(fileURLWithPath: CCUtility.getDirectoryProviderStorageOcId(metadataMov.ocId, fileNameView: metadataMov.fileNameView)!)
-
-        DispatchQueue.main.async {
-            self.hud.textLabel.text = NSLocalizedString("_livephoto_save_", comment: "")
-            self.hud.detailTextLabel.text = ""
-        }
-
-        NCLivePhoto.generate(from: fileNameImage, videoURL: fileNameMov, progress: { progress in
-            self.hud.progress = Float(progress)
-        }, completion: { _, resources in
-            if resources != nil {
-                NCLivePhoto.saveToLibrary(resources!) { result in
-                    DispatchQueue.main.async {
-                        if !result {
-                            self.hud.indicatorView = JGProgressHUDErrorIndicatorView()
-                            self.hud.textLabel.text = NSLocalizedString("_livephoto_save_error_", comment: "")
-                        } else {
-                            self.hud.indicatorView = JGProgressHUDSuccessIndicatorView()
-                            self.hud.textLabel.text = NSLocalizedString("_success_", comment: "")
-                        }
-                        self.hud.dismiss()
-                    }
-                    return self.finish()
-                }
-            } else {
-                DispatchQueue.main.async {
-                    self.hud.indicatorView = JGProgressHUDErrorIndicatorView()
-                    self.hud.textLabel.text = NSLocalizedString("_livephoto_save_error_", comment: "")
-                    self.hud.dismiss()
-                }
-                return self.finish()
-            }
-        })
-    }
-}

+ 3 - 2
iOSClient/Networking/NCService.swift

@@ -309,8 +309,9 @@ class NCService: NSObject {
         let files = NCManageDatabase.shared.getTableLocalFiles(predicate: NSPredicate(format: "account == %@ AND offline == true", account), sorted: "fileName", ascending: true)
         for file: tableLocalFile in files {
             guard let metadata = NCManageDatabase.shared.getMetadataFromOcId(file.ocId) else { continue }
-            if NCManageDatabase.shared.isDownloadMetadata(metadata, download: true) {
-                NCOperationQueue.shared.download(metadata: metadata, selector: NCGlobal.shared.selectorDownloadFile)
+            if NCManageDatabase.shared.isDownloadMetadata(metadata, download: true),
+               appDelegate.downloadQueue.operations.filter({ ($0 as? NCOperationDownload)?.metadata.ocId == metadata.ocId }).isEmpty {
+                appDelegate.downloadQueue.addOperation(NCOperationDownload(metadata: metadata, selector: NCGlobal.shared.selectorDownloadFile))
             }
         }
         NextcloudKit.shared.nkCommonInstance.writeLog("[INFO] end synchronize offline")

+ 1 - 1
iOSClient/Notification/NCNotification.swift

@@ -154,7 +154,7 @@ class NCNotification: UITableViewController, NCNotificationCellDelegate, NCEmpty
                 cell.avatar.image = image
             } else if !FileManager.default.fileExists(atPath: fileNameLocalPath) {
                 cell.fileUser = user
-                NCOperationQueue.shared.downloadAvatar(user: user, dispalyName: json["user"]?["name"].string, fileName: fileName, cell: cell, view: tableView, cellImageView: cell.fileAvatarImageView)
+                NCNetworking.shared.downloadAvatar(user: user, dispalyName: json["user"]?["name"].string, fileName: fileName, cell: cell, view: tableView, cellImageView: cell.fileAvatarImageView)
             }
         }
 

+ 1 - 1
iOSClient/Select/NCSelect.swift

@@ -319,7 +319,7 @@ extension NCSelect: UICollectionViewDataSource {
            activeAccount.account == metadata.account,
            let cell = cell as? NCCellProtocol {
             let fileName = metadata.userBaseUrl + "-" + metadata.ownerId + ".png"
-            NCOperationQueue.shared.downloadAvatar(user: metadata.ownerId, dispalyName: metadata.ownerDisplayName, fileName: fileName, cell: cell, view: collectionView, cellImageView: cell.fileAvatarImageView)
+            NCNetworking.shared.downloadAvatar(user: metadata.ownerId, dispalyName: metadata.ownerDisplayName, fileName: fileName, cell: cell, view: collectionView, cellImageView: cell.fileAvatarImageView)
         }
     }
 

+ 1 - 1
iOSClient/Share/NCShare.swift

@@ -371,7 +371,7 @@ extension NCShare: UITableViewDataSource {
                 cell.delegate = self
                 cell.setupCellUI(userId: appDelegate.userId)
                 let fileName = appDelegate.userBaseUrl + "-" + tableShare.shareWith + ".png"
-                NCOperationQueue.shared.downloadAvatar(user: tableShare.shareWith, dispalyName: tableShare.shareWithDisplayname, fileName: fileName, cell: cell, view: tableView, cellImageView: cell.fileAvatarImageView)
+                NCNetworking.shared.downloadAvatar(user: tableShare.shareWith, dispalyName: tableShare.shareWithDisplayname, fileName: fileName, cell: cell, view: tableView, cellImageView: cell.fileAvatarImageView)
                 return cell
             }
         }

+ 20 - 10
iOSClient/Viewer/NCViewerProviderContextMenu.swift

@@ -116,28 +116,38 @@ class NCViewerProviderContextMenu: UIViewController {
                         maxDownload = NCGlobal.shared.maxAutoDownload
                     }
 
-                    if metadata.size <= maxDownload {
-                        NCOperationQueue.shared.download(metadata: metadata, selector: "")
+                    if metadata.size <= maxDownload, 
+                       let appDelegate = (UIApplication.shared.delegate as? AppDelegate),
+                       appDelegate.downloadQueue.operations.filter({ ($0 as? NCOperationDownload)?.metadata.ocId == metadata.ocId }).isEmpty {
+                        appDelegate.downloadQueue.addOperation(NCOperationDownload(metadata: metadata, selector: ""))
                     }
                 }
             }
 
             // AUTO DOWNLOAD IMAGE GIF
-            if !CCUtility.fileProviderStorageExists(metadata) && metadata.contentType == "image/gif" {
-                NCOperationQueue.shared.download(metadata: metadata, selector: "")
+            if !CCUtility.fileProviderStorageExists(metadata),
+               metadata.contentType == "image/gif",
+               let appDelegate = (UIApplication.shared.delegate as? AppDelegate),
+               appDelegate.downloadQueue.operations.filter({ ($0 as? NCOperationDownload)?.metadata.ocId == metadata.ocId }).isEmpty {
+                appDelegate.downloadQueue.addOperation(NCOperationDownload(metadata: metadata, selector: ""))
             }
 
             // AUTO DOWNLOAD IMAGE SVG
-            if !CCUtility.fileProviderStorageExists(metadata) && metadata.contentType == "image/svg+xml" {
-                NCOperationQueue.shared.download(metadata: metadata, selector: "")
+            if !CCUtility.fileProviderStorageExists(metadata),
+               metadata.contentType == "image/svg+xml",
+               let appDelegate = (UIApplication.shared.delegate as? AppDelegate),
+               appDelegate.downloadQueue.operations.filter({ ($0 as? NCOperationDownload)?.metadata.ocId == metadata.ocId }).isEmpty {
+                appDelegate.downloadQueue.addOperation(NCOperationDownload(metadata: metadata, selector: ""))
             }
 
             // AUTO DOWNLOAD LIVE PHOTO
-            if let metadataLivePhoto = self.metadataLivePhoto {
-                if !CCUtility.fileProviderStorageExists(metadataLivePhoto) {
-                    NCOperationQueue.shared.download(metadata: metadataLivePhoto, selector: "")
-                }
+            if let metadataLivePhoto = self.metadataLivePhoto,
+               !CCUtility.fileProviderStorageExists(metadataLivePhoto),
+               let appDelegate = (UIApplication.shared.delegate as? AppDelegate),
+               appDelegate.downloadQueue.operations.filter({ ($0 as? NCOperationDownload)?.metadata.ocId == metadata.ocId }).isEmpty {
+                appDelegate.downloadQueue.addOperation(NCOperationDownload(metadata: metadataLivePhoto, selector: ""))
             }
+
         }
     }