Browse Source

V 4.9.4 (#2703)

* Improved the Media metadata, ImageCache, convert Live Photo (#2698)

* Update NCManageDatabase+Metadata.swift
* Update NCMedia.swift
* new db updateMetadas
* updateMetadas
* convertLivePhotoQueue
* Update NCManageDatabase+Metadata.swift
Marino Faggiana 1 year ago
parent
commit
b00af0e9e1
100 changed files with 346 additions and 405 deletions
  1. 1 1
      Brand/Database.swift
  2. 2 2
      File Provider Extension/FileProviderEnumerator.swift
  3. 5 5
      Nextcloud.xcodeproj/project.pbxproj
  4. 1 1
      Share/NCShareExtension+Files.swift
  5. 1 1
      Share/NCShareExtension.swift
  6. 2 2
      iOSClient/Activity/NCActivityTableViewCell.swift
  7. 7 14
      iOSClient/AppDelegate.swift
  8. 55 112
      iOSClient/Data/NCManageDatabase+Metadata.swift
  9. 1 1
      iOSClient/Data/NCManageDatabase.swift
  10. 4 4
      iOSClient/Files/NCFiles.swift
  11. 11 11
      iOSClient/Main/Collection Common/NCCollectionViewCommon.swift
  12. 89 120
      iOSClient/Media/NCMedia.swift
  13. 1 3
      iOSClient/Menu/NCContextMenu.swift
  14. 5 5
      iOSClient/Menu/NCMedia+Menu.swift
  15. 3 5
      iOSClient/Menu/NCMenuAction.swift
  16. 0 2
      iOSClient/NCGlobal.swift
  17. 20 31
      iOSClient/NCImageCache.swift
  18. 20 19
      iOSClient/Networking/E2EE/NCNetworkingE2EEUpload.swift
  19. 106 58
      iOSClient/Networking/NCNetworking.swift
  20. 7 3
      iOSClient/Networking/NCNetworkingProcessUpload.swift
  21. 2 2
      iOSClient/Networking/NCService.swift
  22. 3 3
      iOSClient/Select/NCSelect.swift
  23. BIN
      iOSClient/Supporting Files/af.lproj/Localizable.strings
  24. BIN
      iOSClient/Supporting Files/an.lproj/Localizable.strings
  25. BIN
      iOSClient/Supporting Files/ar.lproj/Localizable.strings
  26. BIN
      iOSClient/Supporting Files/ast.lproj/Localizable.strings
  27. BIN
      iOSClient/Supporting Files/az.lproj/Localizable.strings
  28. BIN
      iOSClient/Supporting Files/be.lproj/Localizable.strings
  29. BIN
      iOSClient/Supporting Files/bg_BG.lproj/Localizable.strings
  30. BIN
      iOSClient/Supporting Files/bn_BD.lproj/Localizable.strings
  31. BIN
      iOSClient/Supporting Files/br.lproj/Localizable.strings
  32. BIN
      iOSClient/Supporting Files/bs.lproj/Localizable.strings
  33. BIN
      iOSClient/Supporting Files/ca.lproj/Localizable.strings
  34. BIN
      iOSClient/Supporting Files/cs-CZ.lproj/Localizable.strings
  35. BIN
      iOSClient/Supporting Files/cy_GB.lproj/Localizable.strings
  36. BIN
      iOSClient/Supporting Files/da.lproj/Localizable.strings
  37. BIN
      iOSClient/Supporting Files/de.lproj/Localizable.strings
  38. BIN
      iOSClient/Supporting Files/el.lproj/Localizable.strings
  39. BIN
      iOSClient/Supporting Files/en-GB.lproj/Localizable.strings
  40. BIN
      iOSClient/Supporting Files/eo.lproj/Localizable.strings
  41. BIN
      iOSClient/Supporting Files/es-419.lproj/Localizable.strings
  42. BIN
      iOSClient/Supporting Files/es-AR.lproj/Localizable.strings
  43. BIN
      iOSClient/Supporting Files/es-CL.lproj/Localizable.strings
  44. BIN
      iOSClient/Supporting Files/es-CO.lproj/Localizable.strings
  45. BIN
      iOSClient/Supporting Files/es-CR.lproj/Localizable.strings
  46. BIN
      iOSClient/Supporting Files/es-DO.lproj/Localizable.strings
  47. BIN
      iOSClient/Supporting Files/es-EC.lproj/Localizable.strings
  48. BIN
      iOSClient/Supporting Files/es-GT.lproj/Localizable.strings
  49. BIN
      iOSClient/Supporting Files/es-HN.lproj/Localizable.strings
  50. BIN
      iOSClient/Supporting Files/es-MX.lproj/Localizable.strings
  51. BIN
      iOSClient/Supporting Files/es-NI.lproj/Localizable.strings
  52. BIN
      iOSClient/Supporting Files/es-PA.lproj/Localizable.strings
  53. BIN
      iOSClient/Supporting Files/es-PE.lproj/Localizable.strings
  54. BIN
      iOSClient/Supporting Files/es-PR.lproj/Localizable.strings
  55. BIN
      iOSClient/Supporting Files/es-PY.lproj/Localizable.strings
  56. BIN
      iOSClient/Supporting Files/es-SV.lproj/Localizable.strings
  57. BIN
      iOSClient/Supporting Files/es-UY.lproj/Localizable.strings
  58. BIN
      iOSClient/Supporting Files/es.lproj/Localizable.strings
  59. BIN
      iOSClient/Supporting Files/et_EE.lproj/Localizable.strings
  60. BIN
      iOSClient/Supporting Files/eu.lproj/Localizable.strings
  61. BIN
      iOSClient/Supporting Files/fa.lproj/Localizable.strings
  62. BIN
      iOSClient/Supporting Files/fi-FI.lproj/Localizable.strings
  63. BIN
      iOSClient/Supporting Files/fo.lproj/Localizable.strings
  64. BIN
      iOSClient/Supporting Files/fr.lproj/Localizable.strings
  65. BIN
      iOSClient/Supporting Files/gd.lproj/Localizable.strings
  66. BIN
      iOSClient/Supporting Files/gl.lproj/Localizable.strings
  67. BIN
      iOSClient/Supporting Files/he.lproj/Localizable.strings
  68. BIN
      iOSClient/Supporting Files/hi_IN.lproj/Localizable.strings
  69. BIN
      iOSClient/Supporting Files/hr.lproj/Localizable.strings
  70. BIN
      iOSClient/Supporting Files/hsb.lproj/Localizable.strings
  71. BIN
      iOSClient/Supporting Files/hu.lproj/Localizable.strings
  72. BIN
      iOSClient/Supporting Files/hy.lproj/Localizable.strings
  73. BIN
      iOSClient/Supporting Files/ia.lproj/Localizable.strings
  74. BIN
      iOSClient/Supporting Files/id.lproj/Localizable.strings
  75. BIN
      iOSClient/Supporting Files/ig.lproj/Localizable.strings
  76. BIN
      iOSClient/Supporting Files/is.lproj/Localizable.strings
  77. BIN
      iOSClient/Supporting Files/it.lproj/Localizable.strings
  78. BIN
      iOSClient/Supporting Files/ja-JP.lproj/Localizable.strings
  79. BIN
      iOSClient/Supporting Files/ka-GE.lproj/Localizable.strings
  80. BIN
      iOSClient/Supporting Files/ka.lproj/Localizable.strings
  81. BIN
      iOSClient/Supporting Files/kab.lproj/Localizable.strings
  82. BIN
      iOSClient/Supporting Files/km.lproj/Localizable.strings
  83. BIN
      iOSClient/Supporting Files/kn.lproj/Localizable.strings
  84. BIN
      iOSClient/Supporting Files/ko.lproj/Localizable.strings
  85. BIN
      iOSClient/Supporting Files/la.lproj/Localizable.strings
  86. BIN
      iOSClient/Supporting Files/lb.lproj/Localizable.strings
  87. BIN
      iOSClient/Supporting Files/lo.lproj/Localizable.strings
  88. BIN
      iOSClient/Supporting Files/lt_LT.lproj/Localizable.strings
  89. BIN
      iOSClient/Supporting Files/lv.lproj/Localizable.strings
  90. BIN
      iOSClient/Supporting Files/mk.lproj/Localizable.strings
  91. BIN
      iOSClient/Supporting Files/mn.lproj/Localizable.strings
  92. BIN
      iOSClient/Supporting Files/mr.lproj/Localizable.strings
  93. BIN
      iOSClient/Supporting Files/ms_MY.lproj/Localizable.strings
  94. BIN
      iOSClient/Supporting Files/my.lproj/Localizable.strings
  95. BIN
      iOSClient/Supporting Files/nb-NO.lproj/Localizable.strings
  96. BIN
      iOSClient/Supporting Files/ne.lproj/Localizable.strings
  97. BIN
      iOSClient/Supporting Files/nl.lproj/Localizable.strings
  98. BIN
      iOSClient/Supporting Files/nn_NO.lproj/Localizable.strings
  99. BIN
      iOSClient/Supporting Files/oc.lproj/Localizable.strings
  100. BIN
      iOSClient/Supporting Files/pl.lproj/Localizable.strings

+ 1 - 1
Brand/Database.swift

@@ -26,4 +26,4 @@ import Foundation
 // Database Realm
 //
 let databaseName                    = "nextcloud.realm"
-let databaseSchemaVersion: UInt64   = 331
+let databaseSchemaVersion: UInt64   = 333

+ 2 - 2
File Provider Extension/FileProviderEnumerator.swift

@@ -213,8 +213,8 @@ class FileProviderEnumerator: NSObject, NSFileProviderEnumerator {
                     if error == .success {
                         DispatchQueue.global().async {
                             NCManageDatabase.shared.convertFilesToMetadatas(files, useMetadataFolder: true) { _, metadatasFolder, metadatas in
-                                let metadatasResult = NCManageDatabase.shared.getMetadatas(predicate: NSPredicate(format: "account == %@ AND serverUrl == %@ AND status == %d", account, serverUrl, NCGlobal.shared.metadataStatusNormal))
-                                NCManageDatabase.shared.updateMetadatas(metadatas, metadatasResult: metadatasResult)
+                                let predicate = NSPredicate(format: "account == %@ AND serverUrl == %@ AND status == %d", account, serverUrl, NCGlobal.shared.metadataStatusNormal)
+                                NCManageDatabase.shared.updateMetadatas(metadatas, predicate: predicate)
                                 for metadata in metadatasFolder {
                                     let serverUrl = metadata.serverUrl + "/" + metadata.fileNameView
                                     NCManageDatabase.shared.addDirectory(encrypted: metadata.e2eEncrypted, favorite: metadata.favorite, ocId: metadata.ocId, fileId: metadata.fileId, etag: nil, permissions: metadata.permissions, serverUrl: serverUrl, account: metadata.account)

+ 5 - 5
Nextcloud.xcodeproj/project.pbxproj

@@ -4873,7 +4873,7 @@
 				CLANG_WARN_UNREACHABLE_CODE = YES;
 				CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
 				COPY_PHASE_STRIP = NO;
-				CURRENT_PROJECT_VERSION = 3;
+				CURRENT_PROJECT_VERSION = 2;
 				DEBUG_INFORMATION_FORMAT = dwarf;
 				DEVELOPMENT_TEAM = NKUJUXUJ3B;
 				ENABLE_STRICT_OBJC_MSGSEND = YES;
@@ -4899,7 +4899,7 @@
 					"@executable_path/Frameworks",
 					"@executable_path/../../Frameworks",
 				);
-				MARKETING_VERSION = 4.9.3;
+				MARKETING_VERSION = 4.9.4;
 				ONLY_ACTIVE_ARCH = YES;
 				OTHER_LDFLAGS = "";
 				SDKROOT = iphoneos;
@@ -4938,7 +4938,7 @@
 				CLANG_WARN_UNREACHABLE_CODE = YES;
 				CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
 				COPY_PHASE_STRIP = NO;
-				CURRENT_PROJECT_VERSION = 3;
+				CURRENT_PROJECT_VERSION = 2;
 				DEVELOPMENT_TEAM = NKUJUXUJ3B;
 				ENABLE_STRICT_OBJC_MSGSEND = YES;
 				ENABLE_TESTABILITY = YES;
@@ -4961,7 +4961,7 @@
 					"@executable_path/Frameworks",
 					"@executable_path/../../Frameworks",
 				);
-				MARKETING_VERSION = 4.9.3;
+				MARKETING_VERSION = 4.9.4;
 				ONLY_ACTIVE_ARCH = YES;
 				OTHER_LDFLAGS = "";
 				SDKROOT = iphoneos;
@@ -5253,7 +5253,7 @@
 			repositoryURL = "https://github.com/nextcloud/NextcloudKit";
 			requirement = {
 				kind = exactVersion;
-				version = 2.9.2;
+				version = 2.9.3;
 			};
 		};
 		F788ECC5263AAAF900ADC67F /* XCRemoteSwiftPackageReference "MarkdownKit" */ = {

+ 1 - 1
Share/NCShareExtension+Files.swift

@@ -73,7 +73,7 @@ extension NCShareExtension {
         networkInProgress = true
         collectionView.reloadData()
 
-        NCNetworking.shared.readFolder(serverUrl: serverUrl, account: activeAccount.account) { _, metadataFolder, _, _, _, _, error in
+        NCNetworking.shared.readFolder(serverUrl: serverUrl, account: activeAccount.account) { _, metadataFolder, _, _, _, error in
 
             DispatchQueue.main.async {
                 if error != .success {

+ 1 - 1
Share/NCShareExtension.swift

@@ -352,7 +352,7 @@ extension NCShareExtension {
         hud.textLabel.text = NSLocalizedString("_upload_file_", comment: "") + " \(counterUploaded + 1) " + NSLocalizedString("_of_", comment: "") + " \(filesName.count)"
         hud.show(in: self.view)
 
-        NCNetworking.shared.upload(metadata: metadata, uploadE2EEDelegate: self, hudView: self.view) {
+        NCNetworking.shared.upload(metadata: metadata, uploadE2EEDelegate: self, hudView: self.view, hud: JGProgressHUD()) {
             self.hud.progress = 0
         } progressHandler: { _, _, fractionCompleted in
             self.hud.progress = Float(fractionCompleted)

+ 2 - 2
iOSClient/Activity/NCActivityTableViewCell.swift

@@ -196,8 +196,8 @@ extension NCActivityTableViewCell: UICollectionViewDataSource {
                         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))
+                            if NCNetworking.shared.downloadThumbnailActivityQueue.operations.filter({ ($0 as? NCOperationDownloadThumbnailActivity)?.fileId == fileId }).isEmpty {
+                                NCNetworking.shared.downloadThumbnailActivityQueue.addOperation(NCOperationDownloadThumbnailActivity(fileNamePathOrFileId: activityPreview.source, fileNamePreviewLocalPath: fileNamePath, fileId: fileId, cell: cell, collectionView: collectionView))
                             }
                         }
                     }

+ 7 - 14
iOSClient/AppDelegate.swift

@@ -54,14 +54,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
     var documentPickerViewController: NCDocumentPickerViewController?
     var timerErrorNetworking: Timer?
     private var privacyProtectionWindow: UIWindow?
-
-    let downloadQueue = Queuer(name: "downloadQueue", maxConcurrentOperationCount: NCGlobal.shared.maxConcurrentOperationCountDownload, qualityOfService: .default)
-    let downloadThumbnailQueue = Queuer(name: "downloadThumbnailQueue", maxConcurrentOperationCount: 10, qualityOfService: .default)
-    let downloadThumbnailActivityQueue = Queuer(name: "downloadThumbnailActivityQueue", maxConcurrentOperationCount: 10, qualityOfService: .default)
-    let downloadAvatarQueue = Queuer(name: "downloadAvatarQueue", maxConcurrentOperationCount: 10, qualityOfService: .default)
-    let unifiedSearchQueue = Queuer(name: "unifiedSearchQueue", maxConcurrentOperationCount: 1, qualityOfService: .default)
-    let saveLivePhotoQueue = Queuer(name: "saveLivePhotoQueue", maxConcurrentOperationCount: 1, qualityOfService: .default)
-
     var isAppRefresh: Bool = false
     var isAppProcessing: Bool = false
 
@@ -940,12 +932,13 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
     // MARK: - Queue
 
     @objc func cancelAllQueue() {
-        downloadQueue.cancelAll()
-        downloadThumbnailQueue.cancelAll()
-        downloadThumbnailActivityQueue.cancelAll()
-        downloadAvatarQueue.cancelAll()
-        unifiedSearchQueue.cancelAll()
-        saveLivePhotoQueue.cancelAll()
+        NCNetworking.shared.downloadQueue.cancelAll()
+        NCNetworking.shared.downloadThumbnailQueue.cancelAll()
+        NCNetworking.shared.downloadThumbnailActivityQueue.cancelAll()
+        NCNetworking.shared.downloadAvatarQueue.cancelAll()
+        NCNetworking.shared.unifiedSearchQueue.cancelAll()
+        NCNetworking.shared.saveLivePhotoQueue.cancelAll()
+        NCNetworking.shared.convertLivePhotoQueue.cancelAll()
     }
 
     // MARK: - Universal Links

+ 55 - 112
iOSClient/Data/NCManageDatabase+Metadata.swift

@@ -253,6 +253,10 @@ extension tableMetadata {
         !livePhotoFile.isEmpty
     }
 
+    var isNotFlaggedAsLivePhotoByServer: Bool {
+        !isFlaggedAsLivePhotoByServer
+    }
+
     /// Returns false if the user is lokced out of the file. I.e. The file is locked but by somone else
     func canUnlock(as user: String) -> Bool {
         return !lock || (lockOwner == user && lockOwnerType == 0)
@@ -526,101 +530,6 @@ extension NCManageDatabase {
         }
     }
 
-    @discardableResult
-    func updateMetadatas(_ metadatas: [tableMetadata], metadatasResult: [tableMetadata], addExistsInLocal: Bool = false, addCompareEtagLocal: Bool = false) -> (metadatasUpdate: [tableMetadata], metadatasLocalUpdate: [tableMetadata], metadatasDelete: [tableMetadata]) {
-
-        var ocIdsUdate: [String] = []
-        var ocIdsLocalUdate: [String] = []
-        var metadatasDelete: [tableMetadata] = []
-        var metadatasUpdate: [tableMetadata] = []
-        var metadatasLocalUpdate: [tableMetadata] = []
-
-        do {
-            let realm = try Realm()
-            try realm.write {
-
-                // DELETE
-                for metadataResult in metadatasResult {
-                    if metadatas.firstIndex(where: { $0.ocId == metadataResult.ocId }) == nil {
-                        if let result = realm.objects(tableMetadata.self).filter(NSPredicate(format: "ocId == %@", metadataResult.ocId)).first {
-                            metadatasDelete.append(tableMetadata.init(value: result))
-                            realm.delete(result)
-                        }
-                    }
-                }
-
-                // UPDATE/NEW
-                for metadata in metadatas {
-
-                    if let result = metadatasResult.first(where: { $0.ocId == metadata.ocId }) {
-                        // update
-                        // Workaround: check lock bc no etag changes if lock runs out in directory
-                        // https://github.com/nextcloud/server/issues/8477
-                        if result.status == NCGlobal.shared.metadataStatusNormal &&
-                            (result.etag != metadata.etag ||
-                             result.fileNameView != metadata.fileNameView ||
-                             result.date != metadata.date ||
-                             result.permissions != metadata.permissions ||
-                             result.hasPreview != metadata.hasPreview ||
-                             result.note != metadata.note ||
-                             result.lock != metadata.lock ||
-                             result.shareType != metadata.shareType ||
-                             result.sharePermissionsCloudMesh != metadata.sharePermissionsCloudMesh ||
-                             result.sharePermissionsCollaborationServices != metadata.sharePermissionsCollaborationServices ||
-                             result.favorite != metadata.favorite ||
-                             result.tags != metadata.tags) {
-                            ocIdsUdate.append(metadata.ocId)
-                            realm.add(tableMetadata.init(value: metadata), update: .all)
-                        } else if result.status == NCGlobal.shared.metadataStatusNormal {
-                            ocIdsUdate.append(metadata.ocId)
-                            realm.add(tableMetadata.init(value: metadata), update: .all)
-                        }
-                    } else {
-                        // new
-                        ocIdsUdate.append(metadata.ocId)
-                        realm.add(tableMetadata.init(value: metadata), update: .all)
-                    }
-
-                    if metadata.directory && !ocIdsUdate.contains(metadata.ocId) {
-                        let table = realm.objects(tableDirectory.self).filter(NSPredicate(format: "ocId == %@", metadata.ocId)).first
-                        if table?.etag != metadata.etag {
-                            ocIdsUdate.append(metadata.ocId)
-                        }
-                    }
-
-                    // Local
-                    if !metadata.directory && (addExistsInLocal || addCompareEtagLocal) {
-                        let localFile = realm.objects(tableLocalFile.self).filter(NSPredicate(format: "ocId == %@", metadata.ocId)).first
-                        if addCompareEtagLocal && localFile != nil && localFile?.etag != metadata.etag {
-                            ocIdsLocalUdate.append(metadata.ocId)
-                        }
-                        if addExistsInLocal && (localFile == nil || localFile?.etag != metadata.etag) && !ocIdsLocalUdate.contains(metadata.ocId) {
-                            ocIdsLocalUdate.append(metadata.ocId)
-                        }
-                    }
-                }
-            }
-
-            for ocId in ocIdsUdate {
-                if let result = realm.objects(tableMetadata.self).filter(NSPredicate(format: "ocId == %@", ocId)).first {
-                    metadatasUpdate.append(tableMetadata.init(value: result))
-                }
-            }
-
-            for ocId in ocIdsLocalUdate {
-                if let result = realm.objects(tableMetadata.self).filter(NSPredicate(format: "ocId == %@", ocId)).first {
-                    metadatasLocalUpdate.append(tableMetadata.init(value: result))
-                }
-            }
-
-            return (metadatasUpdate, metadatasLocalUpdate, metadatasDelete)
-        } catch let error {
-            NextcloudKit.shared.nkCommonInstance.writeLog("Could not write to database: \(error)")
-        }
-
-        return ([], [], [])
-    }
-
     func setMetadataSession(ocId: String, newFileName: String? = nil, session: String?, sessionError: String?, sessionSelector: String?, sessionTaskIdentifier: Int?, status: Int?, etag: String? = nil, errorCode: Int?) {
 
         do {
@@ -715,13 +624,14 @@ extension NCManageDatabase {
         }
     }
 
-    func setMetadataLivePhotoByServer(account: String, ocId: String) {
+    func setMetadataLivePhotoByServer(account: String, ocId: String, livePhotoFile: String) {
 
         do {
             let realm = try Realm()
             try realm.write {
                 let result = realm.objects(tableMetadata.self).filter("account == %@ AND ocId == %@", account, ocId).first
                 result?.isFlaggedAsLivePhotoByServer = true
+                result?.livePhotoFile = livePhotoFile
             }
         } catch let error {
             NextcloudKit.shared.nkCommonInstance.writeLog("Could not write to database: \(error)")
@@ -814,11 +724,27 @@ extension NCManageDatabase {
         return []
     }
 
-    func getResultsMetadatas(predicate: NSPredicate) -> Results<tableMetadata>? {
+    func getResultsMetadatas(predicate: NSPredicate, sorted: String? = nil, ascending: Bool = false) -> Results<tableMetadata>? {
+
+        do {
+            let realm = try Realm()
+            if let sorted {
+                return realm.objects(tableMetadata.self).filter(predicate).sorted(byKeyPath: sorted, ascending: ascending)
+            } else {
+                return realm.objects(tableMetadata.self).filter(predicate)
+            }
+        } catch let error as NSError {
+            NextcloudKit.shared.nkCommonInstance.writeLog("Could not access database: \(error)")
+        }
+
+        return nil
+    }
+
+    func getResultMetadata(predicate: NSPredicate) -> tableMetadata? {
 
         do {
             let realm = try Realm()
-            return realm.objects(tableMetadata.self).filter(predicate)
+            return realm.objects(tableMetadata.self).filter(predicate).first
         } catch let error as NSError {
             NextcloudKit.shared.nkCommonInstance.writeLog("Could not access database: \(error)")
         }
@@ -1031,7 +957,7 @@ extension NCManageDatabase {
 
         do {
             let realm = try Realm()
-            guard let result = realm.objects(tableMetadata.self).filter(NSPredicate(format: "account == %@ AND serverUrl == %@ AND fileName == %@", metadata.account, metadata.serverUrl, metadata.livePhotoFile)).first else { return nil }
+            guard let result = realm.objects(tableMetadata.self).filter(NSPredicate(format: "account == %@ AND serverUrl == %@ AND fileId == %@", metadata.account, metadata.serverUrl, metadata.livePhotoFile)).first else { return nil }
             return tableMetadata.init(value: result)
         } catch let error as NSError {
             NextcloudKit.shared.nkCommonInstance.writeLog("Could not access database: \(error)")
@@ -1040,19 +966,6 @@ extension NCManageDatabase {
         return nil
     }
 
-    func getMetadatasMedia(predicate: NSPredicate) -> [tableMetadata] {
-
-        do {
-            let realm = try Realm()
-            let results = realm.objects(tableMetadata.self).filter(predicate).sorted(byKeyPath: "date", ascending: false)
-            return Array(results.map { tableMetadata.init(value: $0) })
-        } catch let error {
-            NextcloudKit.shared.nkCommonInstance.writeLog("Could not write to database: \(error)")
-        }
-
-        return []
-    }
-
     func isMetadataShareOrMounted(metadata: tableMetadata, metadataFolder: tableMetadata?) -> Bool {
 
         var isShare = false
@@ -1171,4 +1084,34 @@ extension NCManageDatabase {
             NextcloudKit.shared.nkCommonInstance.writeLog("Could not access database: \(error)")
         }
     }
+
+    @discardableResult
+    func updateMetadatas(_ metadatas: [tableMetadata], predicate: NSPredicate) -> (metadatasChangedCount: Int, metadatasChanged: Bool) {
+
+        var metadatasChangedCount: Int = 0
+        var metadatasChanged: Bool = false
+
+        do {
+            let realm = try Realm()
+            try realm.write {
+                let results = realm.objects(tableMetadata.self).filter(predicate)
+                metadatasChangedCount = metadatas.count - results.count
+                for metadata in metadatas {
+                    if let result = results.filter({ $0.ocId == metadata.ocId }).first,
+                       metadata.isEqual(result) { } else {
+                        metadatasChanged = true
+                        break
+                    }
+                }
+                if metadatasChangedCount != 0 || metadatasChanged {
+                    realm.delete(results)
+                    realm.add(metadatas, update: .all)
+                }
+            }
+        } catch let error {
+            NextcloudKit.shared.nkCommonInstance.writeLog("Could not write to database: \(error)")
+        }
+
+        return (metadatasChangedCount, metadatasChanged)
+    }
 }

+ 1 - 1
iOSClient/Data/NCManageDatabase.swift

@@ -123,7 +123,7 @@ class NCManageDatabase: NSObject {
                             migration.deleteData(forType: tableGPS.className())
                         }
 
-                        if oldSchemaVersion < 331 {
+                        if oldSchemaVersion < 333 {
                             migration.deleteData(forType: tableMetadata.className())
                             migration.enumerateObjects(ofType: tableDirectory.className()) { _, newObject in
                                 newObject?["etag"] = ""

+ 4 - 4
iOSClient/Files/NCFiles.swift

@@ -175,11 +175,11 @@ class NCFiles: NCCollectionViewCommon {
         isReloadDataSourceNetworkInProgress = true
         collectionView?.reloadData()
 
-        networkReadFolder(isForced: isForced) { tableDirectory, metadatas, metadatasUpdate, metadatasDelete, error in
+        networkReadFolder(isForced: isForced) { tableDirectory, metadatas, metadatasChangedCount, metadatasChanged, error in
             if error == .success {
                 for metadata in metadatas ?? [] where !metadata.directory && downloadMetadata(metadata) {
-                    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))
+                    if NCNetworking.shared.downloadQueue.operations.filter({ ($0 as? NCOperationDownload)?.metadata.ocId == metadata.ocId }).isEmpty {
+                        NCNetworking.shared.downloadQueue.addOperation(NCOperationDownload(metadata: metadata, selector: NCGlobal.shared.selectorDownloadFile))
                     }
                 }
             }
@@ -187,7 +187,7 @@ class NCFiles: NCCollectionViewCommon {
             self.isReloadDataSourceNetworkInProgress = false
             self.richWorkspaceText = tableDirectory?.richWorkspace
 
-            if metadatasUpdate?.count ?? 0 > 0 || metadatasDelete?.count ?? 0 > 0 || isForced {
+            if metadatasChangedCount != 0 || metadatasChanged || isForced {
                 self.reloadDataSource()
             } else if self.dataSource.getMetadataSourceForAllSections().isEmpty {
                 DispatchQueue.main.async {

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

@@ -939,7 +939,7 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS
                     searchResults: self.searchResults)
             } update: { _, _, searchResult, metadatas in
                 guard let metadatas, !metadatas.isEmpty, self.isSearchingMode, let searchResult else { return }
-                self.appDelegate.unifiedSearchQueue.addOperation(NCOperationUnifiedSearch(collectionViewCommon: self, metadatas: metadatas, searchResult: searchResult))
+                NCNetworking.shared.unifiedSearchQueue.addOperation(NCOperationUnifiedSearch(collectionViewCommon: self, metadatas: metadatas, searchResult: searchResult))
             } completion: { _, _ in
                 self.refreshControl.endRefreshing()
                 self.isReloadDataSourceNetworkInProgress = false
@@ -989,13 +989,13 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS
         }
     }
 
-    @objc func networkReadFolder(isForced: Bool, completion: @escaping(_ tableDirectory: tableDirectory?, _ metadatas: [tableMetadata]?, _ metadatasUpdate: [tableMetadata]?, _ metadatasDelete: [tableMetadata]?, _ error: NKError) -> Void) {
+    @objc func networkReadFolder(isForced: Bool, completion: @escaping(_ tableDirectory: tableDirectory?, _ metadatas: [tableMetadata]?, _ metadatasChangedCount: Int, _ metadatasChanged: Bool, _ error: NKError) -> Void) {
 
         var tableDirectory: tableDirectory?
 
         NCNetworking.shared.readFile(serverUrlFileName: serverUrl) { account, metadataFolder, error in
             guard error == .success else {
-                completion(nil, nil, nil, nil, error)
+                completion(nil, nil, 0, false, error)
                 return
             }
 
@@ -1004,9 +1004,9 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS
             }
 
             if isForced || tableDirectory?.etag != metadataFolder?.etag || metadataFolder?.e2eEncrypted ?? true {
-                NCNetworking.shared.readFolder(serverUrl: self.serverUrl, account: self.appDelegate.account) { _, metadataFolder, metadatas, metadatasUpdate, _, metadatasDelete, error in
+                NCNetworking.shared.readFolder(serverUrl: self.serverUrl, account: self.appDelegate.account) { _, metadataFolder, metadatas, metadatasChangedCount, metadatasChanged, error in
                     guard error == .success else {
-                        completion(tableDirectory, nil, nil, nil, error)
+                        completion(tableDirectory, nil, 0, false, error)
                         return
                     }
                     self.metadataFolder = metadataFolder
@@ -1036,14 +1036,14 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS
                             } else {
                                 NCContentPresenter().showError(error: NKError(errorCode: NCGlobal.shared.errorE2EEKeyDecodeMetadata, errorDescription: "_e2e_error_"))
                             }
-                            completion(tableDirectory, metadatas, metadatasUpdate, metadatasDelete, error)
+                            completion(tableDirectory, metadatas, metadatasChangedCount, metadatasChanged, error)
                         }
                     } else {
-                        completion(tableDirectory, metadatas, metadatasUpdate, metadatasDelete, error)
+                        completion(tableDirectory, metadatas, metadatasChangedCount, metadatasChanged, error)
                     }
                 }
             } else {
-                completion(tableDirectory, nil, nil, nil, NKError())
+                completion(tableDirectory, nil, 0, false, NKError())
             }
         }
     }
@@ -1205,8 +1205,8 @@ extension NCCollectionViewCommon: UICollectionViewDataSource {
                         (cell as? NCCellProtocol)?.filePreviewImageView?.image = UIImage(named: metadata.iconName)
                     }
                     if metadata.hasPreview && metadata.status == NCGlobal.shared.metadataStatusNormal && (!utilityFileSystem.fileProviderStoragePreviewIconExists(metadata.ocId, etag: metadata.etag)) {
-                        for case let operation as NCCollectionViewDownloadThumbnail in appDelegate.downloadThumbnailQueue.operations where operation.metadata.ocId == metadata.ocId { return }
-                        appDelegate.downloadThumbnailQueue.addOperation(NCCollectionViewDownloadThumbnail(metadata: metadata, cell: (cell as? NCCellProtocol), collectionView: collectionView))
+                        for case let operation as NCCollectionViewDownloadThumbnail in NCNetworking.shared.downloadThumbnailQueue.operations where operation.metadata.ocId == metadata.ocId { return }
+                        NCNetworking.shared.downloadThumbnailQueue.addOperation(NCCollectionViewDownloadThumbnail(metadata: metadata, cell: (cell as? NCCellProtocol), collectionView: collectionView))
                     }
                 }
             } else {
@@ -1254,7 +1254,7 @@ extension NCCollectionViewCommon: UICollectionViewDataSource {
     func collectionView(_ collectionView: UICollectionView, didEndDisplaying cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
         if !collectionView.indexPathsForVisibleItems.contains(indexPath) {
             guard let metadata = dataSource.cellForItemAt(indexPath: indexPath) else { return }
-            for case let operation as NCCollectionViewDownloadThumbnail in appDelegate.downloadThumbnailQueue.operations where operation.metadata.ocId == metadata.ocId {
+            for case let operation as NCCollectionViewDownloadThumbnail in NCNetworking.shared.downloadThumbnailQueue.operations where operation.metadata.ocId == metadata.ocId {
                 operation.cancel()
             }
         }

+ 89 - 120
iOSClient/Media/NCMedia.swift

@@ -25,6 +25,7 @@ import UIKit
 import NextcloudKit
 import JGProgressHUD
 import Queuer
+import RealmSwift
 
 class NCMedia: UIViewController, NCEmptyDataSetDelegate, NCSelectDelegate {
 
@@ -39,7 +40,7 @@ class NCMedia: UIViewController, NCEmptyDataSetDelegate, NCSelectDelegate {
     internal let utilityFileSystem = NCUtilityFileSystem()
     internal let utility = NCUtility()
 
-    internal var metadatas: [tableMetadata] = []
+    @ThreadSafe internal var metadatas: Results<tableMetadata>?
     internal var isEditMode = false
     internal var selectOcId: [String] = []
     internal var selectIndexPath: [IndexPath] = []
@@ -118,9 +119,6 @@ class NCMedia: UIViewController, NCEmptyDataSetDelegate, NCSelectDelegate {
         navigationController?.setMediaAppreance()
 
         NotificationCenter.default.addObserver(self, selector: #selector(deleteFile(_:)), name: NSNotification.Name(rawValue: NCGlobal.shared.notificationCenterDeleteFile), object: nil)
-        NotificationCenter.default.addObserver(self, selector: #selector(moveFile(_:)), name: NSNotification.Name(rawValue: NCGlobal.shared.notificationCenterMoveFile), object: nil)
-        NotificationCenter.default.addObserver(self, selector: #selector(copyFile(_:)), name: NSNotification.Name(rawValue: NCGlobal.shared.notificationCenterCopyFile), object: nil)
-        NotificationCenter.default.addObserver(self, selector: #selector(renameFile(_:)), name: NSNotification.Name(rawValue: NCGlobal.shared.notificationCenterRenameFile), object: nil)
         NotificationCenter.default.addObserver(self, selector: #selector(uploadedFile(_:)), name: NSNotification.Name(rawValue: NCGlobal.shared.notificationCenterUploadedFile), object: nil)
 
         if let metadatas = NCImageCache.shared.initialMetadatas() {
@@ -142,9 +140,6 @@ class NCMedia: UIViewController, NCEmptyDataSetDelegate, NCSelectDelegate {
         super.viewWillDisappear(animated)
 
         NotificationCenter.default.removeObserver(self, name: NSNotification.Name(rawValue: NCGlobal.shared.notificationCenterDeleteFile), object: nil)
-        NotificationCenter.default.removeObserver(self, name: NSNotification.Name(rawValue: NCGlobal.shared.notificationCenterMoveFile), object: nil)
-        NotificationCenter.default.removeObserver(self, name: NSNotification.Name(rawValue: NCGlobal.shared.notificationCenterCopyFile), object: nil)
-        NotificationCenter.default.removeObserver(self, name: NSNotification.Name(rawValue: NCGlobal.shared.notificationCenterRenameFile), object: nil)
         NotificationCenter.default.removeObserver(self, name: NSNotification.Name(rawValue: NCGlobal.shared.notificationCenterUploadedFile), object: nil)
     }
 
@@ -165,42 +160,16 @@ class NCMedia: UIViewController, NCEmptyDataSetDelegate, NCSelectDelegate {
         guard let userInfo = notification.userInfo as NSDictionary?,
               let error = userInfo["error"] as? NKError else { return }
 
-        self.metadatas = NCImageCache.shared.getMediaMetadatas(account: appDelegate.account, predicate: getPredicate())
-
         if error != .success {
             NCContentPresenter().showError(error: error)
         }
-        self.collectionView?.reloadData()
-
-        if let hud = userInfo["hud"] as? JGProgressHUD {
-            hud.dismiss()
-        }
-    }
-
-    @objc func moveFile(_ notification: NSNotification) {
-
-        guard let userInfo = notification.userInfo as NSDictionary? else { return }
+        self.reloadDataSource()
 
         if let hud = userInfo["hud"] as? JGProgressHUD {
             hud.dismiss()
         }
     }
 
-    @objc func copyFile(_ notification: NSNotification) {
-
-        moveFile(notification)
-    }
-
-    @objc func renameFile(_ notification: NSNotification) {
-
-        guard let userInfo = notification.userInfo as NSDictionary?,
-              let account = userInfo["account"] as? String,
-              account == appDelegate.account
-        else { return }
-
-        self.reloadDataSource { }
-    }
-
     @objc func uploadedFile(_ notification: NSNotification) {
 
         guard let userInfo = notification.userInfo as NSDictionary?,
@@ -210,7 +179,7 @@ class NCMedia: UIViewController, NCEmptyDataSetDelegate, NCSelectDelegate {
               account == appDelegate.account
         else { return }
 
-        self.reloadDataSource { }
+        self.reloadDataSource()
     }
 
     // MARK: - Command
@@ -273,9 +242,8 @@ class NCMedia: UIViewController, NCEmptyDataSetDelegate, NCSelectDelegate {
         let home = utilityFileSystem.getHomeServer(urlBase: appDelegate.urlBase, userId: appDelegate.userId)
         mediaPath = serverUrl.replacingOccurrences(of: home, with: "")
         NCManageDatabase.shared.setAccountMediaPath(mediaPath, account: appDelegate.account)
-        reloadDataSource {
-            self.searchNewMedia()
-        }
+        reloadDataSource()
+        searchNewMedia()
     }
 
     // MARK: - Empty
@@ -298,30 +266,32 @@ extension NCMedia: UICollectionViewDelegate {
 
     func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
 
-        let metadata = self.metadatas[indexPath.row]
-        if isEditMode {
-            if let index = selectOcId.firstIndex(of: metadata.ocId) {
-                selectOcId.remove(at: index)
-                selectIndexPath.removeAll(where: { $0 == indexPath })
-            } else {
-                selectOcId.append(metadata.ocId)
-                selectIndexPath.append(indexPath)
-            }
-            if indexPath.section < collectionView.numberOfSections && indexPath.row < collectionView.numberOfItems(inSection: indexPath.section) {
-                collectionView.reloadItems(at: [indexPath])
+        if let metadata = self.metadatas?[indexPath.row] {
+            if isEditMode {
+                if let index = selectOcId.firstIndex(of: metadata.ocId) {
+                    selectOcId.remove(at: index)
+                    selectIndexPath.removeAll(where: { $0 == indexPath })
+                } else {
+                    selectOcId.append(metadata.ocId)
+                    selectIndexPath.append(indexPath)
+                }
+                if indexPath.section < collectionView.numberOfSections && indexPath.row < collectionView.numberOfItems(inSection: indexPath.section) {
+                    collectionView.reloadItems(at: [indexPath])
+                }
+            } else if let metadatas = self.metadatas {
+                // ACTIVE SERVERURL
+                appDelegate.activeServerUrl = metadata.serverUrl
+                let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "gridCell", for: indexPath) as? NCGridMediaCell
+                let arrayMetadatas = Array(metadatas.map { tableMetadata.init(value: $0) })
+                NCViewer().view(viewController: self, metadata: metadata, metadatas: arrayMetadatas, imageIcon: cell?.imageItem.image)
             }
-        } else {
-            // ACTIVE SERVERURL
-            appDelegate.activeServerUrl = metadata.serverUrl
-            let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "gridCell", for: indexPath) as? NCGridMediaCell
-            NCViewer().view(viewController: self, metadata: metadata, metadatas: self.metadatas, imageIcon: cell?.imageItem.image)
         }
     }
 
     func collectionView(_ collectionView: UICollectionView, contextMenuConfigurationForItemAt indexPath: IndexPath, point: CGPoint) -> UIContextMenuConfiguration? {
 
-        guard let cell = collectionView.cellForItem(at: indexPath) as? NCGridMediaCell else { return nil }
-        let metadata = self.metadatas[indexPath.row]
+        guard let cell = collectionView.cellForItem(at: indexPath) as? NCGridMediaCell,
+              let metadata = self.metadatas?[indexPath.row] else { return nil }
         let identifier = indexPath as NSCopying
         let image = cell.imageItem.image
 
@@ -349,22 +319,20 @@ extension NCMedia: UICollectionViewDataSourcePrefetching {
 
 extension NCMedia: UICollectionViewDataSource {
 
-    func reloadDataThenPerform(_ closure: @escaping (() -> Void)) {
-        CATransaction.begin()
-        CATransaction.setCompletionBlock(closure)
-        collectionView?.reloadData()
-        CATransaction.commit()
-    }
-
     func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
-        emptyDataSet?.numberOfItemsInSection(self.metadatas.count, section: section)
-        return self.metadatas.count
+        guard let metadatas = self.metadatas else { return 0 }
+        emptyDataSet?.numberOfItemsInSection(metadatas.count, section: section)
+        return metadatas.count
     }
 
     func collectionView(_ collectionView: UICollectionView, didEndDisplaying cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
-        if !collectionView.indexPathsForVisibleItems.contains(indexPath) && indexPath.row < self.metadatas.count {
-            let metadata = self.metadatas[indexPath.row]
-            for case let operation as NCMediaDownloadThumbnaill in appDelegate.downloadThumbnailQueue.operations where operation.metadata.ocId == metadata.ocId {
+        guard let metadatas = self.metadatas else { return }
+        if !collectionView.indexPathsForVisibleItems.contains(indexPath) && indexPath.row < metadatas.count {
+            let metadata = metadatas[indexPath.row]
+            for case let operation as NCMediaDownloadThumbnaill in NCNetworking.shared.downloadThumbnailQueue.operations where operation.metadata.ocId == metadata.ocId {
+                operation.cancel()
+            }
+            for case let operation as NCOperationConvertLivePhoto in NCNetworking.shared.convertLivePhotoQueue.operations where operation.ocId == metadata.ocId {
                 operation.cancel()
             }
         }
@@ -372,11 +340,12 @@ extension NCMedia: UICollectionViewDataSource {
 
     func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
 
-        guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "gridCell", for: indexPath) as? NCGridMediaCell else { return UICollectionViewCell() }
+        guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "gridCell", for: indexPath) as? NCGridMediaCell,
+              let metadatas = self.metadatas else { return UICollectionViewCell() }
 
-        if indexPath.section < collectionView.numberOfSections && indexPath.row < collectionView.numberOfItems(inSection: indexPath.section) && indexPath.row < self.metadatas.count {
+        if indexPath.section < collectionView.numberOfSections && indexPath.row < collectionView.numberOfItems(inSection: indexPath.section) && indexPath.row < metadatas.count {
 
-            let metadata = self.metadatas[indexPath.row]
+            let metadata = metadatas[indexPath.row]
 
             self.cellHeigth = cell.frame.size.height
 
@@ -385,24 +354,29 @@ extension NCMedia: UICollectionViewDataSource {
             cell.indexPath = indexPath
             cell.fileUser = metadata.ownerId
 
-            if let cachedImage = NCImageCache.shared.getMediaImage(ocId: metadata.ocId), case let .actual(image) = cachedImage {
+            if let cachedImage = NCImageCache.shared.getMediaImage(ocId: metadata.ocId, etag: metadata.etag), case let .actual(image) = cachedImage {
                 cell.imageItem.backgroundColor = nil
                 cell.imageItem.image = image
             } else if FileManager().fileExists(atPath: utilityFileSystem.getDirectoryProviderStorageIconOcId(metadata.ocId, etag: metadata.etag)) {
                 if let image = UIImage(contentsOfFile: utilityFileSystem.getDirectoryProviderStorageIconOcId(metadata.ocId, etag: metadata.etag)) {
                     cell.imageItem.backgroundColor = nil
                     cell.imageItem.image = image
-                    NCImageCache.shared.setMediaImage(ocId: metadata.ocId, image: .actual(image))
+                    NCImageCache.shared.setMediaImage(ocId: metadata.ocId, etag: metadata.etag, image: .actual(image))
                 }
             } else {
                 if metadata.hasPreview && metadata.status == NCGlobal.shared.metadataStatusNormal && (!utilityFileSystem.fileProviderStoragePreviewIconExists(metadata.ocId, etag: metadata.etag)) {
-                    if appDelegate.downloadThumbnailQueue.operations.filter({ ($0 as? NCMediaDownloadThumbnaill)?.metadata.ocId == metadata.ocId }).isEmpty {
-                        appDelegate.downloadThumbnailQueue.addOperation(NCMediaDownloadThumbnaill(metadata: metadata, cell: cell, collectionView: collectionView))
+                    if NCNetworking.shared.downloadThumbnailQueue.operations.filter({ ($0 as? NCMediaDownloadThumbnaill)?.metadata.ocId == metadata.ocId }).isEmpty {
+                        NCNetworking.shared.downloadThumbnailQueue.addOperation(NCMediaDownloadThumbnaill(metadata: metadata, cell: cell, collectionView: collectionView))
                     }
                 }
                 cell.imageStatus.image = nil
             }
 
+            // Convert OLD Live Photo
+            if NCGlobal.shared.isLivePhotoServerAvailable, metadata.isLivePhoto, metadata.isNotFlaggedAsLivePhotoByServer {
+                NCNetworking.shared.convertLivePhoto(metadata: metadata)
+            }
+
             if metadata.isAudioOrVideo {
                 cell.imageStatus.image = cacheImages.cellPlayImage
             } else if metadata.isLivePhoto {
@@ -444,39 +418,35 @@ extension NCMedia: UICollectionViewDelegateFlowLayout {
 
 extension NCMedia {
 
-    func getPredicate(_ predicateShowBoth: Bool = false) -> NSPredicate {
+    func getPredicate(showAll: Bool = false) -> NSPredicate {
 
         let startServerUrl = NCUtilityFileSystem().getHomeServer(urlBase: appDelegate.urlBase, userId: appDelegate.userId) + mediaPath
-        let showBoth = NSPredicate(format: NCImageCache.shared.showBothPredicateMediaString, appDelegate.account, startServerUrl, NKCommon.TypeClassFile.image.rawValue, NKCommon.TypeClassFile.video.rawValue, NKCommon.TypeClassFile.video.rawValue)
 
-        if predicateShowBoth { return showBoth }
-        if showOnlyImages {
-            return NSPredicate(format: NCImageCache.shared.showOnlyPredicateMediaString, appDelegate.account, startServerUrl, NKCommon.TypeClassFile.image.rawValue, NKCommon.TypeClassFile.video.rawValue)
+        if showAll {
+            return NSPredicate(format: NCImageCache.shared.showAllPredicateMediaString, appDelegate.account, startServerUrl)
+        } else if showOnlyImages {
+            return NSPredicate(format: NCImageCache.shared.showOnlyPredicateMediaString, appDelegate.account, startServerUrl, NKCommon.TypeClassFile.image.rawValue)
         } else if showOnlyVideos {
-            return NSPredicate(format: NCImageCache.shared.showOnlyPredicateMediaString, appDelegate.account, startServerUrl, NKCommon.TypeClassFile.video.rawValue, NKCommon.TypeClassFile.video.rawValue)
+            return NSPredicate(format: NCImageCache.shared.showOnlyPredicateMediaString, appDelegate.account, startServerUrl, NKCommon.TypeClassFile.video.rawValue)
         } else {
-           return showBoth
+           return NSPredicate(format: NCImageCache.shared.showBothPredicateMediaString, appDelegate.account, startServerUrl)
         }
     }
 
-    @objc func reloadDataSource(completion: @escaping () -> Void) {
+    @objc func reloadDataSource() {
         guard !appDelegate.account.isEmpty else { return }
 
-        DispatchQueue.global().async {
-            self.metadatas = NCImageCache.shared.getMediaMetadatas(account: self.appDelegate.account, predicate: self.getPredicate())
-            DispatchQueue.main.sync {
-                self.reloadDataThenPerform {
-                    self.updateMediaControlVisibility()
-                    self.mediaCommandTitle()
-                    completion()
-                }
-            }
+        metadatas = NCImageCache.shared.getMediaMetadatas(account: self.appDelegate.account, predicate: self.getPredicate())
+        DispatchQueue.main.async {
+            self.collectionView?.reloadData()
+            self.updateMediaControlVisibility()
+            self.mediaCommandTitle()
         }
     }
 
     func updateMediaControlVisibility() {
 
-        if self.metadatas.isEmpty {
+        if let metadatas = self.metadatas, metadatas.isEmpty {
             if !self.showOnlyImages && !self.showOnlyVideos {
                 self.mediaCommandView?.toggleEmptyView(isEmpty: true)
                 self.mediaCommandView?.isHidden = false
@@ -530,14 +500,13 @@ extension NCMedia {
             if error == .success && account == self.appDelegate.account {
                 if !files.isEmpty {
                     NCManageDatabase.shared.convertFilesToMetadatas(files, useMetadataFolder: false) { _, _, metadatas in
-                        let predicateDate = NSPredicate(format: "date > %@ AND date < %@", greaterDate as NSDate, lessDate as NSDate)
-                        let predicateResult = NSCompoundPredicate(andPredicateWithSubpredicates: [predicateDate, self.getPredicate(true)])
-                        let metadatasResult = NCManageDatabase.shared.getMetadatas(predicate: predicateResult)
-                        let metadatasChanged = NCManageDatabase.shared.updateMetadatas(metadatas, metadatasResult: metadatasResult)
-                        if metadatasChanged.metadatasUpdate.isEmpty {
+                        var predicate = NSPredicate(format: "date > %@ AND date < %@", greaterDate as NSDate, lessDate as NSDate)
+                        predicate = NSCompoundPredicate(andPredicateWithSubpredicates: [predicate, self.getPredicate(showAll: true)])
+                        let results = NCManageDatabase.shared.updateMetadatas(metadatas, predicate: predicate)
+                        if results.metadatasChangedCount == 0 {
                             self.researchOldMedia(value: value, limit: limit, withElseReloadDataSource: true)
-                        } else {
-                            self.reloadDataSource { }
+                        } else if results.metadatasChanged {
+                            self.reloadDataSource()
                         }
                     }
                 } else {
@@ -561,7 +530,7 @@ extension NCMedia {
             searchOldMedia(value: -999, limit: 0)
         } else {
             if withElseReloadDataSource {
-                self.reloadDataSource { }
+                self.reloadDataSource()
             }
         }
     }
@@ -585,7 +554,7 @@ extension NCMedia {
         if let visibleCells = self.collectionView?.indexPathsForVisibleItems.sorted(by: { $0.row < $1.row }).compactMap({ self.collectionView?.cellForItem(at: $0) }) {
             if let cell = visibleCells.first as? NCGridMediaCell {
                 if cell.date != nil {
-                    if cell.date != self.metadatas.first?.date as Date? {
+                    if cell.date != self.metadatas?.first?.date as Date? {
                         lessDate = Calendar.current.date(byAdding: .second, value: 1, to: cell.date!)!
                         limit = 0
                     }
@@ -598,32 +567,32 @@ extension NCMedia {
             }
         }
 
-        reloadDataThenPerform {
+        collectionView?.reloadData()
 
-            let options = NKRequestOptions(timeout: 300, queue: NextcloudKit.shared.nkCommonInstance.backgroundQueue)
+        let options = NKRequestOptions(timeout: 300, queue: NextcloudKit.shared.nkCommonInstance.backgroundQueue)
 
-            NextcloudKit.shared.searchMedia(path: self.mediaPath, lessDate: lessDate, greaterDate: greaterDate, elementDate: "d:getlastmodified/", limit: limit, showHiddenFiles: NCKeychain().showHiddenFiles, options: options) { account, files, _, error in
+        NextcloudKit.shared.searchMedia(path: self.mediaPath, lessDate: lessDate, greaterDate: greaterDate, elementDate: "d:getlastmodified/", limit: limit, showHiddenFiles: NCKeychain().showHiddenFiles, options: options) { account, files, _, error in
 
-                self.newInProgress = false
-                DispatchQueue.main.async {
-                    self.mediaCommandView?.activityIndicator.stopAnimating()
-                }
+            self.newInProgress = false
+            DispatchQueue.main.async {
+                self.mediaCommandView?.activityIndicator.stopAnimating()
+            }
 
-                if error == .success, account == self.appDelegate.account, !files.isEmpty {
+            if error == .success, account == self.appDelegate.account {
+                if !files.isEmpty {
                     NCManageDatabase.shared.convertFilesToMetadatas(files, useMetadataFolder: false) { _, _, metadatas in
-                        let predicate = NSPredicate(format: "date > %@ AND date < %@", greaterDate as NSDate, lessDate as NSDate)
-                        let predicateResult = NSCompoundPredicate(andPredicateWithSubpredicates: [predicate, self.getPredicate(true)])
-                        let metadatasResult = NCManageDatabase.shared.getMetadatas(predicate: predicateResult)
-                        let updateMetadatas = NCManageDatabase.shared.updateMetadatas(metadatas, metadatasResult: metadatasResult)
-                        if !updateMetadatas.metadatasUpdate.isEmpty || !updateMetadatas.metadatasDelete.isEmpty {
-                            self.reloadDataSource { }
+                        var predicate = NSPredicate(format: "date > %@ AND date < %@", greaterDate as NSDate, lessDate as NSDate)
+                        predicate = NSCompoundPredicate(andPredicateWithSubpredicates: [predicate, self.getPredicate(showAll: true)])
+                        let results = NCManageDatabase.shared.updateMetadatas(metadatas, predicate: predicate)
+                        if results.metadatasChangedCount != 0 || results.metadatasChanged {
+                            self.reloadDataSource()
                         }
                     }
-                } else if error == .success, files.isEmpty, self.metadatas.isEmpty {
+                } else {
                     self.searchOldMedia()
-                } else if error != .success {
-                    NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] Media search new media error code \(error.errorCode) " + error.errorDescription)
                 }
+            } else if error != .success {
+                NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] Media search new media error code \(error.errorCode) " + error.errorDescription)
             }
         }
     }
@@ -871,7 +840,7 @@ class NCMediaDownloadThumbnaill: ConcurrentOperation {
                         self.collectionView?.reloadData()
                     }
                 }
-                NCImageCache.shared.setMediaImage(ocId: self.metadata.ocId, image: .actual(image))
+                NCImageCache.shared.setMediaImage(ocId: self.metadata.ocId, etag: self.metadata.etag, image: .actual(image))
             }
             self.finish()
         }

+ 1 - 3
iOSClient/Menu/NCContextMenu.swift

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

+ 5 - 5
iOSClient/Menu/NCMedia+Menu.swift

@@ -30,7 +30,7 @@ extension NCMedia {
         self.isEditMode = false
         self.selectOcId.removeAll()
         self.selectIndexPath.removeAll()
-        self.reloadDataThenPerform { }
+        self.collectionView?.reloadData()
     }
 
     func toggleMenu() {
@@ -40,7 +40,7 @@ extension NCMedia {
         defer { presentMenu(with: actions) }
 
         if !isEditMode {
-            if !self.metadatas.isEmpty {
+            if let metadatas = self.metadatas, !metadatas.isEmpty {
                 actions.append(
                     NCMenuAction(
                         title: NSLocalizedString("_select_", comment: ""),
@@ -84,7 +84,7 @@ extension NCMedia {
                     action: { _ in
                         self.showOnlyImages = true
                         self.showOnlyVideos = false
-                        self.reloadDataSource { }
+                        self.reloadDataSource()
                     }
                 )
             )
@@ -98,7 +98,7 @@ extension NCMedia {
                     action: { _ in
                         self.showOnlyImages = false
                         self.showOnlyVideos = true
-                        self.reloadDataSource { }
+                        self.reloadDataSource()
                     }
                 )
             )
@@ -112,7 +112,7 @@ extension NCMedia {
                     action: { _ in
                         self.showOnlyImages = false
                         self.showOnlyVideos = false
-                        self.reloadDataSource { }
+                        self.reloadDataSource()
                     }
                 )
             )

+ 3 - 5
iOSClient/Menu/NCMenuAction.swift

@@ -222,15 +222,13 @@ extension NCMenuAction {
             action: { _ in
                 for metadata in selectedMediaMetadatas {
                     if let metadataMOV = NCManageDatabase.shared.getMetadataLivePhoto(metadata: metadata) {
-                        if let appDelegate = (UIApplication.shared.delegate as? AppDelegate) {
-                            appDelegate.saveLivePhotoQueue.addOperation(NCOperationSaveLivePhoto(metadata: metadata, metadataMOV: metadataMOV))
-                        }
+                        NCNetworking.shared.saveLivePhotoQueue.addOperation(NCOperationSaveLivePhoto(metadata: metadata, metadataMOV: metadataMOV))
                     } else {
                         if NCUtilityFileSystem().fileProviderStorageExists(metadata) {
                             NCActionCenter.shared.saveAlbum(metadata: metadata)
                         } else {
-                            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))
+                            if NCNetworking.shared.downloadQueue.operations.filter({ ($0 as? NCOperationDownload)?.metadata.ocId == metadata.ocId }).isEmpty {
+                                NCNetworking.shared.downloadQueue.addOperation(NCOperationDownload(metadata: metadata, selector: NCGlobal.shared.selectorSaveAlbum))
                             }
                         }
                     }

+ 0 - 2
iOSClient/NCGlobal.swift

@@ -395,8 +395,6 @@ class NCGlobal: NSObject {
     let notificationCenterEnableSwipeGesture                    = "enableSwipeGesture"
     let notificationCenterDisableSwipeGesture                   = "disableSwipeGesture"
 
-    let notificationCenterFinishedMediaInProcess                = "finishedMediaInProcess"
-
     // TIP
     //
     let tipNCViewerPDFThumbnail                                 = "tipncviewerpdfthumbnail"

+ 20 - 31
iOSClient/NCImageCache.swift

@@ -24,6 +24,7 @@
 import UIKit
 import LRUCache
 import NextcloudKit
+import RealmSwift
 
 @objc class NCImageCache: NSObject {
     @objc public static let shared: NCImageCache = {
@@ -47,11 +48,11 @@ import NextcloudKit
         return ThumbnailLRUCache(countLimit: limit)
     }()
     private var ocIdEtag: [String: String] = [:]
-    private var metadatas: [tableMetadata]?
+    @ThreadSafe private var metadatas: Results<tableMetadata>?
 
-    let showBothPredicateMediaString = "account == %@ AND serverUrl BEGINSWITH %@ AND (classFile == %@ OR classFile == %@) AND NOT (session CONTAINS[c] 'upload') AND NOT (livePhotoFile != '' AND classFile == %@)"
-    let showOnlyPredicateMediaString = "account == %@ AND serverUrl BEGINSWITH %@ AND classFile == %@ AND NOT (session CONTAINS[c] 'upload') AND NOT (livePhotoFile != '' AND classFile == %@)"
-    var isMediaMetadatasInProcess: Bool = false
+    let showAllPredicateMediaString = "account == %@ AND serverUrl BEGINSWITH %@ AND (classFile == '\(NKCommon.TypeClassFile.image.rawValue)' OR classFile == '\(NKCommon.TypeClassFile.video.rawValue)') AND NOT (session CONTAINS[c] 'upload')"
+    let showBothPredicateMediaString = "account == %@ AND serverUrl BEGINSWITH %@ AND (classFile == '\(NKCommon.TypeClassFile.image.rawValue)' OR classFile == '\(NKCommon.TypeClassFile.video.rawValue)') AND NOT (session CONTAINS[c] 'upload') AND NOT (livePhotoFile != '' AND classFile == '\(NKCommon.TypeClassFile.video.rawValue)')"
+    let showOnlyPredicateMediaString = "account == %@ AND serverUrl BEGINSWITH %@ AND classFile == %@ AND NOT (session CONTAINS[c] 'upload') AND NOT (livePhotoFile != '' AND classFile == '\(NKCommon.TypeClassFile.video.rawValue)')"
 
     override private init() {}
 
@@ -61,7 +62,7 @@ import NextcloudKit
         self.account = account
 
         ocIdEtag.removeAll()
-        self.metadatas = []
+        self.metadatas = nil
         self.metadatas = getMediaMetadatas(account: account)
         guard let metadatas = self.metadatas, !metadatas.isEmpty else { return }
         let ext = ".preview.ico"
@@ -69,7 +70,7 @@ import NextcloudKit
         let resourceKeys = Set<URLResourceKey>([.nameKey, .pathKey, .fileSizeKey, .creationDateKey])
         struct FileInfo {
             var path: URL
-            var ocId: String
+            var ocIdEtag: String
             var date: Date
         }
         var files: [FileInfo] = []
@@ -89,7 +90,7 @@ import NextcloudKit
                       let date = resourceValues.creationDate,
                       let etag = ocIdEtag[ocId],
                       fileName == etag + ext else { continue }
-                files.append(FileInfo(path: fileURL, ocId: ocId, date: date))
+                files.append(FileInfo(path: fileURL, ocIdEtag: ocId + etag, date: date))
             }
         }
 
@@ -106,7 +107,7 @@ import NextcloudKit
             if counter > limit { break }
             autoreleasepool {
                 if let image = UIImage(contentsOfFile: file.path.path) {
-                    cache.setValue(.actual(image), forKey: file.ocId)
+                    cache.setValue(.actual(image), forKey: file.ocIdEtag)
                 }
             }
         }
@@ -119,42 +120,30 @@ import NextcloudKit
         NextcloudKit.shared.nkCommonInstance.writeLog("--------- ThumbnailLRUCache image process ---------")
     }
 
-    func initialMetadatas() -> [tableMetadata]? {
-        let metadatas = self.metadatas
-        self.metadatas = nil
-        return metadatas
+    func initialMetadatas() -> Results<tableMetadata>? {
+        defer { self.metadatas = nil }
+        return self.metadatas
     }
 
-    func getMediaImage(ocId: String) -> ImageType? {
-        return cache.value(forKey: ocId)
+    func getMediaImage(ocId: String, etag: String) -> ImageType? {
+        return cache.value(forKey: ocId + etag)
     }
 
-    func setMediaImage(ocId: String, image: ImageType) {
-        cache.setValue(image, forKey: ocId)
+    func setMediaImage(ocId: String, etag: String, image: ImageType) {
+        cache.setValue(image, forKey: ocId + etag)
     }
 
     @objc func clearMediaCache() {
-
         self.ocIdEtag.removeAll()
-        self.metadatas?.removeAll()
         self.metadatas = nil
         cache.removeAllValues()
     }
 
-    func getMediaMetadatas(account: String, predicate: NSPredicate? = nil) -> [tableMetadata] {
-
-        defer {
-            self.isMediaMetadatasInProcess = false
-            NotificationCenter.default.postOnMainThread(name: NCGlobal.shared.notificationCenterFinishedMediaInProcess)
-        }
-        self.isMediaMetadatasInProcess = true
-
-        guard let account = NCManageDatabase.shared.getAccount(predicate: NSPredicate(format: "account == %@", account)) else { return [] }
+    func getMediaMetadatas(account: String, predicate: NSPredicate? = nil) -> Results<tableMetadata>? {
+        guard let account = NCManageDatabase.shared.getAccount(predicate: NSPredicate(format: "account == %@", account)) else { return nil }
         let startServerUrl = NCUtilityFileSystem().getHomeServer(urlBase: account.urlBase, userId: account.userId) + account.mediaPath
-
-        let predicateDefault = NSPredicate(format: showBothPredicateMediaString, account.account, startServerUrl, NKCommon.TypeClassFile.image.rawValue, NKCommon.TypeClassFile.video.rawValue, NKCommon.TypeClassFile.video.rawValue)
-
-        return NCManageDatabase.shared.getMetadatasMedia(predicate: predicate ?? predicateDefault)
+        let predicateBoth = NSPredicate(format: showBothPredicateMediaString, account.account, startServerUrl)
+        return NCManageDatabase.shared.getResultsMetadatas(predicate: predicate ?? predicateBoth, sorted: "date")
     }
 
     // MARK: -

+ 20 - 19
iOSClient/Networking/E2EE/NCNetworkingE2EEUpload.swift

@@ -39,13 +39,12 @@ extension uploadE2EEDelegate {
 
 class NCNetworkingE2EEUpload: NSObject {
 
-    let hud = JGProgressHUD()
     let networkingE2EE = NCNetworkingE2EE()
     let utilityFileSystem = NCUtilityFileSystem()
     let utility = NCUtility()
     var numChunks: Int = 0
 
-    func upload(metadata: tableMetadata, uploadE2EEDelegate: uploadE2EEDelegate? = nil, hudView: UIView?) async -> NKError {
+    func upload(metadata: tableMetadata, uploadE2EEDelegate: uploadE2EEDelegate? = nil, hudView: UIView?, hud: JGProgressHUD?) async -> NKError {
 
         var metadata = metadata
         let ocIdTemp = metadata.ocId
@@ -134,8 +133,8 @@ class NCNetworkingE2EEUpload: NSObject {
         //
         if let hudView {
             DispatchQueue.main.async {
-                self.hud.textLabel.text = NSLocalizedString("_wait_file_encryption_", comment: "")
-                self.hud.show(in: hudView)
+                hud?.textLabel.text = NSLocalizedString("_wait_file_encryption_", comment: "")
+                hud?.show(in: hudView)
             }
         }
 
@@ -143,7 +142,7 @@ class NCNetworkingE2EEUpload: NSObject {
         //
         let sendE2eeError = await sendE2ee(e2eToken: e2eToken, fileId: fileId)
         guard sendE2eeError == .success else {
-            DispatchQueue.main.async { self.hud.dismiss() }
+            DispatchQueue.main.async { hud?.dismiss() }
             NCManageDatabase.shared.deleteMetadata(predicate: NSPredicate(format: "ocId == %@", ocIdTemp))
             NotificationCenter.default.postOnMainThread(name: NCGlobal.shared.notificationCenterUploadedFile, userInfo: ["ocId": metadata.ocId, "serverUrl": metadata.serverUrl, "account": metadata.account, "fileName": metadata.fileName, "ocIdTemp": ocIdTemp, "error": sendE2eeError])
             await networkingE2EE.unlock(account: metadata.account, serverUrl: metadata.serverUrl)
@@ -154,21 +153,23 @@ class NCNetworkingE2EEUpload: NSObject {
         //
         if hudView != nil {
             DispatchQueue.main.async {
-                self.hud.indicatorView = JGProgressHUDRingIndicatorView()
-                if let indicatorView = self.hud.indicatorView as? JGProgressHUDRingIndicatorView {
-                    indicatorView.ringWidth = 1.5
+                if let hud {
+                    hud.indicatorView = JGProgressHUDRingIndicatorView()
+                    if let indicatorView = hud.indicatorView as? JGProgressHUDRingIndicatorView {
+                        indicatorView.ringWidth = 1.5
+                    }
+                    hud.tapOnHUDViewBlock = { _ in
+                        NotificationCenter.default.postOnMainThread(name: "NextcloudKit.chunkedFile.stop")
+                    }
+                    hud.textLabel.text = NSLocalizedString("_wait_file_preparation_", comment: "")
+                    hud.detailTextLabel.text = NSLocalizedString("_tap_to_cancel_", comment: "")
                 }
-                self.hud.tapOnHUDViewBlock = { _ in
-                    NotificationCenter.default.postOnMainThread(name: "NextcloudKit.chunkedFile.stop")
-                }
-                self.hud.textLabel.text = NSLocalizedString("_wait_file_preparation_", comment: "")
-                self.hud.detailTextLabel.text = NSLocalizedString("_tap_to_cancel_", comment: "")
             }
         }
 
         // UPLOAD
         //
-        let resultsSendFile = await sendFile(metadata: metadata, e2eToken: e2eToken, uploadE2EEDelegate: uploadE2EEDelegate)
+        let resultsSendFile = await sendFile(metadata: metadata, e2eToken: e2eToken, hud: hud, uploadE2EEDelegate: uploadE2EEDelegate)
 
         // UNLOCK
         //
@@ -211,7 +212,7 @@ class NCNetworkingE2EEUpload: NSObject {
 
     // BRIDGE for chunk
     //
-    private func sendFile(metadata: tableMetadata, e2eToken: String, uploadE2EEDelegate: uploadE2EEDelegate? = nil) async -> (ocId: String?, etag: String?, date: NSDate?, afError: AFError?, error: NKError) {
+    private func sendFile(metadata: tableMetadata, e2eToken: String, hud: JGProgressHUD?, uploadE2EEDelegate: uploadE2EEDelegate? = nil) async -> (ocId: String?, etag: String?, date: NSDate?, afError: AFError?, error: NKError) {
 
         if metadata.chunk > 0 {
 
@@ -219,14 +220,14 @@ class NCNetworkingE2EEUpload: NSObject {
                 NCNetworking.shared.uploadChunkFile(metadata: metadata, withUploadComplete: false, customHeaders: ["e2e-token": e2eToken]) { num in
                     self.numChunks = num
                 } counterChunk: { counter in
-                    DispatchQueue.main.async { self.hud.progress = Float(counter) / Float(self.numChunks) }
+                    DispatchQueue.main.async { hud?.progress = Float(counter) / Float(self.numChunks) }
                 } start: {
-                    DispatchQueue.main.async { self.hud.dismiss() }
+                    DispatchQueue.main.async { hud?.dismiss() }
                     uploadE2EEDelegate?.start()
                 } progressHandler: {totalBytesExpected, totalBytes, fractionCompleted in
                     uploadE2EEDelegate?.uploadE2EEProgress(totalBytesExpected, totalBytes, fractionCompleted)
                 } completion: { _, file, afError, error in
-                    DispatchQueue.main.async { self.hud.dismiss() }
+                    DispatchQueue.main.async { hud?.dismiss() }
                     continuation.resume(returning: (ocId: file?.ocId, etag: file?.etag, date: file?.date, afError: afError, error: error))
                 }
             })
@@ -236,7 +237,7 @@ class NCNetworkingE2EEUpload: NSObject {
             let fileNameLocalPath = utilityFileSystem.getDirectoryProviderStorageOcId(metadata.ocId, fileNameView: metadata.fileName)
             return await withCheckedContinuation({ continuation in
                 NCNetworking.shared.uploadFile(metadata: metadata, fileNameLocalPath: fileNameLocalPath, withUploadComplete: false, customHeaders: ["e2e-token": e2eToken]) {
-                    DispatchQueue.main.async { self.hud.dismiss() }
+                    DispatchQueue.main.async { hud?.dismiss() }
                     uploadE2EEDelegate?.start()
                 } progressHandler: { totalBytesExpected, totalBytes, fractionCompleted in
                     uploadE2EEDelegate?.uploadE2EEProgress(totalBytesExpected, totalBytes, fractionCompleted)

+ 106 - 58
iOSClient/Networking/NCNetworking.swift

@@ -111,6 +111,15 @@ class NCNetworking: NSObject, NKCommonDelegate {
     // REQUESTS
     var requestsUnifiedSearch: [DataRequest] = []
 
+    // OPERATIONQUEUE
+    let downloadThumbnailQueue = Queuer(name: "downloadThumbnailQueue", maxConcurrentOperationCount: 10, qualityOfService: .default)
+    let downloadThumbnailActivityQueue = Queuer(name: "downloadThumbnailActivityQueue", maxConcurrentOperationCount: 10, qualityOfService: .default)
+    let unifiedSearchQueue = Queuer(name: "unifiedSearchQueue", maxConcurrentOperationCount: 1, qualityOfService: .default)
+    let saveLivePhotoQueue = Queuer(name: "saveLivePhotoQueue", maxConcurrentOperationCount: 1, qualityOfService: .default)
+    let downloadQueue = Queuer(name: "downloadQueue", maxConcurrentOperationCount: NCGlobal.shared.maxConcurrentOperationCountDownload, qualityOfService: .default)
+    let downloadAvatarQueue = Queuer(name: "downloadAvatarQueue", maxConcurrentOperationCount: 10, qualityOfService: .default)
+    let convertLivePhotoQueue = Queuer(name: "convertLivePhotoQueue", maxConcurrentOperationCount: 10, qualityOfService: .default)
+
     // MARK: - init
 
     override init() {
@@ -364,7 +373,7 @@ 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 = utilityFileSystem.directoryUserData + "/" + fileName
 
         if let image = NCManageDatabase.shared.getImageAvatarLoaded(fileName: fileName) {
@@ -377,8 +386,8 @@ class NCNetworking: NSObject, NKCommonDelegate {
             cellImageView?.image = utility.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))
+        for case let operation as NCOperationDownloadAvatar in downloadAvatarQueue.operations where operation.fileName == fileName { return }
+        downloadAvatarQueue.addOperation(NCOperationDownloadAvatar(user: user, fileName: fileName, fileNameLocalPath: fileNameLocalPath, cell: cell, view: view, cellImageView: cellImageView))
     }
 #endif
 
@@ -387,11 +396,11 @@ class NCNetworking: NSObject, NKCommonDelegate {
     func upload(metadata: tableMetadata,
                 uploadE2EEDelegate: uploadE2EEDelegate? = nil,
                 hudView: UIView?,
+                hud: JGProgressHUD?,
                 start: @escaping () -> Void = { },
                 progressHandler: @escaping (_ totalBytesExpected: Int64, _ totalBytes: Int64, _ fractionCompleted: Double) -> Void = { _, _, _ in },
                 completion: @escaping (_ error: NKError) -> Void = { _ in }) {
 
-        let hud = JGProgressHUD()
         let metadata = tableMetadata.init(value: metadata)
         var numChunks: Int = 0
         NextcloudKit.shared.nkCommonInstance.writeLog("[INFO] Upload file \(metadata.fileNameView) with Identifier \(metadata.assetLocalIdentifier) with size \(metadata.size) [CHUNK \(metadata.chunk), E2EE \(metadata.isDirectoryE2EE)]")
@@ -399,33 +408,35 @@ class NCNetworking: NSObject, NKCommonDelegate {
         if metadata.isDirectoryE2EE {
 #if !EXTENSION_FILE_PROVIDER_EXTENSION && !EXTENSION_WIDGET
             Task {
-                let error = await NCNetworkingE2EEUpload().upload(metadata: metadata, uploadE2EEDelegate: uploadE2EEDelegate, hudView: hudView)
+                let error = await NCNetworkingE2EEUpload().upload(metadata: metadata, uploadE2EEDelegate: uploadE2EEDelegate, hudView: hudView, hud: hud)
                 completion(error)
             }
 #endif
         } else if metadata.chunk > 0 {
                 if let hudView {
                     DispatchQueue.main.async {
-                        hud.indicatorView = JGProgressHUDRingIndicatorView()
-                        if let indicatorView = hud.indicatorView as? JGProgressHUDRingIndicatorView {
-                            indicatorView.ringWidth = 1.5
-                        }
-                        hud.tapOnHUDViewBlock = { _ in
-                            NotificationCenter.default.postOnMainThread(name: "NextcloudKit.chunkedFile.stop")
+                        if let hud {
+                            hud.indicatorView = JGProgressHUDRingIndicatorView()
+                            if let indicatorView = hud.indicatorView as? JGProgressHUDRingIndicatorView {
+                                indicatorView.ringWidth = 1.5
+                            }
+                            hud.tapOnHUDViewBlock = { _ in
+                                NotificationCenter.default.postOnMainThread(name: "NextcloudKit.chunkedFile.stop")
+                            }
+                            hud.textLabel.text = NSLocalizedString("_wait_file_preparation_", comment: "")
+                            hud.detailTextLabel.text = NSLocalizedString("_tap_to_cancel_", comment: "")
+                            hud.show(in: hudView)
                         }
-                        hud.textLabel.text = NSLocalizedString("_wait_file_preparation_", comment: "")
-                        hud.detailTextLabel.text = NSLocalizedString("_tap_to_cancel_", comment: "")
-                        hud.show(in: hudView)
                     }
                 }
             uploadChunkFile(metadata: metadata) { num in
                 numChunks = num
             } counterChunk: { counter in
-                DispatchQueue.main.async { hud.progress = Float(counter) / Float(numChunks) }
+                DispatchQueue.main.async { hud?.progress = Float(counter) / Float(numChunks) }
             } start: {
-                DispatchQueue.main.async { hud.dismiss() }
+                DispatchQueue.main.async { hud?.dismiss() }
             } completion: { _, _, _, error in
-                DispatchQueue.main.async { hud.dismiss() }
+                DispatchQueue.main.async { hud?.dismiss() }
                 completion(error)
             }
         } else if metadata.session == NextcloudKit.shared.nkCommonInstance.sessionIdentifierUpload {
@@ -801,39 +812,38 @@ class NCNetworking: NSObject, NKCommonDelegate {
         }
 
         Task {
-            let serverUrlfileNamePath = metadata.urlBase + metadata.path + metadata.livePhotoFile
-            var results = await NextcloudKit.shared.setLivephoto(serverUrlfileNamePath: serverUrlfileNamePath, livePhotoFile: metadata1.livePhotoFile)
+            let serverUrlfileNamePath = metadata.urlBase + metadata.path + metadata.fileName
+            var livePhotoFile = metadata1.fileId
+            var results = await NextcloudKit.shared.setLivephoto(serverUrlfileNamePath: serverUrlfileNamePath, livePhotoFile: livePhotoFile)
             if results.error == .success {
-                NCManageDatabase.shared.setMetadataLivePhotoByServer(account: metadata.account, ocId: metadata.ocId)
+                NCManageDatabase.shared.setMetadataLivePhotoByServer(account: metadata.account, ocId: metadata.ocId, livePhotoFile: livePhotoFile)
             }
 
-            let serverUrlfileNamePath1 = metadata1.urlBase + metadata1.path + metadata1.livePhotoFile
-            results = await NextcloudKit.shared.setLivephoto(serverUrlfileNamePath: serverUrlfileNamePath1, livePhotoFile: metadata.livePhotoFile)
+            let serverUrlfileNamePath1 = metadata1.urlBase + metadata1.path + metadata1.fileName
+            livePhotoFile = metadata.fileId
+            results = await NextcloudKit.shared.setLivephoto(serverUrlfileNamePath: serverUrlfileNamePath1, livePhotoFile: livePhotoFile)
             if results.error == .success {
-                NCManageDatabase.shared.setMetadataLivePhotoByServer(account: metadata1.account, ocId: metadata1.ocId)
+                NCManageDatabase.shared.setMetadataLivePhotoByServer(account: metadata1.account, ocId: metadata1.ocId, livePhotoFile: livePhotoFile)
             }
         }
     }
 
-    func convertLivePhoto() {
+    func convertLivePhoto(metadata: tableMetadata) {
 
-        guard NCGlobal.shared.isLivePhotoServerAvailable else { return }
+        guard metadata.status == NCGlobal.shared.metadataStatusNormal else { return }
 
-        if let results = NCManageDatabase.shared.getResultsMetadatas(predicate: NSPredicate(format: "isFlaggedAsLivePhotoByServer == false AND livePhotoFile != ''")) {
-            var index: Int = 0
-            for result in results {
-                index += 1
-                let serverUrlfileNamePath = result.urlBase + result.path + result.fileName
-                NextcloudKit.shared.setLivephoto(serverUrlfileNamePath: serverUrlfileNamePath, livePhotoFile: result.livePhotoFile) { _, error in
-                    if error == .success {
-                        NCManageDatabase.shared.setMetadataLivePhotoByServer(account: result.account, ocId: result.ocId)
-                    }
-                    print("Convert LivePhoto with error \(error.errorCode)")
-                }
-                if index >= 20 { break }
+        let account = metadata.account
+        let livePhotoFile = metadata.livePhotoFile
+        let serverUrlfileNamePath = metadata.urlBase + metadata.path + metadata.fileName
+        let ocId = metadata.ocId
+
+        Task {
+            if let result = NCManageDatabase.shared.getResultMetadata(predicate: NSPredicate(format: "account == '\(account)' AND status == \(NCGlobal.shared.metadataStatusNormal) AND (fileName == '\(livePhotoFile)' || fileId == '\(livePhotoFile)')")) {
+                if livePhotoFile == result.fileId { return }
+                for case let operation as NCOperationConvertLivePhoto in convertLivePhotoQueue.operations where operation.serverUrlfileNamePath == serverUrlfileNamePath { continue }
+                convertLivePhotoQueue.addOperation(NCOperationConvertLivePhoto(serverUrlfileNamePath: serverUrlfileNamePath, livePhotoFile: result.fileId, account: account, ocId: ocId))
             }
         }
-
     }
 
     // MARK: - Cancel (Download Upload)
@@ -974,7 +984,7 @@ class NCNetworking: NSObject, NKCommonDelegate {
 
     // MARK: - WebDav Read file, folder
 
-    func readFolder(serverUrl: String, account: String, completion: @escaping (_ account: String, _ metadataFolder: tableMetadata?, _ metadatas: [tableMetadata]?, _ metadatasUpdate: [tableMetadata]?, _ metadatasLocalUpdate: [tableMetadata]?, _ metadatasDelete: [tableMetadata]?, _ error: NKError) -> Void) {
+    func readFolder(serverUrl: String, account: String, completion: @escaping (_ account: String, _ metadataFolder: tableMetadata?, _ metadatas: [tableMetadata]?, _ metadatasChangedCount: Int, _ metadatasChanged: Bool, _ error: NKError) -> Void) {
 
         NextcloudKit.shared.readFileOrFolder(serverUrlFileName: serverUrl,
                                              depth: "1",
@@ -983,7 +993,7 @@ class NCNetworking: NSObject, NKCommonDelegate {
                                              options: NKRequestOptions(queue: NextcloudKit.shared.nkCommonInstance.backgroundQueue)) { account, files, _, error in
 
             guard error == .success else {
-                completion(account, nil, nil, nil, nil, nil, error)
+                completion(account, nil, nil, 0, false, error)
                 return
             }
 
@@ -1002,10 +1012,19 @@ class NCNetworking: NSObject, NKCommonDelegate {
                     NCManageDatabase.shared.addDirectory(encrypted: metadata.e2eEncrypted, favorite: metadata.favorite, ocId: metadata.ocId, fileId: metadata.fileId, etag: nil, permissions: metadata.permissions, serverUrl: serverUrl, account: account)
                 }
 
-                let metadatasResult = NCManageDatabase.shared.getMetadatas(predicate: NSPredicate(format: "account == %@ AND serverUrl == %@ AND status == %d", account, serverUrl, NCGlobal.shared.metadataStatusNormal))
-                let metadatasChanged = NCManageDatabase.shared.updateMetadatas(metadatas, metadatasResult: metadatasResult, addCompareEtagLocal: true)
+#if !EXTENSION
+                // Convert Live Photo
+                for metadata in metadatas {
+                    if NCGlobal.shared.isLivePhotoServerAvailable, metadata.isLivePhoto {
+                        NCNetworking.shared.convertLivePhoto(metadata: metadata)
+                    }
+                }
+#endif
+
+                let predicate = NSPredicate(format: "account == %@ AND serverUrl == %@ AND status == %d", account, serverUrl, NCGlobal.shared.metadataStatusNormal)
+                let results = NCManageDatabase.shared.updateMetadatas(metadatas, predicate: predicate)
 
-                completion(account, metadataFolder, metadatas, metadatasChanged.metadatasUpdate, metadatasChanged.metadatasLocalUpdate, metadatasChanged.metadatasDelete, error)
+                completion(account, metadataFolder, metadatas, results.metadatasChangedCount, results.metadatasChanged, error)
             }
         }
     }
@@ -1072,7 +1091,6 @@ class NCNetworking: NSObject, NKCommonDelegate {
 
     func synchronizationServerUrl(_ serverUrl: String, account: String, selector: String) {
 
-#if !EXTENSION
         NextcloudKit.shared.readFileOrFolder(serverUrlFileName: serverUrl,
                                              depth: "infinity",
                                              showHiddenFiles: NCKeychain().showHiddenFiles,
@@ -1081,23 +1099,21 @@ class NCNetworking: NSObject, NKCommonDelegate {
             if error == .success {
                 NCManageDatabase.shared.convertFilesToMetadatas(files, useMetadataFolder: true) { metadataFolder, _, metadatas in
                     NCManageDatabase.shared.addDirectory(encrypted: metadataFolder.e2eEncrypted, favorite: metadataFolder.favorite, ocId: metadataFolder.ocId, fileId: metadataFolder.fileId, etag: metadataFolder.etag, permissions: metadataFolder.permissions, serverUrl: metadataFolder.serverUrl + "/" + metadataFolder.fileName, account: metadataFolder.account)
-                    let metadatasResult = NCManageDatabase.shared.getMetadatas(predicate: NSPredicate(format: "account == %@ AND serverUrl BEGINSWITH %@ AND status == %d", account, serverUrl, NCGlobal.shared.metadataStatusNormal))
-                    NCManageDatabase.shared.updateMetadatas(metadatas, metadatasResult: metadatasResult)
+                    let predicate = NSPredicate(format: "account == %@ AND serverUrl BEGINSWITH %@ AND status == %d", account, serverUrl, NCGlobal.shared.metadataStatusNormal)
+                    NCManageDatabase.shared.updateMetadatas(metadatas, predicate: predicate)
                     for metadata in metadatas {
                         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,
                                   self.synchronizeMetadata(metadata),
-                                  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))
+                                  self.downloadQueue.operations.filter({ ($0 as? NCOperationDownload)?.metadata.ocId == metadata.ocId }).isEmpty {
+                            self.downloadQueue.addOperation(NCOperationDownload(metadata: metadata, selector: selector))
                         }
                     }
                 }
             }
         }
-#endif
     }
 
     func synchronizeMetadata(_ metadata: tableMetadata) -> Bool {
@@ -1460,7 +1476,7 @@ class NCNetworking: NSObject, NKCommonDelegate {
             return NKError()
 #endif
         } else {
-            if let metadataLive = NCManageDatabase.shared.getMetadataLivePhoto(metadata: metadata) {
+            if let metadataLive = NCManageDatabase.shared.getMetadataLivePhoto(metadata: metadata), metadata.isNotFlaggedAsLivePhotoByServer {
                 let error = await deleteMetadataPlain(metadataLive)
                 if error == .success {
                     return await deleteMetadataPlain(metadata)
@@ -1594,16 +1610,16 @@ class NCNetworking: NSObject, NKCommonDelegate {
             }
 #endif
         } else {
-            if metadataLive == nil {
-                renameMetadataPlain(metadata, fileNameNew: fileNameNew, indexPath: indexPath, completion: completion)
-            } else {
-                renameMetadataPlain(metadataLive!, fileNameNew: fileNameNewLive, indexPath: indexPath) { error in
+            if let metadataLive, metadata.isNotFlaggedAsLivePhotoByServer {
+                renameMetadataPlain(metadataLive, fileNameNew: fileNameNewLive, indexPath: indexPath) { error in
                     if error == .success {
                         self.renameMetadataPlain(metadata, fileNameNew: fileNameNew, indexPath: indexPath, completion: completion)
                     } else {
                         completion(error)
                     }
                 }
+            } else {
+                renameMetadataPlain(metadata, fileNameNew: fileNameNew, indexPath: indexPath, completion: completion)
             }
         }
     }
@@ -1679,7 +1695,7 @@ class NCNetworking: NSObject, NKCommonDelegate {
 
     func moveMetadata(_ metadata: tableMetadata, serverUrlTo: String, overwrite: Bool) async -> NKError {
 
-        if let metadataLive = NCManageDatabase.shared.getMetadataLivePhoto(metadata: metadata) {
+        if let metadataLive = NCManageDatabase.shared.getMetadataLivePhoto(metadata: metadata), metadata.isNotFlaggedAsLivePhotoByServer {
             let error = await moveMetadataPlain(metadataLive, serverUrlTo: serverUrlTo, overwrite: overwrite)
             if error == .success {
                 return await moveMetadataPlain(metadata, serverUrlTo: serverUrlTo, overwrite: overwrite)
@@ -1704,8 +1720,12 @@ class NCNetworking: NSObject, NKCommonDelegate {
         if result.error == .success {
             if metadata.directory {
                 NCManageDatabase.shared.deleteDirectoryAndSubDirectory(serverUrl: utilityFileSystem.stringAppendServerUrl(metadata.serverUrl, addFileName: metadata.fileName), account: result.account)
+            } else {
+                NCManageDatabase.shared.deleteMetadata(predicate: NSPredicate(format: "ocId == %@", metadata.ocId))
+                if let metadataLive = NCManageDatabase.shared.getMetadataLivePhoto(metadata: metadata) {
+                    NCManageDatabase.shared.deleteMetadata(predicate: NSPredicate(format: "ocId == %@", metadataLive.ocId))
+                }
             }
-            NCManageDatabase.shared.moveMetadata(ocId: metadata.ocId, serverUrlTo: serverUrlTo)
         }
 
         return result.error
@@ -1715,7 +1735,7 @@ class NCNetworking: NSObject, NKCommonDelegate {
 
     func copyMetadata(_ metadata: tableMetadata, serverUrlTo: String, overwrite: Bool) async -> NKError {
 
-        if let metadataLive = NCManageDatabase.shared.getMetadataLivePhoto(metadata: metadata) {
+        if let metadataLive = NCManageDatabase.shared.getMetadataLivePhoto(metadata: metadata), metadata.isNotFlaggedAsLivePhotoByServer {
             let error = await copyMetadataPlain(metadataLive, serverUrlTo: serverUrlTo, overwrite: overwrite)
             if error == .success {
                 return await copyMetadataPlain(metadata, serverUrlTo: serverUrlTo, overwrite: overwrite)
@@ -1826,6 +1846,34 @@ class NCOperationDownload: ConcurrentOperation {
     }
 }
 
+class NCOperationConvertLivePhoto: ConcurrentOperation {
+
+    var serverUrlfileNamePath, livePhotoFile, account, ocId: String
+
+    init(serverUrlfileNamePath: String, livePhotoFile: String, account: String, ocId: String) {
+        self.serverUrlfileNamePath = serverUrlfileNamePath
+        self.livePhotoFile = livePhotoFile
+        self.account = account
+        self.ocId = ocId
+    }
+
+    override func start() {
+
+        guard !isCancelled else { return self.finish() }
+        NextcloudKit.shared.setLivephoto(serverUrlfileNamePath: serverUrlfileNamePath, livePhotoFile: livePhotoFile, options: NKRequestOptions(queue: NextcloudKit.shared.nkCommonInstance.backgroundQueue)) { _, error in
+            if error == .success {
+                NCManageDatabase.shared.setMetadataLivePhotoByServer(account: self.account, ocId: self.ocId, livePhotoFile: self.livePhotoFile)
+            } else {
+                NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] Convert LivePhoto with error \(error.errorCode)")
+            }
+            self.finish()
+            if NCNetworking.shared.convertLivePhotoQueue.operationCount == 0 {
+                NotificationCenter.default.postOnMainThread(name: NCGlobal.shared.notificationCenterReloadDataSource, second: 0.1)
+            }
+        }
+    }
+}
+
 class NCOperationDownloadAvatar: ConcurrentOperation {
 
     var user: String

+ 7 - 3
iOSClient/Networking/NCNetworkingProcessUpload.swift

@@ -39,6 +39,7 @@ class NCNetworkingProcessUpload: NSObject {
     private var notificationToken: NotificationToken?
     private var timerProcess: Timer?
     private var pauseProcess: Bool = false
+    private var hud: JGProgressHUD?
 
     func observeTableMetadata() {
         do {
@@ -102,7 +103,10 @@ class NCNetworkingProcessUpload: NSObject {
         let applicationState = UIApplication.shared.applicationState
         let queue = DispatchQueue.global()
         var maxConcurrentOperationUpload = NCBrandOptions.shared.maxConcurrentOperationUpload
-        let hud = JGProgressHUD()
+
+        if applicationState == .active {
+            hud = JGProgressHUD()
+        }
 
         queue.async {
 
@@ -156,7 +160,7 @@ class NCNetworkingProcessUpload: NSObject {
 
                         let semaphore = DispatchSemaphore(value: 0)
                         let cameraRoll = NCCameraRoll()
-                        cameraRoll.extractCameraRoll(from: metadata, viewController: self.rootViewController, hud: hud) { metadatas in
+                        cameraRoll.extractCameraRoll(from: metadata, viewController: self.rootViewController, hud: self.hud) { metadatas in
                             if metadatas.isEmpty {
                                 NCManageDatabase.shared.deleteMetadata(predicate: NSPredicate(format: "ocId == %@", metadata.ocId))
                             }
@@ -175,7 +179,7 @@ class NCNetworkingProcessUpload: NSObject {
                                 }
 
                                 if let metadata = NCManageDatabase.shared.setMetadataStatus(ocId: metadata.ocId, status: NCGlobal.shared.metadataStatusInUpload) {
-                                    NCNetworking.shared.upload(metadata: metadata, hudView: self.hudView)
+                                    NCNetworking.shared.upload(metadata: metadata, hudView: self.hudView, hud: self.hud)
                                     if isInDirectoryE2EE || metadata.chunk > 0 {
                                         maxConcurrentOperationUpload = 1
                                     }

+ 2 - 2
iOSClient/Networking/NCService.swift

@@ -302,8 +302,8 @@ class NCService: NSObject {
         for file: tableLocalFile in files {
             guard let metadata = NCManageDatabase.shared.getMetadataFromOcId(file.ocId) else { continue }
             if NCNetworking.shared.synchronizeMetadata(metadata),
-               appDelegate.downloadQueue.operations.filter({ ($0 as? NCOperationDownload)?.metadata.ocId == metadata.ocId }).isEmpty {
-                appDelegate.downloadQueue.addOperation(NCOperationDownload(metadata: metadata, selector: NCGlobal.shared.selectorDownloadFile))
+               NCNetworking.shared.downloadQueue.operations.filter({ ($0 as? NCOperationDownload)?.metadata.ocId == metadata.ocId }).isEmpty {
+                NCNetworking.shared.downloadQueue.addOperation(NCOperationDownload(metadata: metadata, selector: NCGlobal.shared.selectorDownloadFile))
             }
         }
         NextcloudKit.shared.nkCommonInstance.writeLog("[INFO] end synchronize offline")

+ 3 - 3
iOSClient/Select/NCSelect.swift

@@ -309,8 +309,8 @@ extension NCSelect: UICollectionViewDataSource {
                     (cell as? NCCellProtocol)?.filePreviewImageView?.image = UIImage(named: metadata.iconName)
                 }
                 if metadata.hasPreview && metadata.status == NCGlobal.shared.metadataStatusNormal && (!utilityFileSystem.fileProviderStoragePreviewIconExists(metadata.ocId, etag: metadata.etag)) {
-                    for case let operation as NCCollectionViewDownloadThumbnail in appDelegate.downloadThumbnailQueue.operations where operation.metadata.ocId == metadata.ocId { return }
-                    appDelegate.downloadThumbnailQueue.addOperation(NCCollectionViewDownloadThumbnail(metadata: metadata, cell: (cell as? NCCellProtocol), collectionView: collectionView))
+                    for case let operation as NCCollectionViewDownloadThumbnail in NCNetworking.shared.downloadThumbnailQueue.operations where operation.metadata.ocId == metadata.ocId { return }
+                    NCNetworking.shared.downloadThumbnailQueue.addOperation(NCCollectionViewDownloadThumbnail(metadata: metadata, cell: (cell as? NCCellProtocol), collectionView: collectionView))
                 }
             }
         }
@@ -564,7 +564,7 @@ extension NCSelect {
         networkInProgress = true
         collectionView.reloadData()
 
-        NCNetworking.shared.readFolder(serverUrl: serverUrl, account: activeAccount.account) { _, _, _, _, _, _, error in
+        NCNetworking.shared.readFolder(serverUrl: serverUrl, account: activeAccount.account) { _, _, _, _, _, error in
             if error != .success {
                 NCContentPresenter().showError(error: error)
             }

BIN
iOSClient/Supporting Files/af.lproj/Localizable.strings


BIN
iOSClient/Supporting Files/an.lproj/Localizable.strings


BIN
iOSClient/Supporting Files/ar.lproj/Localizable.strings


BIN
iOSClient/Supporting Files/ast.lproj/Localizable.strings


BIN
iOSClient/Supporting Files/az.lproj/Localizable.strings


BIN
iOSClient/Supporting Files/be.lproj/Localizable.strings


BIN
iOSClient/Supporting Files/bg_BG.lproj/Localizable.strings


BIN
iOSClient/Supporting Files/bn_BD.lproj/Localizable.strings


BIN
iOSClient/Supporting Files/br.lproj/Localizable.strings


BIN
iOSClient/Supporting Files/bs.lproj/Localizable.strings


BIN
iOSClient/Supporting Files/ca.lproj/Localizable.strings


BIN
iOSClient/Supporting Files/cs-CZ.lproj/Localizable.strings


BIN
iOSClient/Supporting Files/cy_GB.lproj/Localizable.strings


BIN
iOSClient/Supporting Files/da.lproj/Localizable.strings


BIN
iOSClient/Supporting Files/de.lproj/Localizable.strings


BIN
iOSClient/Supporting Files/el.lproj/Localizable.strings


BIN
iOSClient/Supporting Files/en-GB.lproj/Localizable.strings


BIN
iOSClient/Supporting Files/eo.lproj/Localizable.strings


BIN
iOSClient/Supporting Files/es-419.lproj/Localizable.strings


BIN
iOSClient/Supporting Files/es-AR.lproj/Localizable.strings


BIN
iOSClient/Supporting Files/es-CL.lproj/Localizable.strings


BIN
iOSClient/Supporting Files/es-CO.lproj/Localizable.strings


BIN
iOSClient/Supporting Files/es-CR.lproj/Localizable.strings


BIN
iOSClient/Supporting Files/es-DO.lproj/Localizable.strings


BIN
iOSClient/Supporting Files/es-EC.lproj/Localizable.strings


BIN
iOSClient/Supporting Files/es-GT.lproj/Localizable.strings


BIN
iOSClient/Supporting Files/es-HN.lproj/Localizable.strings


BIN
iOSClient/Supporting Files/es-MX.lproj/Localizable.strings


BIN
iOSClient/Supporting Files/es-NI.lproj/Localizable.strings


BIN
iOSClient/Supporting Files/es-PA.lproj/Localizable.strings


BIN
iOSClient/Supporting Files/es-PE.lproj/Localizable.strings


BIN
iOSClient/Supporting Files/es-PR.lproj/Localizable.strings


BIN
iOSClient/Supporting Files/es-PY.lproj/Localizable.strings


BIN
iOSClient/Supporting Files/es-SV.lproj/Localizable.strings


BIN
iOSClient/Supporting Files/es-UY.lproj/Localizable.strings


BIN
iOSClient/Supporting Files/es.lproj/Localizable.strings


BIN
iOSClient/Supporting Files/et_EE.lproj/Localizable.strings


BIN
iOSClient/Supporting Files/eu.lproj/Localizable.strings


BIN
iOSClient/Supporting Files/fa.lproj/Localizable.strings


BIN
iOSClient/Supporting Files/fi-FI.lproj/Localizable.strings


BIN
iOSClient/Supporting Files/fo.lproj/Localizable.strings


BIN
iOSClient/Supporting Files/fr.lproj/Localizable.strings


BIN
iOSClient/Supporting Files/gd.lproj/Localizable.strings


BIN
iOSClient/Supporting Files/gl.lproj/Localizable.strings


BIN
iOSClient/Supporting Files/he.lproj/Localizable.strings


BIN
iOSClient/Supporting Files/hi_IN.lproj/Localizable.strings


BIN
iOSClient/Supporting Files/hr.lproj/Localizable.strings


BIN
iOSClient/Supporting Files/hsb.lproj/Localizable.strings


BIN
iOSClient/Supporting Files/hu.lproj/Localizable.strings


BIN
iOSClient/Supporting Files/hy.lproj/Localizable.strings


BIN
iOSClient/Supporting Files/ia.lproj/Localizable.strings


BIN
iOSClient/Supporting Files/id.lproj/Localizable.strings


BIN
iOSClient/Supporting Files/ig.lproj/Localizable.strings


BIN
iOSClient/Supporting Files/is.lproj/Localizable.strings


BIN
iOSClient/Supporting Files/it.lproj/Localizable.strings


BIN
iOSClient/Supporting Files/ja-JP.lproj/Localizable.strings


BIN
iOSClient/Supporting Files/ka-GE.lproj/Localizable.strings


BIN
iOSClient/Supporting Files/ka.lproj/Localizable.strings


BIN
iOSClient/Supporting Files/kab.lproj/Localizable.strings


BIN
iOSClient/Supporting Files/km.lproj/Localizable.strings


BIN
iOSClient/Supporting Files/kn.lproj/Localizable.strings


BIN
iOSClient/Supporting Files/ko.lproj/Localizable.strings


BIN
iOSClient/Supporting Files/la.lproj/Localizable.strings


BIN
iOSClient/Supporting Files/lb.lproj/Localizable.strings


BIN
iOSClient/Supporting Files/lo.lproj/Localizable.strings


BIN
iOSClient/Supporting Files/lt_LT.lproj/Localizable.strings


BIN
iOSClient/Supporting Files/lv.lproj/Localizable.strings


BIN
iOSClient/Supporting Files/mk.lproj/Localizable.strings


BIN
iOSClient/Supporting Files/mn.lproj/Localizable.strings


BIN
iOSClient/Supporting Files/mr.lproj/Localizable.strings


BIN
iOSClient/Supporting Files/ms_MY.lproj/Localizable.strings


BIN
iOSClient/Supporting Files/my.lproj/Localizable.strings


BIN
iOSClient/Supporting Files/nb-NO.lproj/Localizable.strings


BIN
iOSClient/Supporting Files/ne.lproj/Localizable.strings


BIN
iOSClient/Supporting Files/nl.lproj/Localizable.strings


BIN
iOSClient/Supporting Files/nn_NO.lproj/Localizable.strings


BIN
iOSClient/Supporting Files/oc.lproj/Localizable.strings


BIN
iOSClient/Supporting Files/pl.lproj/Localizable.strings


Some files were not shown because too many files changed in this diff