Marino Faggiana 1 жил өмнө
parent
commit
e51ce11520
100 өөрчлөгдсөн 2564 нэмэгдсэн , 2120 устгасан
  1. 2 2
      .swiftlint.yml
  2. 1 1
      Brand/Database.swift
  3. 4 2
      Brand/NCBrand.swift
  4. 0 2
      ExternalResources/NCApplicationHandle.swift
  5. 1 1
      File Provider Extension/FileProviderEnumerator.swift
  6. 4 4
      File Provider Extension/FileProviderExtension.swift
  7. 61 17
      Nextcloud.xcodeproj/project.pbxproj
  8. 1 0
      Widget/Files/FilesData.swift
  9. 1 1
      iOSClient/Activity/NCActivity.swift
  10. 7 19
      iOSClient/AppDelegate.swift
  11. 3 34
      iOSClient/Data/NCDataSource.swift
  12. 51 41
      iOSClient/Data/NCManageDatabase+Capabilities.swift
  13. 22 1
      iOSClient/Data/NCManageDatabase+Directory.swift
  14. 1 33
      iOSClient/Data/NCManageDatabase+E2EE.swift
  15. 0 6
      iOSClient/Data/NCManageDatabase+LocalFile.swift
  16. 173 50
      iOSClient/Data/NCManageDatabase+Metadata.swift
  17. 117 0
      iOSClient/Data/NCManageDatabase+SecurityGuard.swift
  18. 3 2
      iOSClient/Data/NCManageDatabase+Video.swift
  19. 3 3
      iOSClient/Data/NCManageDatabase.swift
  20. 3 0
      iOSClient/Diagnostics/NCCapabilitiesView.swift
  21. 6 0
      iOSClient/Extensions/String+Extension.swift
  22. 0 1
      iOSClient/Extensions/UIAlertController+Extension.swift
  23. 1 2
      iOSClient/Extensions/UINavigationController+Extension.swift
  24. 10 23
      iOSClient/Favorites/NCFavorite.swift
  25. 4 4
      iOSClient/Files/NCFiles.storyboard
  26. 87 45
      iOSClient/Files/NCFiles.swift
  27. 10 22
      iOSClient/Groupfolders/NCGroupfolders.swift
  28. 11 10
      iOSClient/Login/NCLogin.swift
  29. 10 15
      iOSClient/Login/NCLoginWeb.swift
  30. 123 177
      iOSClient/Main/Collection Common/NCCollectionViewCommon.swift
  31. 33 33
      iOSClient/Main/Collection Common/NCListCell.xib
  32. 5 5
      iOSClient/Main/Collection Common/NCSelectableNavigationView.swift
  33. 2 6
      iOSClient/Main/Create cloud/NCCreateFormUploadDocuments.swift
  34. 1 1
      iOSClient/Main/Create cloud/NCCreateFormUploadVoiceNote.swift
  35. 1 1
      iOSClient/Main/Create cloud/NCUploadAssets.swift
  36. 2 2
      iOSClient/Main/Main.storyboard
  37. 51 38
      iOSClient/Main/NCActionCenter.swift
  38. 53 35
      iOSClient/Main/NCMainTabBar.swift
  39. 1 1
      iOSClient/Main/NCPickerViewController.swift
  40. 12 9
      iOSClient/Main/Section Header Footer/NCSectionFooter.xib
  41. 3 3
      iOSClient/Main/Section Header Footer/NCSectionHeaderMenu.swift
  42. 4 4
      iOSClient/Media/NCMedia.storyboard
  43. 128 588
      iOSClient/Media/NCMedia.swift
  44. 252 0
      iOSClient/Media/NCMediaCommandView.swift
  45. 37 137
      iOSClient/Media/NCMediaCommandView.xib
  46. 137 0
      iOSClient/Media/NCMediaDataSource.swift
  47. 84 0
      iOSClient/Media/NCMediaDownloadThumbnaill.swift
  48. 64 0
      iOSClient/Media/NCMediaGridLayout.swift
  49. 18 4
      iOSClient/Menu/NCCollectionViewCommon+Menu.swift
  50. 23 5
      iOSClient/Menu/NCContextMenu.swift
  51. 0 205
      iOSClient/Menu/NCMedia+Menu.swift
  52. 9 2
      iOSClient/Menu/NCMenuAction.swift
  53. 7 3
      iOSClient/Menu/NCOperationSaveLivePhoto.swift
  54. 29 7
      iOSClient/Menu/NCViewer+Menu.swift
  55. 25 12
      iOSClient/NCGlobal.swift
  56. 18 13
      iOSClient/NCImageCache.swift
  57. 2 3
      iOSClient/Networking/E2EE/NCEndToEndMetadataV1.swift
  58. 97 99
      iOSClient/Networking/E2EE/NCEndToEndMetadataV20.swift
  59. 19 9
      iOSClient/Networking/E2EE/NCNetworkingE2EE.swift
  60. 1 1
      iOSClient/Networking/E2EE/NCNetworkingE2EECreateFolder.swift
  61. 1 1
      iOSClient/Networking/E2EE/NCNetworkingE2EEMarkFolder.swift
  62. 49 7
      iOSClient/Networking/E2EE/NCNetworkingE2EEUpload.swift
  63. 7 7
      iOSClient/Networking/NCAutoUpload.swift
  64. 445 165
      iOSClient/Networking/NCNetworking.swift
  65. 2 1
      iOSClient/Networking/NCNetworkingCheckRemoteUser.swift
  66. 71 68
      iOSClient/Networking/NCNetworkingProcessUpload.swift
  67. 97 29
      iOSClient/Networking/NCService.swift
  68. 1 1
      iOSClient/Notification/NCNotification.swift
  69. 6 17
      iOSClient/Offline/NCOffline.swift
  70. 4 4
      iOSClient/PushNotification/NCPushNotification.m
  71. 6 23
      iOSClient/Recent/NCRecent.swift
  72. 7 10
      iOSClient/RichWorkspace/NCViewerRichWorkspace.swift
  73. 1 1
      iOSClient/Scan document/NCUploadScanDocument.swift
  74. 2 1
      iOSClient/Settings/CCAdvanced.m
  75. 7 19
      iOSClient/Settings/NCKeychain.swift
  76. 3 0
      iOSClient/Settings/NCSettings.m
  77. 11 21
      iOSClient/Shares/NCShares.swift
  78. BIN
      iOSClient/Supporting Files/af.lproj/Localizable.strings
  79. BIN
      iOSClient/Supporting Files/an.lproj/Localizable.strings
  80. BIN
      iOSClient/Supporting Files/ar.lproj/Localizable.strings
  81. BIN
      iOSClient/Supporting Files/ast.lproj/Localizable.strings
  82. BIN
      iOSClient/Supporting Files/az.lproj/Localizable.strings
  83. BIN
      iOSClient/Supporting Files/be.lproj/Localizable.strings
  84. BIN
      iOSClient/Supporting Files/bg_BG.lproj/Localizable.strings
  85. BIN
      iOSClient/Supporting Files/bn_BD.lproj/Localizable.strings
  86. BIN
      iOSClient/Supporting Files/br.lproj/Localizable.strings
  87. BIN
      iOSClient/Supporting Files/bs.lproj/Localizable.strings
  88. BIN
      iOSClient/Supporting Files/ca.lproj/Localizable.strings
  89. BIN
      iOSClient/Supporting Files/cs-CZ.lproj/Localizable.strings
  90. BIN
      iOSClient/Supporting Files/cy_GB.lproj/Localizable.strings
  91. BIN
      iOSClient/Supporting Files/da.lproj/Localizable.strings
  92. BIN
      iOSClient/Supporting Files/de.lproj/Localizable.strings
  93. BIN
      iOSClient/Supporting Files/el.lproj/Localizable.strings
  94. BIN
      iOSClient/Supporting Files/en-GB.lproj/Localizable.strings
  95. 6 1
      iOSClient/Supporting Files/en.lproj/Localizable.strings
  96. BIN
      iOSClient/Supporting Files/eo.lproj/Localizable.strings
  97. BIN
      iOSClient/Supporting Files/es-419.lproj/Localizable.strings
  98. BIN
      iOSClient/Supporting Files/es-AR.lproj/Localizable.strings
  99. BIN
      iOSClient/Supporting Files/es-CL.lproj/Localizable.strings
  100. BIN
      iOSClient/Supporting Files/es-CO.lproj/Localizable.strings

+ 2 - 2
.swiftlint.yml

@@ -17,8 +17,8 @@ function_body_length:
    warning: 400
 
 type_body_length:
-  warning: 1500
-  error: 2000
+  warning: 2000
+  error: 2500
 
 file_length:
   warning: 2000

+ 1 - 1
Brand/Database.swift

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

+ 4 - 2
Brand/NCBrand.swift

@@ -80,12 +80,14 @@ let userAgent: String = {
     @objc public var disable_log: Bool = false
     @objc public var disable_mobileconfig: Bool = false
     @objc public var disable_show_more_nextcloud_apps_in_settings: Bool = false
+    @objc public var doNotAskPasscodeAtStartup: Bool = false
 
     // Internal option behaviour
     @objc public var cleanUpDay: Int = 0                                                        // Set default "Delete, in the cache, all files older than" possible days value are: 0, 1, 7, 30, 90, 180, 365
 
-    // Max upload concurrent
-    public let maxConcurrentOperationUpload: Int = 10
+    // Max download/upload concurrent
+    public let maxConcurrentOperationDownload: Int = 5
+    public let maxConcurrentOperationUpload: Int = 5
 
     // Number of failed attempts after reset app
     @objc public let resetAppPasscodeAttempts: Int = 10

+ 0 - 2
ExternalResources/NCApplicationHandle.swift

@@ -28,8 +28,6 @@ import Parchment
 
 class NCApplicationHandle: NSObject {
 
-    let appDelegate = (UIApplication.shared.delegate as? AppDelegate)!
-
     // class: AppDelegate
     // func nextcloudPushNotificationAction(data: [String: AnyObject])
     func nextcloudPushNotificationAction(data: [String: AnyObject]) -> [String: AnyObject]? {

+ 1 - 1
File Provider Extension/FileProviderEnumerator.swift

@@ -174,7 +174,7 @@ class FileProviderEnumerator: NSObject, NSFileProviderEnumerator {
 
             for metadata in metadatas! {
 
-                if metadata.e2eEncrypted || (!metadata.session.isEmpty && metadata.session != NCNetworking.shared.sessionIdentifierBackgroundExtension) { continue }
+                if metadata.e2eEncrypted || (!metadata.session.isEmpty && metadata.session != NCNetworking.shared.sessionUploadBackgroundExtension) { continue }
 
                 fpUtility.createocIdentifierOnFileSystem(metadata: metadata)
 

+ 4 - 4
File Provider Extension/FileProviderExtension.swift

@@ -64,7 +64,7 @@ class FileProviderExtension: NSFileProviderExtension, NCNetworkingDelegate {
         // Create directory File Provider Storage
         _ = utilityFileSystem.directoryProviderStorage
         // Configure URLSession
-        _ = NCNetworking.shared.sessionManagerBackgroundExtension
+        _ = NCNetworking.shared.sessionManagerUploadBackgroundExtension
     }
 
     deinit {
@@ -285,7 +285,7 @@ class FileProviderExtension: NSFileProviderExtension, NCNetworkingDelegate {
         let serverUrlFileName = metadata.serverUrl + "/" + fileName
         let fileNameLocalPath = url.path
 
-        if let task = NKBackground(nkCommonInstance: NextcloudKit.shared.nkCommonInstance).upload(serverUrlFileName: serverUrlFileName, fileNameLocalPath: fileNameLocalPath, dateCreationFile: nil, dateModificationFile: nil, description: metadata.ocId, session: NCNetworking.shared.sessionManagerBackgroundExtension) {
+        if let task = NKBackground(nkCommonInstance: NextcloudKit.shared.nkCommonInstance).upload(serverUrlFileName: serverUrlFileName, fileNameLocalPath: fileNameLocalPath, dateCreationFile: nil, dateModificationFile: nil, description: metadata.ocId, session: NCNetworking.shared.sessionManagerUploadBackgroundExtension) {
 
             fileProviderData.shared.fileProviderManager.register(task, forItemWithIdentifier: NSFileProviderItemIdentifier(metadata.fileId)) { _ in }
         }
@@ -356,7 +356,7 @@ class FileProviderExtension: NSFileProviderExtension, NCNetworkingDelegate {
                 fileURL.stopAccessingSecurityScopedResource()
 
                 let metadata = NCManageDatabase.shared.createMetadata(account: fileProviderData.shared.account, user: fileProviderData.shared.user, userId: fileProviderData.shared.userId, fileName: fileName, fileNameView: fileName, ocId: ocIdTemp, serverUrl: tableDirectory.serverUrl, urlBase: fileProviderData.shared.accountUrlBase, url: "", contentType: "")
-                metadata.session = NCNetworking.shared.sessionIdentifierBackgroundExtension
+                metadata.session = NCNetworking.shared.sessionUploadBackgroundExtension
                 metadata.size = size
                 metadata.status = NCGlobal.shared.metadataStatusUploading
 
@@ -365,7 +365,7 @@ class FileProviderExtension: NSFileProviderExtension, NCNetworkingDelegate {
                 let serverUrlFileName = tableDirectory.serverUrl + "/" + fileName
                 let fileNameLocalPath = self.utilityFileSystem.getDirectoryProviderStorageOcId(ocIdTemp, fileNameView: fileName)
 
-                if let task = NKBackground(nkCommonInstance: NextcloudKit.shared.nkCommonInstance).upload(serverUrlFileName: serverUrlFileName, fileNameLocalPath: fileNameLocalPath, dateCreationFile: nil, dateModificationFile: nil, description: ocIdTemp, session: NCNetworking.shared.sessionManagerBackgroundExtension) {
+                if let task = NKBackground(nkCommonInstance: NextcloudKit.shared.nkCommonInstance).upload(serverUrlFileName: serverUrlFileName, fileNameLocalPath: fileNameLocalPath, dateCreationFile: nil, dateModificationFile: nil, description: ocIdTemp, session: NCNetworking.shared.sessionManagerUploadBackgroundExtension) {
 
                     self.outstandingSessionTasks[URL(fileURLWithPath: fileNameLocalPath)] = task as URLSessionTask
 

+ 61 - 17
Nextcloud.xcodeproj/project.pbxproj

@@ -202,6 +202,13 @@
 		F719D9E0288D37A300762E33 /* NCColorPicker.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = F719D9DF288D37A300762E33 /* NCColorPicker.storyboard */; };
 		F719D9E2288D396100762E33 /* NCColorPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = F719D9E1288D396100762E33 /* NCColorPicker.swift */; };
 		F71CD6CA2930D7B1006C95C1 /* NCApplicationHandle.swift in Sources */ = {isa = PBXBuildFile; fileRef = F71CD6C92930D7B1006C95C1 /* NCApplicationHandle.swift */; };
+		F71F6D072B6A6A5E00F1EB15 /* ThreadSafeArray.swift in Sources */ = {isa = PBXBuildFile; fileRef = F71F6D062B6A6A5E00F1EB15 /* ThreadSafeArray.swift */; };
+		F71F6D082B6A6A5E00F1EB15 /* ThreadSafeArray.swift in Sources */ = {isa = PBXBuildFile; fileRef = F71F6D062B6A6A5E00F1EB15 /* ThreadSafeArray.swift */; };
+		F71F6D092B6A6A5E00F1EB15 /* ThreadSafeArray.swift in Sources */ = {isa = PBXBuildFile; fileRef = F71F6D062B6A6A5E00F1EB15 /* ThreadSafeArray.swift */; };
+		F71F6D0A2B6A6A5E00F1EB15 /* ThreadSafeArray.swift in Sources */ = {isa = PBXBuildFile; fileRef = F71F6D062B6A6A5E00F1EB15 /* ThreadSafeArray.swift */; };
+		F71F6D0B2B6A6A5E00F1EB15 /* ThreadSafeArray.swift in Sources */ = {isa = PBXBuildFile; fileRef = F71F6D062B6A6A5E00F1EB15 /* ThreadSafeArray.swift */; };
+		F71F6D0C2B6A6A5E00F1EB15 /* ThreadSafeArray.swift in Sources */ = {isa = PBXBuildFile; fileRef = F71F6D062B6A6A5E00F1EB15 /* ThreadSafeArray.swift */; };
+		F71F6D0D2B6A6A5E00F1EB15 /* ThreadSafeArray.swift in Sources */ = {isa = PBXBuildFile; fileRef = F71F6D062B6A6A5E00F1EB15 /* ThreadSafeArray.swift */; };
 		F7226EDC1EE4089300EBECB1 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = F7226EDB1EE4089300EBECB1 /* Main.storyboard */; };
 		F723985C253C95CE00257F49 /* NCViewerRichdocument.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = F723985B253C95CE00257F49 /* NCViewerRichdocument.storyboard */; };
 		F7239871253D86B600257F49 /* NCEmptyDataSet.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7239870253D86B600257F49 /* NCEmptyDataSet.swift */; };
@@ -409,7 +416,6 @@
 		F757CC8C29E82D0500F31428 /* NCGroupfolders.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = F757CC8A29E82D0500F31428 /* NCGroupfolders.storyboard */; };
 		F757CC8D29E82D0500F31428 /* NCGroupfolders.swift in Sources */ = {isa = PBXBuildFile; fileRef = F757CC8B29E82D0500F31428 /* NCGroupfolders.swift */; };
 		F7581D1A25EFDA61004DC699 /* NCLoginWeb+Menu.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7581D1925EFDA60004DC699 /* NCLoginWeb+Menu.swift */; };
-		F7581D2425EFDDDF004DC699 /* NCMedia+Menu.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7581D2325EFDDDF004DC699 /* NCMedia+Menu.swift */; };
 		F758A01227A7F03E0069468B /* JGProgressHUD in Frameworks */ = {isa = PBXBuildFile; productRef = F758A01127A7F03E0069468B /* JGProgressHUD */; };
 		F758B45A212C564000515F55 /* NCScan.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = F758B457212C564000515F55 /* NCScan.storyboard */; };
 		F758B45E212C569D00515F55 /* NCScanCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = F758B45D212C569C00515F55 /* NCScanCell.swift */; };
@@ -588,6 +594,9 @@
 		F78ACD4B21903F850088454D /* NCTrashListCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = F78ACD4921903F850088454D /* NCTrashListCell.xib */; };
 		F78ACD52219046DC0088454D /* NCSectionHeaderMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = F78ACD51219046DC0088454D /* NCSectionHeaderMenu.swift */; };
 		F78ACD54219047D40088454D /* NCSectionFooter.xib in Resources */ = {isa = PBXBuildFile; fileRef = F78ACD53219047D40088454D /* NCSectionFooter.xib */; };
+		F78B87E72B62527100C65ADC /* NCMediaDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = F78B87E62B62527100C65ADC /* NCMediaDataSource.swift */; };
+		F78B87E92B62550800C65ADC /* NCMediaDownloadThumbnaill.swift in Sources */ = {isa = PBXBuildFile; fileRef = F78B87E82B62550800C65ADC /* NCMediaDownloadThumbnaill.swift */; };
+		F78B87EB2B627AA100C65ADC /* NCMediaCommandView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F78B87EA2B627AA100C65ADC /* NCMediaCommandView.swift */; };
 		F78C6FDE296D677300C952C3 /* NCContextMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = F78C6FDD296D677300C952C3 /* NCContextMenu.swift */; };
 		F78E2D6529AF02DB0024D4F3 /* Database.swift in Sources */ = {isa = PBXBuildFile; fileRef = F78E2D6429AF02DB0024D4F3 /* Database.swift */; };
 		F78E2D6629AF02DB0024D4F3 /* Database.swift in Sources */ = {isa = PBXBuildFile; fileRef = F78E2D6429AF02DB0024D4F3 /* Database.swift */; };
@@ -670,6 +679,7 @@
 		F7BB7E4727A18C56009B9F29 /* Parchment in Frameworks */ = {isa = PBXBuildFile; productRef = F7BB7E4627A18C56009B9F29 /* Parchment */; };
 		F7BC287E26663F6C004D46C5 /* NCViewCertificateDetails.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = F7BC287D26663F6C004D46C5 /* NCViewCertificateDetails.storyboard */; };
 		F7BC288026663F85004D46C5 /* NCViewCertificateDetails.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7BC287F26663F85004D46C5 /* NCViewCertificateDetails.swift */; };
+		F7BD50312B65216300D5AEF9 /* NCMediaGridLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7BD50302B65216300D5AEF9 /* NCMediaGridLayout.swift */; };
 		F7BD71E62636EAFC00643C34 /* NCNetworkingE2EE.swift in Sources */ = {isa = PBXBuildFile; fileRef = F785EE9C246196DF00B3F945 /* NCNetworkingE2EE.swift */; };
 		F7BF9D822934CA21009EE9A6 /* NCManageDatabase+LayoutForView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7BF9D812934CA21009EE9A6 /* NCManageDatabase+LayoutForView.swift */; };
 		F7BF9D832934CA21009EE9A6 /* NCManageDatabase+LayoutForView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7BF9D812934CA21009EE9A6 /* NCManageDatabase+LayoutForView.swift */; };
@@ -694,6 +704,13 @@
 		F7C9739228F17131002C43E2 /* Intents.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F7C9739128F17131002C43E2 /* Intents.framework */; };
 		F7C9739528F17131002C43E2 /* IntentHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7C9739428F17131002C43E2 /* IntentHandler.swift */; };
 		F7C9739928F17131002C43E2 /* WidgetDashboardIntentHandler.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = F7C9739028F17131002C43E2 /* WidgetDashboardIntentHandler.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
+		F7C9B91D2B582F550064EA91 /* NCManageDatabase+SecurityGuard.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7C9B91C2B582F550064EA91 /* NCManageDatabase+SecurityGuard.swift */; };
+		F7C9B91E2B582F550064EA91 /* NCManageDatabase+SecurityGuard.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7C9B91C2B582F550064EA91 /* NCManageDatabase+SecurityGuard.swift */; };
+		F7C9B91F2B582F550064EA91 /* NCManageDatabase+SecurityGuard.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7C9B91C2B582F550064EA91 /* NCManageDatabase+SecurityGuard.swift */; };
+		F7C9B9202B582F550064EA91 /* NCManageDatabase+SecurityGuard.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7C9B91C2B582F550064EA91 /* NCManageDatabase+SecurityGuard.swift */; };
+		F7C9B9212B582F550064EA91 /* NCManageDatabase+SecurityGuard.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7C9B91C2B582F550064EA91 /* NCManageDatabase+SecurityGuard.swift */; };
+		F7C9B9222B582F550064EA91 /* NCManageDatabase+SecurityGuard.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7C9B91C2B582F550064EA91 /* NCManageDatabase+SecurityGuard.swift */; };
+		F7C9B9232B582F550064EA91 /* NCManageDatabase+SecurityGuard.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7C9B91C2B582F550064EA91 /* NCManageDatabase+SecurityGuard.swift */; };
 		F7CA212D25F1333300826ABB /* NCAccountRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7CA212B25F1333200826ABB /* NCAccountRequest.swift */; };
 		F7CA212E25F1333300826ABB /* NCAccountRequest.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = F7CA212C25F1333200826ABB /* NCAccountRequest.storyboard */; };
 		F7CB689A2541676B0050EC94 /* NCMore.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = F7CB68992541676B0050EC94 /* NCMore.storyboard */; };
@@ -1038,6 +1055,7 @@
 		F719D9DF288D37A300762E33 /* NCColorPicker.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = NCColorPicker.storyboard; sourceTree = "<group>"; };
 		F719D9E1288D396100762E33 /* NCColorPicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCColorPicker.swift; sourceTree = "<group>"; };
 		F71CD6C92930D7B1006C95C1 /* NCApplicationHandle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCApplicationHandle.swift; sourceTree = "<group>"; };
+		F71F6D062B6A6A5E00F1EB15 /* ThreadSafeArray.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreadSafeArray.swift; sourceTree = "<group>"; };
 		F7226EDB1EE4089300EBECB1 /* Main.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = Main.storyboard; sourceTree = "<group>"; };
 		F723985B253C95CE00257F49 /* NCViewerRichdocument.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = NCViewerRichdocument.storyboard; sourceTree = "<group>"; };
 		F7239870253D86B600257F49 /* NCEmptyDataSet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCEmptyDataSet.swift; sourceTree = "<group>"; };
@@ -1116,7 +1134,6 @@
 		F757CC8A29E82D0500F31428 /* NCGroupfolders.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = NCGroupfolders.storyboard; sourceTree = "<group>"; };
 		F757CC8B29E82D0500F31428 /* NCGroupfolders.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NCGroupfolders.swift; sourceTree = "<group>"; };
 		F7581D1925EFDA60004DC699 /* NCLoginWeb+Menu.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NCLoginWeb+Menu.swift"; sourceTree = "<group>"; };
-		F7581D2325EFDDDF004DC699 /* NCMedia+Menu.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NCMedia+Menu.swift"; sourceTree = "<group>"; };
 		F758B457212C564000515F55 /* NCScan.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = NCScan.storyboard; sourceTree = "<group>"; };
 		F758B45D212C569C00515F55 /* NCScanCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NCScanCell.swift; sourceTree = "<group>"; };
 		F758B45F212C56A400515F55 /* NCScan.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NCScan.swift; sourceTree = "<group>"; };
@@ -1233,6 +1250,9 @@
 		F78ACD4921903F850088454D /* NCTrashListCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = NCTrashListCell.xib; sourceTree = "<group>"; };
 		F78ACD51219046DC0088454D /* NCSectionHeaderMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCSectionHeaderMenu.swift; sourceTree = "<group>"; };
 		F78ACD53219047D40088454D /* NCSectionFooter.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = NCSectionFooter.xib; sourceTree = "<group>"; };
+		F78B87E62B62527100C65ADC /* NCMediaDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCMediaDataSource.swift; sourceTree = "<group>"; };
+		F78B87E82B62550800C65ADC /* NCMediaDownloadThumbnaill.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCMediaDownloadThumbnaill.swift; sourceTree = "<group>"; };
+		F78B87EA2B627AA100C65ADC /* NCMediaCommandView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCMediaCommandView.swift; sourceTree = "<group>"; };
 		F78C6FDD296D677300C952C3 /* NCContextMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCContextMenu.swift; sourceTree = "<group>"; };
 		F78D6F461F0B7CB9002F9619 /* es-MX */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "es-MX"; path = "es-MX.lproj/Localizable.strings"; sourceTree = "<group>"; };
 		F78D6F4D1F0B7CE4002F9619 /* nb-NO */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "nb-NO"; path = "nb-NO.lproj/Localizable.strings"; sourceTree = "<group>"; };
@@ -1331,6 +1351,7 @@
 		F7BB04851FD58ACB00BBFD2A /* cs-CZ */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "cs-CZ"; path = "cs-CZ.lproj/Localizable.strings"; sourceTree = "<group>"; };
 		F7BC287D26663F6C004D46C5 /* NCViewCertificateDetails.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = NCViewCertificateDetails.storyboard; sourceTree = "<group>"; };
 		F7BC287F26663F85004D46C5 /* NCViewCertificateDetails.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCViewCertificateDetails.swift; sourceTree = "<group>"; };
+		F7BD50302B65216300D5AEF9 /* NCMediaGridLayout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCMediaGridLayout.swift; sourceTree = "<group>"; };
 		F7BE7C25290AC8C9002ABB61 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Intent.strings; sourceTree = "<group>"; };
 		F7BE7C27290ADEFD002ABB61 /* eu */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = eu; path = eu.lproj/Intent.strings; sourceTree = "<group>"; };
 		F7BE7C29290ADEFD002ABB61 /* ca */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ca; path = ca.lproj/Intent.strings; sourceTree = "<group>"; };
@@ -1398,6 +1419,7 @@
 		F7C9739028F17131002C43E2 /* WidgetDashboardIntentHandler.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = WidgetDashboardIntentHandler.appex; sourceTree = BUILT_PRODUCTS_DIR; };
 		F7C9739128F17131002C43E2 /* Intents.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Intents.framework; path = System/Library/Frameworks/Intents.framework; sourceTree = SDKROOT; };
 		F7C9739428F17131002C43E2 /* IntentHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IntentHandler.swift; sourceTree = "<group>"; };
+		F7C9B91C2B582F550064EA91 /* NCManageDatabase+SecurityGuard.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NCManageDatabase+SecurityGuard.swift"; sourceTree = "<group>"; };
 		F7CA212B25F1333200826ABB /* NCAccountRequest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NCAccountRequest.swift; sourceTree = "<group>"; };
 		F7CA212C25F1333200826ABB /* NCAccountRequest.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = NCAccountRequest.storyboard; sourceTree = "<group>"; };
 		F7CB68992541676B0050EC94 /* NCMore.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = NCMore.storyboard; sourceTree = "<group>"; };
@@ -1627,7 +1649,6 @@
 				8491B1CC273BBA82001C8C5B /* UIViewController+Menu.swift */,
 				F77A697C250A0FBC00FF1708 /* NCCollectionViewCommon+Menu.swift */,
 				F7581D1925EFDA60004DC699 /* NCLoginWeb+Menu.swift */,
-				F7581D2325EFDDDF004DC699 /* NCMedia+Menu.swift */,
 				F7CBC31B24F78E79004D3812 /* NCSortMenu.swift */,
 				F75D19E225EFE09000D74598 /* NCTrash+Menu.swift */,
 				AF93471127E2341B002537EE /* NCShare+Menu.swift */,
@@ -2295,6 +2316,7 @@
 				F7864ACB2A78FE73004870E0 /* NCManageDatabase+LocalFile.swift */,
 				AF4BF61827562A4B0081CEEF /* NCManageDatabase+Metadata.swift */,
 				F73EF7C62B0225610087E6E9 /* NCManageDatabase+PhotoLibrary.swift */,
+				F7C9B91C2B582F550064EA91 /* NCManageDatabase+SecurityGuard.swift */,
 				F749B649297B0CBB00087535 /* NCManageDatabase+Share.swift */,
 				F73EF7CE2B0225BA0087E6E9 /* NCManageDatabase+Tag.swift */,
 				F73EF7D62B0226080087E6E9 /* NCManageDatabase+Tip.swift */,
@@ -2339,6 +2361,7 @@
 				F74AF3A3247FB6AE00AC767B /* NCUtilityFileSystem.swift */,
 				F702F2FC25EE5D2C008F8E80 /* NYMnemonic */,
 				AF36077027BFA4E8001A243D /* ParallelWorker.swift */,
+				F71F6D062B6A6A5E00F1EB15 /* ThreadSafeArray.swift */,
 				F7245923289BB50B00474787 /* ThreadSafeDictionary.swift */,
 			);
 			path = Utility;
@@ -2471,7 +2494,11 @@
 				F720B5B72507B9A5008C94E5 /* Cell */,
 				F7501C302212E57400FB1415 /* NCMedia.storyboard */,
 				F7501C312212E57400FB1415 /* NCMedia.swift */,
+				F78B87EA2B627AA100C65ADC /* NCMediaCommandView.swift */,
 				F7F1E54B2492369A00E42386 /* NCMediaCommandView.xib */,
+				F78B87E62B62527100C65ADC /* NCMediaDataSource.swift */,
+				F78B87E82B62550800C65ADC /* NCMediaDownloadThumbnaill.swift */,
+				F7BD50302B65216300D5AEF9 /* NCMediaGridLayout.swift */,
 			);
 			path = Media;
 			sourceTree = "<group>";
@@ -3439,6 +3466,7 @@
 				F749B64F297B0CBB00087535 /* NCManageDatabase+Share.swift in Sources */,
 				F359D86D2A7D03420023F405 /* NCUtility+Exif.swift in Sources */,
 				F73EF7AD2B0223900087E6E9 /* NCManageDatabase+Comments.swift in Sources */,
+				F7C9B9232B582F550064EA91 /* NCManageDatabase+SecurityGuard.swift in Sources */,
 				F73EF7CD2B0225610087E6E9 /* NCManageDatabase+PhotoLibrary.swift in Sources */,
 				D575039F27146F93008DC9DC /* String+Extension.swift in Sources */,
 				F769CA1A2966EA3C00039397 /* ComponentView.swift in Sources */,
@@ -3456,6 +3484,7 @@
 				F343A4B92A1E084400DDA874 /* PHAsset+Extension.swift in Sources */,
 				F73EF7E52B02266D0087E6E9 /* NCManageDatabase+Trash.swift in Sources */,
 				F73EF7D52B0225BA0087E6E9 /* NCManageDatabase+Tag.swift in Sources */,
+				F71F6D0D2B6A6A5E00F1EB15 /* ThreadSafeArray.swift in Sources */,
 				F763D2A32A249C4500A3C901 /* NCManageDatabase+Capabilities.swift in Sources */,
 				F749B656297B0F2400087535 /* NCManageDatabase+Avatar.swift in Sources */,
 				F782FDC424E6933900666099 /* NCUtility.swift in Sources */,
@@ -3518,11 +3547,13 @@
 				F7183BD82AEBDCD8000CD020 /* NCKeychain.swift in Sources */,
 				F7490E8129882C79009DCE94 /* NCManageDatabase+DashboardWidget.swift in Sources */,
 				F73EF7D42B0225BA0087E6E9 /* NCManageDatabase+Tag.swift in Sources */,
+				F71F6D0C2B6A6A5E00F1EB15 /* ThreadSafeArray.swift in Sources */,
 				F7490E8629882C99009DCE94 /* NCUtilityFileSystem.swift in Sources */,
 				F763D2A22A249C4500A3C901 /* NCManageDatabase+Capabilities.swift in Sources */,
 				F73EF7E42B02266D0087E6E9 /* NCManageDatabase+Trash.swift in Sources */,
 				F343A4C02A1E734600DDA874 /* Optional+Extension.swift in Sources */,
 				F711A4E12AF92CAE00095DD8 /* NCUtility+Date.swift in Sources */,
+				F7C9B9222B582F550064EA91 /* NCManageDatabase+SecurityGuard.swift in Sources */,
 				F7490E8529882C8C009DCE94 /* NCManageDatabase+Video.swift in Sources */,
 				F7490E8C29882D02009DCE94 /* CCUtility.m in Sources */,
 				F7490E7729882C10009DCE94 /* UIColor+Extension.swift in Sources */,
@@ -3555,6 +3586,7 @@
 			buildActionMask = 2147483647;
 			files = (
 				F7864ACF2A78FE73004870E0 /* NCManageDatabase+LocalFile.swift in Sources */,
+				F71F6D0A2B6A6A5E00F1EB15 /* ThreadSafeArray.swift in Sources */,
 				F7245925289BB59100474787 /* ThreadSafeDictionary.swift in Sources */,
 				F7EDE4E5262D7BBE00414FE6 /* NCSectionHeaderMenu.swift in Sources */,
 				F7BF9D852934CA21009EE9A6 /* NCManageDatabase+LayoutForView.swift in Sources */,
@@ -3585,6 +3617,7 @@
 				AF4BF615275629E20081CEEF /* NCManageDatabase+Account.swift in Sources */,
 				F798F0E225880608000DAFFD /* UIColor+Extension.swift in Sources */,
 				AF3FDCC32796F3FB00710F60 /* NCTrashListCell+NCTrashCellProtocol.swift in Sources */,
+				F7C9B9202B582F550064EA91 /* NCManageDatabase+SecurityGuard.swift in Sources */,
 				AF817EF2274BC781009ED85B /* NCUserBaseUrl.swift in Sources */,
 				F78E2D6829AF02DB0024D4F3 /* Database.swift in Sources */,
 				F711A4DF2AF92CAE00095DD8 /* NCUtility+Date.swift in Sources */,
@@ -3662,6 +3695,7 @@
 				F711D63128F44801003F43C8 /* IntentHandler.swift in Sources */,
 				F76DEE9728F808AF0041B1C9 /* LockscreenData.swift in Sources */,
 				F72EA95A28B7BD0D00C88F0C /* FilesWidgetView.swift in Sources */,
+				F71F6D082B6A6A5E00F1EB15 /* ThreadSafeArray.swift in Sources */,
 				F78302FE28B4C44700B84583 /* NCBrand.swift in Sources */,
 				F749B64B297B0CBB00087535 /* NCManageDatabase+Share.swift in Sources */,
 				F7075B682AE15F8100512300 /* NCCellProtocol.swift in Sources */,
@@ -3705,6 +3739,7 @@
 				F73EF7E02B02266D0087E6E9 /* NCManageDatabase+Trash.swift in Sources */,
 				F73EF7E82B0226B90087E6E9 /* NCManageDatabase+UserStatus.swift in Sources */,
 				F73EF7D82B0226080087E6E9 /* NCManageDatabase+Tip.swift in Sources */,
+				F7C9B91E2B582F550064EA91 /* NCManageDatabase+SecurityGuard.swift in Sources */,
 				F72FD3B6297ED49A00075D28 /* NCManageDatabase+E2EE.swift in Sources */,
 				F783030128B4C49700B84583 /* UIImage+Extension.swift in Sources */,
 				F72EA95428B7BABA00C88F0C /* FilesWidgetProvider.swift in Sources */,
@@ -3738,6 +3773,7 @@
 				F7D68FCF28CB9051009139F3 /* NCManageDatabase+DashboardWidget.swift in Sources */,
 				F73EF7CB2B0225610087E6E9 /* NCManageDatabase+PhotoLibrary.swift in Sources */,
 				F73EF7C32B02250B0087E6E9 /* NCManageDatabase+GPS.swift in Sources */,
+				F7C9B9212B582F550064EA91 /* NCManageDatabase+SecurityGuard.swift in Sources */,
 				F359D86B2A7D03420023F405 /* NCUtility+Exif.swift in Sources */,
 				F7864AD02A78FE73004870E0 /* NCManageDatabase+LocalFile.swift in Sources */,
 				F7183BD72AEBDCD8000CD020 /* NCKeychain.swift in Sources */,
@@ -3763,6 +3799,7 @@
 				F72429372AFE39980040AEF3 /* NCLivePhoto.swift in Sources */,
 				F7A76DCD256A71CE00119AB3 /* UIImage+Extension.swift in Sources */,
 				F73EF7DB2B0226080087E6E9 /* NCManageDatabase+Tip.swift in Sources */,
+				F71F6D0B2B6A6A5E00F1EB15 /* ThreadSafeArray.swift in Sources */,
 				F73EF7EB2B0226B90087E6E9 /* NCManageDatabase+UserStatus.swift in Sources */,
 				F771E3F820E239B500AFB62D /* FileProviderExtension+Thumbnail.swift in Sources */,
 				F343A4BF2A1E734600DDA874 /* Optional+Extension.swift in Sources */,
@@ -3819,6 +3856,7 @@
 				F78A18B823CDE2B300F681F3 /* NCViewerRichWorkspace.swift in Sources */,
 				F7A60F86292D215000FCE1F2 /* NCShareAccounts.swift in Sources */,
 				F77910AB25DD53C700CEDB9E /* NCSettingsBundleHelper.swift in Sources */,
+				F78B87EB2B627AA100C65ADC /* NCMediaCommandView.swift in Sources */,
 				F73EF7B72B0224AB0087E6E9 /* NCManageDatabase+ExternalSites.swift in Sources */,
 				AF4BF61927562A4B0081CEEF /* NCManageDatabase+Metadata.swift in Sources */,
 				F73EF7E72B0226B90087E6E9 /* NCManageDatabase+UserStatus.swift in Sources */,
@@ -3872,6 +3910,7 @@
 				AFCE353327E4ED1900FEA6C2 /* UIToolbar+Extension.swift in Sources */,
 				8491B1CD273BBA82001C8C5B /* UIViewController+Menu.swift in Sources */,
 				F73EF7BF2B02250B0087E6E9 /* NCManageDatabase+GPS.swift in Sources */,
+				F71F6D072B6A6A5E00F1EB15 /* ThreadSafeArray.swift in Sources */,
 				F761856C29E98543006EB3B0 /* NCIntroCollectionViewCell.swift in Sources */,
 				F75DD765290ABB25002EB562 /* Intent.intentdefinition in Sources */,
 				F74B6D952A7E239A00F03C5F /* NCManageDatabase+Chunk.swift in Sources */,
@@ -3888,6 +3927,7 @@
 				F72944F22A84246400246839 /* NCEndToEndMetadataV20.swift in Sources */,
 				F70BFC7420E0FA7D00C67599 /* NCUtility.swift in Sources */,
 				F79EDAA526B004980007D134 /* NCPlayer.swift in Sources */,
+				F7BD50312B65216300D5AEF9 /* NCMediaGridLayout.swift in Sources */,
 				F7C1EEA525053A9C00866ACC /* NCDataSource.swift in Sources */,
 				F713FF002472764100214AF6 /* UIImage+animatedGIF.m in Sources */,
 				AFCE353527E4ED5900FEA6C2 /* DateFormatter+Extension.swift in Sources */,
@@ -3902,10 +3942,10 @@
 				F765F73125237E3F00391DBE /* NCRecent.swift in Sources */,
 				F76B3CCE1EAE01BD00921AC9 /* NCBrand.swift in Sources */,
 				F359D8672A7D03420023F405 /* NCUtility+Exif.swift in Sources */,
-				F7581D2425EFDDDF004DC699 /* NCMedia+Menu.swift in Sources */,
 				F738D4902756740100CD1D38 /* NCLoginNavigationController.swift in Sources */,
 				F77B0E981D118A16002130FE /* CCManageAccount.m in Sources */,
 				F769CA192966EA3C00039397 /* ComponentView.swift in Sources */,
+				F7C9B91D2B582F550064EA91 /* NCManageDatabase+SecurityGuard.swift in Sources */,
 				AF93474C27E34120002537EE /* NCUtility+Image.swift in Sources */,
 				F702F30125EE5D2C008F8E80 /* NYMnemonic.m in Sources */,
 				AF93474E27E3F212002537EE /* NCShareNewUserAddComment.swift in Sources */,
@@ -3947,6 +3987,7 @@
 				F70968A424212C4E00ED60E5 /* NCLivePhoto.swift in Sources */,
 				F7C30DFA291BCF790017149B /* NCNetworkingE2EECreateFolder.swift in Sources */,
 				F7BC288026663F85004D46C5 /* NCViewCertificateDetails.swift in Sources */,
+				F78B87E92B62550800C65ADC /* NCMediaDownloadThumbnaill.swift in Sources */,
 				F73EF7AF2B0224350087E6E9 /* NCManageDatabase+DirectEditing.swift in Sources */,
 				F702F2E625EE5C86008F8E80 /* NCAudioRecorderViewController.swift in Sources */,
 				D5B6AA7827200C7200D49C24 /* NCActivityTableViewCell.swift in Sources */,
@@ -3972,6 +4013,7 @@
 				F7A7FA6329265CF4000603EF /* NCManageE2EE.swift in Sources */,
 				F7C7B489245EBA4100D93E60 /* NCViewerQuickLook.swift in Sources */,
 				F758B45E212C569D00515F55 /* NCScanCell.swift in Sources */,
+				F78B87E72B62527100C65ADC /* NCMediaDataSource.swift in Sources */,
 				F7581D1A25EFDA61004DC699 /* NCLoginWeb+Menu.swift in Sources */,
 				F7864ACC2A78FE73004870E0 /* NCManageDatabase+LocalFile.swift in Sources */,
 				F7F4F11027ECDC4A008676F9 /* UIDevice+Extension.swift in Sources */,
@@ -4002,6 +4044,7 @@
 				F7C9739528F17131002C43E2 /* IntentHandler.swift in Sources */,
 				F7A8D73D28F181D3008BBE1C /* NCUtilityFileSystem.swift in Sources */,
 				F73EF7E12B02266D0087E6E9 /* NCManageDatabase+Trash.swift in Sources */,
+				F7C9B91F2B582F550064EA91 /* NCManageDatabase+SecurityGuard.swift in Sources */,
 				F7A8D74528F1828E008BBE1C /* CCUtility.m in Sources */,
 				F75DD767290ABB25002EB562 /* Intent.intentdefinition in Sources */,
 				F749B64C297B0CBB00087535 /* NCManageDatabase+Share.swift in Sources */,
@@ -4034,6 +4077,7 @@
 				F73EF7D12B0225BA0087E6E9 /* NCManageDatabase+Tag.swift in Sources */,
 				F73EF7C92B0225610087E6E9 /* NCManageDatabase+PhotoLibrary.swift in Sources */,
 				F73EF7B92B0224AB0087E6E9 /* NCManageDatabase+ExternalSites.swift in Sources */,
+				F71F6D092B6A6A5E00F1EB15 /* ThreadSafeArray.swift in Sources */,
 				F757CC8429E7F88B00F31428 /* NCManageDatabase+Groupfolders.swift in Sources */,
 				F78E2D6729AF02DB0024D4F3 /* Database.swift in Sources */,
 				F7A8D73628F17E1A008BBE1C /* NCManageDatabase+Activity.swift in Sources */,
@@ -4308,7 +4352,7 @@
 				"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
 				ENABLE_HARDENED_RUNTIME = YES;
 				GENERATE_INFOPLIST_FILE = YES;
-				IPHONEOS_DEPLOYMENT_TARGET = 16.2;
+				IPHONEOS_DEPLOYMENT_TARGET = 15.0;
 				OTHER_LDFLAGS = "";
 				PRODUCT_BUNDLE_IDENTIFIER = it.twsweb.NextcloudTests;
 				PRODUCT_NAME = "$(TARGET_NAME)";
@@ -4325,7 +4369,7 @@
 				"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
 				ENABLE_HARDENED_RUNTIME = YES;
 				GENERATE_INFOPLIST_FILE = YES;
-				IPHONEOS_DEPLOYMENT_TARGET = 16.2;
+				IPHONEOS_DEPLOYMENT_TARGET = 15.0;
 				OTHER_LDFLAGS = "";
 				PRODUCT_BUNDLE_IDENTIFIER = it.twsweb.NextcloudTests;
 				PRODUCT_NAME = "$(TARGET_NAME)";
@@ -4360,7 +4404,7 @@
 				GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
 				GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
 				GENERATE_INFOPLIST_FILE = YES;
-				IPHONEOS_DEPLOYMENT_TARGET = 16.2;
+				IPHONEOS_DEPLOYMENT_TARGET = 15.0;
 				MARKETING_VERSION = 1.0;
 				MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
 				MTL_FAST_MATH = YES;
@@ -4398,7 +4442,7 @@
 				GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
 				GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
 				GENERATE_INFOPLIST_FILE = YES;
-				IPHONEOS_DEPLOYMENT_TARGET = 16.2;
+				IPHONEOS_DEPLOYMENT_TARGET = 15.0;
 				MARKETING_VERSION = 1.0;
 				MTL_ENABLE_DEBUG_INFO = NO;
 				MTL_FAST_MATH = YES;
@@ -4441,7 +4485,7 @@
 				GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
 				GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
 				GENERATE_INFOPLIST_FILE = YES;
-				IPHONEOS_DEPLOYMENT_TARGET = 16.2;
+				IPHONEOS_DEPLOYMENT_TARGET = 15.0;
 				MARKETING_VERSION = 1.0;
 				MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
 				MTL_FAST_MATH = YES;
@@ -4480,7 +4524,7 @@
 				GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
 				GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
 				GENERATE_INFOPLIST_FILE = YES;
-				IPHONEOS_DEPLOYMENT_TARGET = 16.2;
+				IPHONEOS_DEPLOYMENT_TARGET = 15.0;
 				MARKETING_VERSION = 1.0;
 				MTL_ENABLE_DEBUG_INFO = NO;
 				MTL_FAST_MATH = YES;
@@ -4522,7 +4566,7 @@
 				GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
 				GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
 				GENERATE_INFOPLIST_FILE = YES;
-				IPHONEOS_DEPLOYMENT_TARGET = 16.2;
+				IPHONEOS_DEPLOYMENT_TARGET = 15.0;
 				MARKETING_VERSION = 1.0;
 				MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
 				MTL_FAST_MATH = YES;
@@ -4561,7 +4605,7 @@
 				GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
 				GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
 				GENERATE_INFOPLIST_FILE = YES;
-				IPHONEOS_DEPLOYMENT_TARGET = 16.2;
+				IPHONEOS_DEPLOYMENT_TARGET = 15.0;
 				MARKETING_VERSION = 1.0;
 				MTL_ENABLE_DEBUG_INFO = NO;
 				MTL_FAST_MATH = YES;
@@ -4873,7 +4917,7 @@
 				CLANG_WARN_UNREACHABLE_CODE = YES;
 				CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
 				COPY_PHASE_STRIP = NO;
-				CURRENT_PROJECT_VERSION = 1;
+				CURRENT_PROJECT_VERSION = 4;
 				DEBUG_INFORMATION_FORMAT = dwarf;
 				DEVELOPMENT_TEAM = NKUJUXUJ3B;
 				ENABLE_STRICT_OBJC_MSGSEND = YES;
@@ -4899,7 +4943,7 @@
 					"@executable_path/Frameworks",
 					"@executable_path/../../Frameworks",
 				);
-				MARKETING_VERSION = 4.9.6;
+				MARKETING_VERSION = 5.0.0;
 				ONLY_ACTIVE_ARCH = YES;
 				OTHER_LDFLAGS = "";
 				SDKROOT = iphoneos;
@@ -4938,7 +4982,7 @@
 				CLANG_WARN_UNREACHABLE_CODE = YES;
 				CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
 				COPY_PHASE_STRIP = NO;
-				CURRENT_PROJECT_VERSION = 1;
+				CURRENT_PROJECT_VERSION = 4;
 				DEVELOPMENT_TEAM = NKUJUXUJ3B;
 				ENABLE_STRICT_OBJC_MSGSEND = YES;
 				ENABLE_TESTABILITY = YES;
@@ -4961,7 +5005,7 @@
 					"@executable_path/Frameworks",
 					"@executable_path/../../Frameworks",
 				);
-				MARKETING_VERSION = 4.9.6;
+				MARKETING_VERSION = 5.0.0;
 				ONLY_ACTIVE_ARCH = YES;
 				OTHER_LDFLAGS = "";
 				SDKROOT = iphoneos;
@@ -5253,7 +5297,7 @@
 			repositoryURL = "https://github.com/nextcloud/NextcloudKit";
 			requirement = {
 				kind = exactVersion;
-				version = 2.9.4;
+				version = 2.9.5;
 			};
 		};
 		F788ECC5263AAAF900ADC67F /* XCRemoteSwiftPackageReference "MarkdownKit" */ = {

+ 1 - 0
Widget/Files/FilesData.swift

@@ -213,6 +213,7 @@ func getFilesDataEntry(configuration: AccountIntent?, isPreview: Bool, displaySi
             var datas: [FilesData] = []
             var imageRecent = UIImage(named: "file")!
             let title = getTitleFilesWidget(account: account)
+            let files = files.sorted(by: { ($0.date as Date) > ($1.date as Date) })
 
             for file in files {
 

+ 1 - 1
iOSClient/Activity/NCActivity.swift

@@ -100,7 +100,7 @@ class NCActivity: UIViewController, NCSharePagingContent {
 
         appDelegate.activeViewController = self
 
-        navigationController?.setFileAppreance()
+        navigationController?.setNavigationBarAppearance()
 
         fetchAll(isInitial: true)
     }

+ 7 - 19
iOSClient/AppDelegate.swift

@@ -156,13 +156,10 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
             self.handleProcessingTask(task)
         }
 
-        // Intro
-        if NCBrandOptions.shared.disable_intro {
-            if account.isEmpty {
+        if account.isEmpty {
+            if NCBrandOptions.shared.disable_intro {
                 openLogin(viewController: nil, selector: NCGlobal.shared.introLogin, openLoginWeb: false)
-            }
-        } else {
-            if !NCKeychain().intro {
+            } else {
                 if let viewController = UIStoryboard(name: "NCIntro", bundle: nil).instantiateInitialViewController() {
                     let navigationController = NCLoginNavigationController(rootViewController: viewController)
                     window?.rootViewController = navigationController
@@ -272,6 +269,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
 
         scheduleAppRefresh()
         scheduleAppProcessing()
+        NCNetworking.shared.cancelAllQueue()
         NCNetworking.shared.cancelDataTask()
         NCNetworking.shared.cancelDownloadTasks()
         presentPasscode { }
@@ -579,6 +577,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
 
     @objc func changeAccount(_ account: String, userProfile: NKUserProfile?) {
 
+        NCNetworking.shared.cancelAllQueue()
         NCNetworking.shared.cancelDataTask()
         NCNetworking.shared.cancelDownloadTasks()
         NCNetworking.shared.cancelUploadTasks()
@@ -882,10 +881,11 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
 
         let utilityFileSystem = NCUtilityFileSystem()
 
+        NCNetworking.shared.cancelAllQueue()
         NCNetworking.shared.cancelDataTask()
         NCNetworking.shared.cancelDownloadTasks()
         NCNetworking.shared.cancelUploadTasks()
-        NCNetworking.shared.cancelUploadBackgroundTask()
+        NCNetworking.shared.cancelUploadBackgroundTask(withNotification: false)
 
         URLCache.shared.memoryCapacity = 0
         URLCache.shared.diskCapacity = 0
@@ -928,18 +928,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
         }
     }
 
-    // MARK: - Queue
-
-    @objc func cancelAllQueue() {
-        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
 
     func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool {

+ 3 - 34
iOSClient/Data/NCDataSource.swift

@@ -137,7 +137,7 @@ class NCDataSource: NSObject {
 
         } else {
 
-        // normal
+            // normal
             let directory = NSLocalizedString("directory", comment: "").lowercased().firstUppercased
             self.sectionsValue = self.sectionsValue.sorted {
                 if directoryOnTop && $0 == directory {
@@ -191,10 +191,9 @@ class NCDataSource: NSObject {
 
     // MARK: -
 
-    @discardableResult
-    func appendMetadatasToSection(_ metadatas: [tableMetadata], metadataForSection: NCMetadataForSection, lastSearchResult: NKSearchResult) -> [IndexPath] {
+    func appendMetadatasToSection(_ metadatas: [tableMetadata], metadataForSection: NCMetadataForSection, lastSearchResult: NKSearchResult) {
 
-        guard let sectionIndex = getSectionIndex(metadataForSection.sectionValue) else { return [] }
+        guard let sectionIndex = getSectionIndex(metadataForSection.sectionValue) else { return }
         var indexPaths: [IndexPath] = []
 
         self.metadatas.append(contentsOf: metadatas)
@@ -207,36 +206,6 @@ class NCDataSource: NSObject {
                 indexPaths.append(IndexPath(row: rowIndex, section: sectionIndex))
             }
         }
-
-        return indexPaths
-    }
-
-    @discardableResult
-    func reloadMetadata(ocId: String, ocIdTemp: String? = nil) -> (indexPath: IndexPath?, sameSections: Bool) {
-
-        let numberOfSections = self.numberOfSections()
-        var ocIdSearch = ocId
-
-        guard let metadata = NCManageDatabase.shared.getMetadataFromOcId(ocId) else { return (nil, self.isSameNumbersOfSections(numberOfSections: numberOfSections)) }
-
-        if let ocIdTemp = ocIdTemp {
-            ocIdSearch = ocIdTemp
-        }
-
-        // UPDATE metadataForSection (IMPORTANT FIRST)
-        let (indexPath, metadataForSection) = self.getIndexPathMetadata(ocId: ocIdSearch)
-        if let indexPath = indexPath, let metadataForSection = metadataForSection {
-            metadataForSection.metadatas[indexPath.row] = metadata
-            metadataForSection.createMetadatas()
-        }
-
-        // UPDATE metadatasSource (IMPORTANT LAST)
-        if let rowIndex = self.metadatas.firstIndex(where: {$0.ocId == ocIdSearch}) {
-            self.metadatas[rowIndex] = metadata
-        }
-
-        let result = self.getIndexPathMetadata(ocId: ocId)
-        return (result.indexPath, self.isSameNumbersOfSections(numberOfSections: numberOfSections))
     }
 
     // MARK: -

+ 51 - 41
iOSClient/Data/NCManageDatabase+Capabilities.swift

@@ -102,6 +102,7 @@ extension NCManageDatabase {
                         let userstatus: UserStatus?
                         let external: External?
                         let groupfolders: GroupFolders?
+                        let securityguard: SecurityGuard?
 
                         enum CodingKeys: String, CodingKey {
                             case filessharing = "files_sharing"
@@ -110,6 +111,7 @@ extension NCManageDatabase {
                             case richdocuments, activity, notifications, files
                             case userstatus = "user_status"
                             case external, groupfolders
+                            case securityguard = "security_guard"
                         }
 
                         struct FilesSharing: Codable {
@@ -263,6 +265,10 @@ extension NCManageDatabase {
                         struct GroupFolders: Codable {
                             let hasGroupFolders: Bool?
                         }
+
+                        struct SecurityGuard: Codable {
+                            let diagnostics: Bool?
+                        }
                     }
                 }
             }
@@ -288,65 +294,69 @@ extension NCManageDatabase {
         guard let jsonData = jsonData else { return }
 
         do {
+            var global = NCGlobal.shared
             let json = try JSONDecoder().decode(CapabilityNextcloud.self, from: jsonData)
-            NCGlobal.shared.capabilityServerVersion = json.ocs.data.version.string
-            NCGlobal.shared.capabilityServerVersionMajor = json.ocs.data.version.major
+            var data = json.ocs.data
+
+            global.capabilityServerVersion = data.version.string
+            global.capabilityServerVersionMajor = data.version.major
 
-            if NCGlobal.shared.capabilityServerVersionMajor > 0 {
-                NextcloudKit.shared.setup(nextcloudVersion: NCGlobal.shared.capabilityServerVersionMajor)
+            if global.capabilityServerVersionMajor > 0 {
+                NextcloudKit.shared.setup(nextcloudVersion: global.capabilityServerVersionMajor)
             }
 
-            NCGlobal.shared.capabilityFileSharingApiEnabled = json.ocs.data.capabilities.filessharing?.apienabled ?? false
-            NCGlobal.shared.capabilityFileSharingDefaultPermission = json.ocs.data.capabilities.filessharing?.defaultpermissions ?? 0
-            NCGlobal.shared.capabilityFileSharingPubPasswdEnforced = json.ocs.data.capabilities.filessharing?.ncpublic?.password?.enforced ?? false
-            NCGlobal.shared.capabilityFileSharingPubExpireDateEnforced = json.ocs.data.capabilities.filessharing?.ncpublic?.expiredate?.enforced ?? false
-            NCGlobal.shared.capabilityFileSharingPubExpireDateDays = json.ocs.data.capabilities.filessharing?.ncpublic?.expiredate?.days ?? 0
-            NCGlobal.shared.capabilityFileSharingInternalExpireDateEnforced = json.ocs.data.capabilities.filessharing?.ncpublic?.expiredateinternal?.enforced ?? false
-            NCGlobal.shared.capabilityFileSharingInternalExpireDateDays = json.ocs.data.capabilities.filessharing?.ncpublic?.expiredateinternal?.days ?? 0
-            NCGlobal.shared.capabilityFileSharingRemoteExpireDateEnforced = json.ocs.data.capabilities.filessharing?.ncpublic?.expiredateremote?.enforced ?? false
-            NCGlobal.shared.capabilityFileSharingRemoteExpireDateDays = json.ocs.data.capabilities.filessharing?.ncpublic?.expiredateremote?.days ?? 0
-
-            NCGlobal.shared.capabilityThemingColor = json.ocs.data.capabilities.theming?.color ?? ""
-            NCGlobal.shared.capabilityThemingColorElement = json.ocs.data.capabilities.theming?.colorelement ?? ""
-            NCGlobal.shared.capabilityThemingColorText = json.ocs.data.capabilities.theming?.colortext ?? ""
-            NCGlobal.shared.capabilityThemingName = json.ocs.data.capabilities.theming?.name ?? ""
-            NCGlobal.shared.capabilityThemingSlogan = json.ocs.data.capabilities.theming?.slogan ?? ""
-
-            NCGlobal.shared.capabilityE2EEEnabled = json.ocs.data.capabilities.endtoendencryption?.enabled ?? false
-            NCGlobal.shared.capabilityE2EEApiVersion = json.ocs.data.capabilities.endtoendencryption?.apiversion ?? ""
-
-            NCGlobal.shared.capabilityRichdocumentsEnabled = json.ocs.data.capabilities.richdocuments?.directediting ?? false
-            NCGlobal.shared.capabilityRichdocumentsMimetypes.removeAll()
-            if let mimetypes = json.ocs.data.capabilities.richdocuments?.mimetypes {
+            global.capabilityFileSharingApiEnabled = data.capabilities.filessharing?.apienabled ?? false
+            global.capabilityFileSharingDefaultPermission = data.capabilities.filessharing?.defaultpermissions ?? 0
+            global.capabilityFileSharingPubPasswdEnforced = data.capabilities.filessharing?.ncpublic?.password?.enforced ?? false
+            global.capabilityFileSharingPubExpireDateEnforced = data.capabilities.filessharing?.ncpublic?.expiredate?.enforced ?? false
+            global.capabilityFileSharingPubExpireDateDays = data.capabilities.filessharing?.ncpublic?.expiredate?.days ?? 0
+            global.capabilityFileSharingInternalExpireDateEnforced = data.capabilities.filessharing?.ncpublic?.expiredateinternal?.enforced ?? false
+            global.capabilityFileSharingInternalExpireDateDays = data.capabilities.filessharing?.ncpublic?.expiredateinternal?.days ?? 0
+            global.capabilityFileSharingRemoteExpireDateEnforced = data.capabilities.filessharing?.ncpublic?.expiredateremote?.enforced ?? false
+            global.capabilityFileSharingRemoteExpireDateDays = data.capabilities.filessharing?.ncpublic?.expiredateremote?.days ?? 0
+
+            global.capabilityThemingColor = data.capabilities.theming?.color ?? ""
+            global.capabilityThemingColorElement = data.capabilities.theming?.colorelement ?? ""
+            global.capabilityThemingColorText = data.capabilities.theming?.colortext ?? ""
+            global.capabilityThemingName = data.capabilities.theming?.name ?? ""
+            global.capabilityThemingSlogan = data.capabilities.theming?.slogan ?? ""
+
+            global.capabilityE2EEEnabled = data.capabilities.endtoendencryption?.enabled ?? false
+            global.capabilityE2EEApiVersion = data.capabilities.endtoendencryption?.apiversion ?? ""
+
+            global.capabilityRichdocumentsEnabled = json.ocs.data.capabilities.richdocuments?.directediting ?? false
+            global.capabilityRichdocumentsMimetypes.removeAll()
+            if let mimetypes = data.capabilities.richdocuments?.mimetypes {
                 for mimetype in mimetypes {
-                    NCGlobal.shared.capabilityRichdocumentsMimetypes.append(mimetype)
+                    global.capabilityRichdocumentsMimetypes.append(mimetype)
                 }
             }
 
-            NCGlobal.shared.capabilityActivity.removeAll()
-            if let activities = json.ocs.data.capabilities.activity?.apiv2 {
+            global.capabilityActivity.removeAll()
+            if let activities = data.capabilities.activity?.apiv2 {
                 for activity in activities {
-                    NCGlobal.shared.capabilityActivity.append(activity)
+                    global.capabilityActivity.append(activity)
                 }
             }
 
-            NCGlobal.shared.capabilityNotification.removeAll()
-            if let notifications = json.ocs.data.capabilities.notifications?.ocsendpoints {
+            global.capabilityNotification.removeAll()
+            if let notifications = data.capabilities.notifications?.ocsendpoints {
                 for notification in notifications {
-                    NCGlobal.shared.capabilityNotification.append(notification)
+                    global.capabilityNotification.append(notification)
                 }
             }
 
-            NCGlobal.shared.capabilityFilesUndelete = json.ocs.data.capabilities.files?.undelete ?? false
-            NCGlobal.shared.capabilityFilesLockVersion = json.ocs.data.capabilities.files?.locking ?? ""
-            NCGlobal.shared.capabilityFilesComments = json.ocs.data.capabilities.files?.comments ?? false
-            NCGlobal.shared.capabilityFilesBigfilechunking = json.ocs.data.capabilities.files?.bigfilechunking ?? false
+            global.capabilityFilesUndelete = data.capabilities.files?.undelete ?? false
+            global.capabilityFilesLockVersion = data.capabilities.files?.locking ?? ""
+            global.capabilityFilesComments = data.capabilities.files?.comments ?? false
+            global.capabilityFilesBigfilechunking = data.capabilities.files?.bigfilechunking ?? false
 
-            NCGlobal.shared.capabilityUserStatusEnabled = json.ocs.data.capabilities.files?.undelete ?? false
-            if json.ocs.data.capabilities.external != nil {
-                NCGlobal.shared.capabilityExternalSites = true
+            global.capabilityUserStatusEnabled = data.capabilities.files?.undelete ?? false
+            if data.capabilities.external != nil {
+                global.capabilityExternalSites = true
             }
-            NCGlobal.shared.capabilityGroupfoldersEnabled = json.ocs.data.capabilities.groupfolders?.hasGroupFolders ?? false
+            global.capabilityGroupfoldersEnabled = data.capabilities.groupfolders?.hasGroupFolders ?? false
+            global.capabilitySecurityGuardDiagnostics = data.capabilities.securityguard?.diagnostics ?? false
         } catch let error as NSError {
             NextcloudKit.shared.nkCommonInstance.writeLog("Could not access database: \(error)")
             return

+ 22 - 1
iOSClient/Data/NCManageDatabase+Directory.swift

@@ -77,6 +77,14 @@ extension NCManageDatabase {
 
     func deleteDirectoryAndSubDirectory(serverUrl: String, account: String) {
 
+#if !EXTENSION
+        DispatchQueue.main.async {
+            if let appDelegate = UIApplication.shared.delegate as? AppDelegate {
+                appDelegate.listFilesVC[serverUrl] = nil
+            }
+        }
+#endif
+
         do {
             let realm = try Realm()
             let results = realm.objects(tableDirectory.self).filter("account == %@ AND serverUrl BEGINSWITH %@", account, serverUrl)
@@ -123,6 +131,20 @@ extension NCManageDatabase {
         }
     }
 
+    func cleanEtagDirectory(account: String, serverUrl: String) {
+
+        do {
+            let realm = try Realm()
+            try realm.write {
+                if let result = realm.objects(tableDirectory.self).filter("account == %@ AND serverUrl == %@", account, serverUrl).first {
+                    result.etag = ""
+                }
+            }
+        } catch let error {
+            NextcloudKit.shared.nkCommonInstance.writeLog("Could not write to database: \(error)")
+        }
+    }
+
     func getTableDirectory(predicate: NSPredicate) -> tableDirectory? {
 
         do {
@@ -141,7 +163,6 @@ extension NCManageDatabase {
 
         do {
             let realm = try Realm()
-            realm.refresh()
             return realm.objects(tableDirectory.self).filter("account == %@ AND serverUrl == %@", account, serverUrl).first
         } catch let error as NSError {
             NextcloudKit.shared.nkCommonInstance.writeLog("Could not access database: \(error)")

+ 1 - 33
iOSClient/Data/NCManageDatabase+E2EE.swift

@@ -46,7 +46,6 @@ class tableE2eEncryptionV3: Object {
     @Persisted var key = ""
     @Persisted var initializationVector = ""
     @Persisted var metadataKey = ""
-    @Persisted var metadataKeyFiledrop = ""
     @Persisted var metadataKeyIndex: Int = 0
     @Persisted var metadataVersion: Double = 0
     @Persisted var mimeType = ""
@@ -132,25 +131,6 @@ class tableE2eUsers: Object {
      }
 }
 
-class tableE2eUsersFiledrop: Object {
-
-    @Persisted(primaryKey: true) var primaryKey = ""
-    @Persisted var account = ""
-    @Persisted var certificate = ""
-    @Persisted var encryptedFiledropKey: String?
-    @Persisted var ocIdServerUrl: String = ""
-    @Persisted var serverUrl: String = ""
-    @Persisted var userId = ""
-
-    convenience init(account: String, ocIdServerUrl: String, userId: String) {
-        self.init()
-        self.primaryKey = account + ocIdServerUrl + userId
-        self.account = account
-        self.ocIdServerUrl = ocIdServerUrl
-        self.userId = userId
-     }
-}
-
 extension NCManageDatabase {
 
     // MARK: -
@@ -371,7 +351,7 @@ extension NCManageDatabase {
         return nil
     }
 
-    func getE2EUsers(account: String, ocIdServerUrl: String, userId: String) -> tableE2eUsers? {
+    func getE2EUser(account: String, ocIdServerUrl: String, userId: String) -> tableE2eUsers? {
 
         do {
             let realm = try Realm()
@@ -383,18 +363,6 @@ extension NCManageDatabase {
         return nil
     }
 
-    func getE2EUsersFiledrop(account: String, ocIdServerUrl: String, userId: String) -> tableE2eUsersFiledrop? {
-
-        do {
-            let realm = try Realm()
-            return realm.objects(tableE2eUsersFiledrop.self).filter("account == %@ && ocIdServerUrl == %@ AND userId == %@", account, ocIdServerUrl, userId).first
-        } catch let error as NSError {
-            NextcloudKit.shared.nkCommonInstance.writeLog("Could not access database: \(error)")
-        }
-
-        return nil
-    }
-
     func getE2eMetadata(account: String, ocIdServerUrl: String) -> tableE2eMetadata? {
 
         do {

+ 0 - 6
iOSClient/Data/NCManageDatabase+LocalFile.swift

@@ -52,7 +52,6 @@ extension NCManageDatabase {
 
         do {
             let realm = try Realm()
-            realm.refresh()
             return realm.objects(tableLocalFile.self).filter("ocId == %@", ocId).first
         } catch let error as NSError {
             NextcloudKit.shared.nkCommonInstance.writeLog("Could not access database: \(error)")
@@ -170,7 +169,6 @@ extension NCManageDatabase {
 
         do {
             let realm = try Realm()
-            realm.refresh()
             let results = realm.objects(tableLocalFile.self).filter("account == %@", account)
             return Array(results.map { tableLocalFile.init(value: $0) })
         } catch let error as NSError {
@@ -184,7 +182,6 @@ extension NCManageDatabase {
 
         do {
             let realm = try Realm()
-            realm.refresh()
             guard let result = realm.objects(tableLocalFile.self).filter(predicate).first else { return nil }
             return tableLocalFile.init(value: result)
         } catch let error as NSError {
@@ -198,7 +195,6 @@ extension NCManageDatabase {
 
         do {
             let realm = try Realm()
-            realm.refresh()
             return realm.objects(tableLocalFile.self).filter(predicate)
         } catch let error as NSError {
             NextcloudKit.shared.nkCommonInstance.writeLog("Could not access database: \(error)")
@@ -211,7 +207,6 @@ extension NCManageDatabase {
 
         do {
             let realm = try Realm()
-            realm.refresh()
             let results = realm.objects(tableLocalFile.self).filter(predicate).sorted(byKeyPath: sorted, ascending: ascending)
             return Array(results.map { tableLocalFile.init(value: $0) })
         } catch let error as NSError {
@@ -225,7 +220,6 @@ extension NCManageDatabase {
 
         do {
             let realm = try Realm()
-            realm.refresh()
             return realm.objects(tableLocalFile.self).filter(predicate).sorted(byKeyPath: sorted, ascending: ascending)
         } catch let error as NSError {
             NextcloudKit.shared.nkCommonInstance.writeLog("Could not access database: \(error)")

+ 173 - 50
iOSClient/Data/NCManageDatabase+Metadata.swift

@@ -27,9 +27,25 @@ import NextcloudKit
 
 class tableMetadata: Object, NCUserBaseUrl {
     override func isEqual(_ object: Any?) -> Bool {
-        if let object = object as? tableMetadata {
-            return self.fileId == object.fileId && self.account == object.account
-                   && self.path == object.path && self.fileName == object.fileName
+        if let object = object as? tableMetadata,
+           self.account == object.account,
+           self.etag == object.etag,
+           self.fileId == object.fileId,
+           self.path == object.path,
+           self.fileName == object.fileName,
+           self.fileNameView == object.fileNameView,
+           self.date == object.date,
+           self.permissions == object.permissions,
+           self.hasPreview == object.hasPreview,
+           self.note == object.note,
+           self.lock == object.lock,
+           self.favorite == object.favorite,
+           self.livePhotoFile == object.livePhotoFile,
+           self.sharePermissionsCollaborationServices == object.sharePermissionsCollaborationServices,
+           Array(self.tags).elementsEqual(Array(object.tags)),
+           Array(self.shareType).elementsEqual(Array(object.shareType)),
+           Array(self.sharePermissionsCloudMesh).elementsEqual(Array(object.sharePermissionsCloudMesh)) {
+            return true
         } else {
             return false
         }
@@ -60,11 +76,11 @@ class tableMetadata: Object, NCUserBaseUrl {
     @objc dynamic var hidden: Bool = false
     @objc dynamic var iconName = ""
     @objc dynamic var iconUrl = ""
-    @objc dynamic var isFlaggedAsLivePhotoByServer: Bool = false
+    @objc dynamic var isFlaggedAsLivePhotoByServer: Bool = false // Indicating if the file is sent as a live photo from the server, or if we should detect it as such and convert it client-side
     @objc dynamic var isExtractFile: Bool = false
-    @objc dynamic var livePhotoFile = ""
+    @objc dynamic var livePhotoFile = "" // If this is not empty, the media is a live photo. New media gets this straight from server, but old media needs to be detected as live photo (look isFlaggedAsLivePhotoByServer)
     @objc dynamic var mountType = ""
-    @objc dynamic var name = ""                                             // for unifiedSearch is the provider.id
+    @objc dynamic var name = "" // for unifiedSearch is the provider.id
     @objc dynamic var note = ""
     @objc dynamic var ocId = ""
     @objc dynamic var ownerId = ""
@@ -107,8 +123,6 @@ class tableMetadata: Object, NCUserBaseUrl {
     @objc dynamic var height: Int = 0
     @objc dynamic var width: Int = 0
     @objc dynamic var errorCode: Int = 0
-    @objc dynamic var errorCodeCounter: Int = 0
-    @objc dynamic var errorCodeDate: Date?
 
     override static func primaryKey() -> String {
         return "ocId"
@@ -226,7 +240,7 @@ extension tableMetadata {
     }
 
     var isInTransfer: Bool {
-        status == NCGlobal.shared.metadataStatusInDownload || status == NCGlobal.shared.metadataStatusDownloading || status == NCGlobal.shared.metadataStatusInUpload || status == NCGlobal.shared.metadataStatusUploading
+        status == NCGlobal.shared.metadataStatusDownloading || status == NCGlobal.shared.metadataStatusUploading
     }
 
     var isTransferInForeground: Bool {
@@ -234,11 +248,11 @@ extension tableMetadata {
     }
 
     var isDownload: Bool {
-        status == NCGlobal.shared.metadataStatusInDownload || status == NCGlobal.shared.metadataStatusDownloading
+        status == NCGlobal.shared.metadataStatusDownloading
     }
 
     var isUpload: Bool {
-        status == NCGlobal.shared.metadataStatusInUpload || status == NCGlobal.shared.metadataStatusUploading
+        status == NCGlobal.shared.metadataStatusUploading
     }
 
     @objc var isDirectoryE2EE: Bool {
@@ -257,6 +271,14 @@ extension tableMetadata {
         !isFlaggedAsLivePhotoByServer
     }
 
+    var isSynchronizable: Bool {
+        let localFile = NCManageDatabase.shared.getResultsTableLocalFile(predicate: NSPredicate(format: "ocId == %@", ocId))?.first
+        if localFile?.etag != etag || NCUtilityFileSystem().fileProviderStorageSize(ocId, fileNameView: fileNameView) == 0 {
+            return true
+        }
+        return false
+    }
+
     /// 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)
@@ -264,10 +286,8 @@ extension tableMetadata {
 
     // Return if is sharable
     func isSharable() -> Bool {
-        guard NCGlobal.shared.capabilityFileSharingApiEnabled else { return false }
-        if isDirectoryE2EE || e2eEncrypted {
-            guard directory, NCGlobal.shared.capabilityE2EEEnabled else { return false }
-            return true
+        if !NCGlobal.shared.capabilityFileSharingApiEnabled || (NCGlobal.shared.capabilityE2EEEnabled && isDirectoryE2EE) {
+            return false
         }
         return true
     }
@@ -402,6 +422,15 @@ extension NCManageDatabase {
         completion(metadataFolder, metadataFolders, metadatas)
     }
 
+    func convertFilesToMetadatas(_ files: [NKFile], useMetadataFolder: Bool) async -> (metadataFolder: tableMetadata, metadatasFolder: [tableMetadata], metadatas: [tableMetadata]) {
+
+        await withUnsafeContinuation({ continuation in
+            convertFilesToMetadatas(files, useMetadataFolder: useMetadataFolder) { metadataFolder, metadatasFolder, metadatas in
+                continuation.resume(returning: (metadataFolder, metadatasFolder, metadatas))
+            }
+        })
+    }
+
     func createMetadata(account: String, user: String, userId: String, fileName: String, fileNameView: String, ocId: String, serverUrl: String, urlBase: String, url: String, contentType: String, isUrl: Bool = false, name: String = NCGlobal.shared.appName, subline: String? = nil, iconName: String? = nil, iconUrl: String? = nil) -> tableMetadata {
 
         let metadata = tableMetadata()
@@ -497,6 +526,18 @@ extension NCManageDatabase {
         }
     }
 
+    func deleteMetadata(results: Results<tableMetadata>) {
+
+        do {
+            let realm = try Realm()
+            try realm.write {
+                realm.delete(results)
+            }
+        } catch let error {
+            NextcloudKit.shared.nkCommonInstance.writeLog("Could not write to database: \(error)")
+        }
+    }
+
     func moveMetadata(ocId: String, serverUrlTo: String) {
 
         do {
@@ -530,7 +571,14 @@ extension NCManageDatabase {
         }
     }
 
-    func setMetadataSession(ocId: String, newFileName: String? = nil, session: String?, sessionError: String?, sessionSelector: String?, sessionTaskIdentifier: Int?, status: Int?, etag: String? = nil, errorCode: Int?) {
+    func setMetadataSession(ocId: String,
+                            newFileName: String? = nil,
+                            session: String? = nil,
+                            sessionError: String? = nil,
+                            selector: String? = nil,
+                            status: Int? = nil,
+                            etag: String? = nil,
+                            errorCode: Int? = nil) {
 
         do {
             let realm = try Realm()
@@ -546,11 +594,8 @@ extension NCManageDatabase {
                     if let sessionError {
                         result.sessionError = sessionError
                     }
-                    if let sessionSelector {
-                        result.sessionSelector = sessionSelector
-                    }
-                    if let sessionTaskIdentifier {
-                        result.sessionTaskIdentifier = sessionTaskIdentifier
+                    if let selector {
+                        result.sessionSelector = selector
                     }
                     if let status {
                         result.status = status
@@ -560,12 +605,6 @@ extension NCManageDatabase {
                     }
                     if let errorCode {
                         result.errorCode = errorCode
-                        if errorCode == 0 {
-                            result.errorCodeCounter = 0
-                        } else {
-                            result.errorCodeCounter += 1
-                            result.errorCodeDate = Date()
-                        }
                     }
                 }
             }
@@ -574,6 +613,49 @@ extension NCManageDatabase {
         }
     }
 
+    func setMetadataSession(ocId: String,
+                            status: Int? = nil,
+                            taskIdentifier: Int? = nil) {
+
+        do {
+            let realm = try Realm()
+            try realm.write {
+                if let result = realm.objects(tableMetadata.self).filter("ocId == %@", ocId).first {
+                    if let status {
+                        result.status = status
+                    }
+                    if let taskIdentifier {
+                        result.sessionTaskIdentifier = taskIdentifier
+                    }
+                }
+            }
+        } catch let error {
+            NextcloudKit.shared.nkCommonInstance.writeLog("Could not write to database: \(error)")
+        }
+    }
+
+    func setMetadataSessionInWaitDownload(ocId: String, selector: String) -> tableMetadata? {
+
+        var metadata: tableMetadata?
+
+        do {
+            let realm = try Realm()
+            try realm.write {
+                if let result = realm.objects(tableMetadata.self).filter("ocId == %@", ocId).first {
+                    result.session = NextcloudKit.shared.nkCommonInstance.sessionIdentifierDownload
+                    result.sessionError = ""
+                    result.sessionSelector = selector
+                    result.status = NCGlobal.shared.metadataStatusWaitDownload
+                    metadata = tableMetadata(value: result)
+                }
+            }
+        } catch let error {
+            NextcloudKit.shared.nkCommonInstance.writeLog("Could not write to database: \(error)")
+        }
+
+        return metadata
+    }
+
     @discardableResult
     func setMetadataStatus(ocId: String, status: Int) -> tableMetadata? {
 
@@ -629,9 +711,10 @@ extension NCManageDatabase {
         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
+                if 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)")
@@ -724,6 +807,19 @@ extension NCManageDatabase {
         return []
     }
 
+    func getMetadatas(predicate: NSPredicate, sorted: String, ascending: Bool = false) -> [tableMetadata]? {
+
+        do {
+            let realm = try Realm()
+            let results = realm.objects(tableMetadata.self).filter(predicate).sorted(byKeyPath: sorted, ascending: ascending)
+            return Array(results.map { tableMetadata.init(value: $0) })
+        } catch let error as NSError {
+            NextcloudKit.shared.nkCommonInstance.writeLog("Could not access database: \(error)")
+        }
+
+        return nil
+    }
+
     func getResultsMetadatas(predicate: NSPredicate, sorted: String? = nil, ascending: Bool = false) -> Results<tableMetadata>? {
 
         do {
@@ -740,6 +836,18 @@ extension NCManageDatabase {
         return nil
     }
 
+    func getResultsMetadatas(predicate: NSPredicate, sorted: [RealmSwift.SortDescriptor]) -> Results<tableMetadata>? {
+
+        do {
+            let realm = try Realm()
+            return realm.objects(tableMetadata.self).filter(predicate).sorted(by: sorted)
+        } catch let error as NSError {
+            NextcloudKit.shared.nkCommonInstance.writeLog("Could not access database: \(error)")
+        }
+
+        return nil
+    }
+
     func getResultMetadata(predicate: NSPredicate) -> tableMetadata? {
 
         do {
@@ -1007,7 +1115,7 @@ extension NCManageDatabase {
         do {
             let realm = try Realm()
             realm.refresh()
-            return realm.objects(tableMetadata.self).filter(NSPredicate(format: "status == %i || status == %i", NCGlobal.shared.metadataStatusInUpload, NCGlobal.shared.metadataStatusUploading)).count
+            return realm.objects(tableMetadata.self).filter(NSPredicate(format: "status == %i", NCGlobal.shared.metadataStatusUploading)).count
         } catch let error as NSError {
             NextcloudKit.shared.nkCommonInstance.writeLog("Could not access database: \(error)")
         }
@@ -1067,24 +1175,6 @@ extension NCManageDatabase {
         return nil
     }
 
-    func clearErrorCodeMetadatas(metadatas: Results<tableMetadata>?) {
-
-        guard let metadatas else { return }
-
-        do {
-            let realm = try Realm()
-            try realm.write {
-                for metadata in metadatas {
-                    metadata.errorCode = 0
-                    metadata.errorCodeCounter = 0
-                    metadata.errorCodeDate = nil
-                }
-            }
-        } catch let error as NSError {
-            NextcloudKit.shared.nkCommonInstance.writeLog("Could not access database: \(error)")
-        }
-    }
-
     @discardableResult
     func updateMetadatas(_ metadatas: [tableMetadata], predicate: NSPredicate) -> (metadatasChangedCount: Int, metadatasChanged: Bool) {
 
@@ -1097,7 +1187,7 @@ extension NCManageDatabase {
                 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,
+                    if let result = results.first(where: { $0.ocId == metadata.ocId }),
                        metadata.isEqual(result) { } else {
                         metadatasChanged = true
                         break
@@ -1116,4 +1206,37 @@ extension NCManageDatabase {
 
         return (metadatasChangedCount, metadatasChanged)
     }
+
+    func replaceMetadata(_ metadatas: [tableMetadata], predicate: NSPredicate) {
+
+        do {
+            let realm = try Realm()
+            try realm.write {
+                let results = realm.objects(tableMetadata.self).filter(predicate)
+                realm.delete(results)
+                for metadata in metadatas {
+                    if results.where({ $0.ocId == metadata.ocId }).isEmpty {
+                        realm.add(tableMetadata(value: metadata), update: .modified)
+                    } else {
+                        continue
+                    }
+                }
+            }
+        } catch let error {
+            NextcloudKit.shared.nkCommonInstance.writeLog("Could not write to database: \(error)")
+        }
+    }
+
+    func getMediaMetadatas(predicate: NSPredicate) -> ThreadSafeArray<tableMetadata>? {
+
+        do {
+            let realm = try Realm()
+            let results = realm.objects(tableMetadata.self).filter(predicate).sorted(byKeyPath: "date", ascending: false)
+            return ThreadSafeArray(results.map { tableMetadata.init(value: $0) })
+        } catch let error as NSError {
+            NextcloudKit.shared.nkCommonInstance.writeLog("Could not access database: \(error)")
+        }
+
+        return nil
+    }
 }

+ 117 - 0
iOSClient/Data/NCManageDatabase+SecurityGuard.swift

@@ -0,0 +1,117 @@
+//
+//  NCManageDatabase+SecurityGuard.swift
+//  Nextcloud
+//
+//  Created by Marino Faggiana on 17/01/24.
+//  Copyright © 2024 Marino Faggiana. All rights reserved.
+//
+//  Author Marino Faggiana <marino.faggiana@nextcloud.com>
+//
+//  This program is free software: you can redistribute it and/or modify
+//  it under the terms of the GNU General Public License as published by
+//  the Free Software Foundation, either version 3 of the License, or
+//  (at your option) any later version.
+//
+//  This program is distributed in the hope that it will be useful,
+//  but WITHOUT ANY WARRANTY; without even the implied warranty of
+//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+//  GNU General Public License for more details.
+//
+//  You should have received a copy of the GNU General Public License
+//  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+//
+
+import Foundation
+import RealmSwift
+import NextcloudKit
+
+class TableSecurityGuardDiagnostics: Object {
+
+    @Persisted var account = ""
+    @Persisted(primaryKey: true) var primaryKey = ""
+    @Persisted var issue: String = ""
+    @Persisted var error: String?
+    @Persisted var counter: Int = 0
+    @Persisted var oldest: TimeInterval
+    @Persisted var id: ObjectId
+
+    convenience init(account: String, issue: String, error: String?, date: Date) {
+        self.init()
+
+        self.account = account
+        self.primaryKey = account + issue + (error ?? "")
+        self.issue = issue
+        self.error = error
+        self.counter = 1
+        self.oldest = date.timeIntervalSince1970
+     }
+}
+
+extension NCManageDatabase {
+
+    func addDiagnostic(account: String, issue: String, error: String? = nil) {
+
+        do {
+            let realm = try Realm()
+            try realm.write {
+                let primaryKey = account + issue + (error ?? "")
+                if let result = realm.object(ofType: TableSecurityGuardDiagnostics.self, forPrimaryKey: primaryKey) {
+                    result.counter += 1
+                    result.oldest = Date().timeIntervalSince1970
+                } else {
+                    let table = TableSecurityGuardDiagnostics(account: account, issue: issue, error: error, date: Date())
+                    realm.add(table)
+                }
+            }
+        } catch let error {
+            NextcloudKit.shared.nkCommonInstance.writeLog("Could not write to database: \(error)")
+        }
+    }
+
+    func existsDiagnostics(account: String) -> Bool {
+
+        do {
+            let realm = try Realm()
+            let results = realm.objects(TableSecurityGuardDiagnostics.self).where({
+                $0.account == account
+            })
+            if !results.isEmpty { return true }
+        } catch let error as NSError {
+            NextcloudKit.shared.nkCommonInstance.writeLog("Could not access database: \(error)")
+        }
+
+        return false
+    }
+
+    func getDiagnostics(account: String, issue: String) -> Results<TableSecurityGuardDiagnostics>? {
+
+        do {
+            let realm = try Realm()
+            let results = realm.objects(TableSecurityGuardDiagnostics.self).where({
+                $0.account == account && $0.issue == issue
+            })
+            return results
+        } catch let error as NSError {
+            NextcloudKit.shared.nkCommonInstance.writeLog("Could not access database: \(error)")
+        }
+
+        return nil
+    }
+
+    func deleteDiagnostics(account: String, ids: [ObjectId]) {
+
+        do {
+            let realm = try Realm()
+            try realm.write {
+                let results = realm.objects(TableSecurityGuardDiagnostics.self).where({
+                    $0.account == account
+                })
+                for result in results where ids.contains(result.id) {
+                    realm.delete(result)
+                }
+            }
+        } catch let error {
+            NextcloudKit.shared.nkCommonInstance.writeLog("Could not write to database: \(error)")
+        }
+    }
+}

+ 3 - 2
iOSClient/Data/NCManageDatabase+Video.swift

@@ -155,8 +155,9 @@ extension NCManageDatabase {
         do {
             let realm = try Realm()
             try realm.write {
-                let result = realm.objects(tableVideo.self).filter("account == %@ AND ocId == %@", metadata.account, metadata.ocId)
-                realm.delete(result)
+                if let result = realm.objects(tableVideo.self).filter("account == %@ AND ocId == %@", metadata.account, metadata.ocId).first {
+                    realm.delete(result)
+                }
             }
         } catch let error {
             NextcloudKit.shared.nkCommonInstance.writeLog("Could not write to database: \(error)")

+ 3 - 3
iOSClient/Data/NCManageDatabase.swift

@@ -86,13 +86,13 @@ class NCManageDatabase: NSObject {
                               tableE2eMetadata.self,
                               tableE2eUsers.self,
                               tableE2eCounter.self,
-                              tableE2eUsersFiledrop.self,
                               tableShare.self,
                               tableChunk.self,
                               tableAvatar.self,
                               tableDashboardWidget.self,
                               tableDashboardWidgetButton.self,
-                              NCDBLayoutForView.self]
+                              NCDBLayoutForView.self,
+                              TableSecurityGuardDiagnostics.self]
             )
 
         } else {
@@ -235,6 +235,7 @@ class NCManageDatabase: NSObject {
         self.clearTable(tableMetadata.self, account: account)
         self.clearTable(tablePhotoLibrary.self, account: account)
         self.clearTable(tableShare.self, account: account)
+        self.clearTable(TableSecurityGuardDiagnostics.self, account: account)
         self.clearTable(tableTag.self, account: account)
         self.clearTable(tableTip.self)
         self.clearTable(tableTrash.self, account: account)
@@ -250,7 +251,6 @@ class NCManageDatabase: NSObject {
         self.clearTable(tableE2eMetadata.self, account: account)
         self.clearTable(tableE2eUsers.self, account: account)
         self.clearTable(tableE2eCounter.self, account: account)
-        self.clearTable(tableE2eUsersFiledrop.self, account: account)
     }
 
     @objc func removeDB() {

+ 3 - 0
iOSClient/Diagnostics/NCCapabilitiesView.swift

@@ -115,6 +115,9 @@ class NCCapabilitiesViewOO: ObservableObject {
         if let image = UIImage(systemName: "person.2") {
             capabililies.append(Capability(text: "Group folders", image: image, resize: false, available: NCGlobal.shared.capabilityGroupfoldersEnabled))
         }
+        if let image = UIImage(systemName: "shield") {
+            capabililies.append(Capability(text: "Security Guard Diagnostics", image: image, resize: false, available: NCGlobal.shared.capabilitySecurityGuardDiagnostics))
+        }
 
         homeServer = utilityFileSystem.getHomeServer(urlBase: activeAccount.urlBase, userId: activeAccount.userId) + "/"
     }

+ 6 - 0
iOSClient/Extensions/String+Extension.swift

@@ -31,6 +31,12 @@ extension String {
         return self.components(separatedBy: CharacterSet.alphanumerics.inverted).joined().lowercased()
     }
 
+    var isNumber: Bool {
+        return self.allSatisfy { character in
+            character.isNumber
+        }
+    }
+
     public var uppercaseInitials: String? {
         let initials = self.components(separatedBy: .whitespaces)
             .reduce("", {

+ 0 - 1
iOSClient/Extensions/UIAlertController+Extension.swift

@@ -38,7 +38,6 @@ extension UIAlertController {
             guard let fileNameFolder = alertController.textFields?.first?.text else { return }
             if markE2ee {
                 Task {
-
                     let createFolderResults = await NextcloudKit.shared.createFolder(serverUrlFileName: serverUrl + "/" + fileNameFolder)
                     if createFolderResults.error == .success {
                         let error = await NCNetworkingE2EEMarkFolder().markFolderE2ee(account: urlBase.account, fileName: fileNameFolder, serverUrl: serverUrl, userId: urlBase.userId)

+ 1 - 2
iOSClient/Extensions/UINavigationController+Extension.swift

@@ -30,7 +30,7 @@ extension UINavigationController {
         return self.visibleViewController!.topMostViewController()
     }
 
-    func setFileAppreance() {
+    func setNavigationBarAppearance() {
 
         navigationBar.tintColor = .systemBlue
 
@@ -39,7 +39,6 @@ extension UINavigationController {
 
         standardAppearance.largeTitleTextAttributes = [NSAttributedString.Key.foregroundColor: UIColor.label]
         standardAppearance.titleTextAttributes = [NSAttributedString.Key.foregroundColor: UIColor.label]
-        standardAppearance.backgroundColor = .systemGray6
         navigationBar.standardAppearance = standardAppearance
 
         let scrollEdgeAppearance = UINavigationBarAppearance()

+ 10 - 23
iOSClient/Favorites/NCFavorite.swift

@@ -44,12 +44,16 @@ class NCFavorite: NCCollectionViewCommon {
     override func viewWillAppear(_ animated: Bool) {
         super.viewWillAppear(animated)
 
-        navigationController?.setFileAppreance()
+        if dataSource.metadatas.isEmpty {
+            reloadDataSource()
+        }
+        reloadDataSourceNetwork()
     }
 
     // MARK: - DataSource + NC Endpoint
 
-    override func queryDB(isForced: Bool) {
+    override func queryDB() {
+        super.queryDB()
 
         var metadatas: [tableMetadata] = []
 
@@ -70,37 +74,20 @@ class NCFavorite: NCCollectionViewCommon {
                                        searchResults: self.searchResults)
     }
 
-    override func reloadDataSource(isForced: Bool = true) {
-        super.reloadDataSource()
-
-        DispatchQueue.global().async {
-            self.queryDB(isForced: isForced)
-            DispatchQueue.main.async {
-                self.refreshControl.endRefreshing()
-                self.collectionView.reloadData()
-            }
-        }
-    }
-
-    override func reloadDataSourceNetwork(isForced: Bool = false) {
-        super.reloadDataSourceNetwork(isForced: isForced)
-
-        NextcloudKit.shared.nkCommonInstance.writeLog("[INFO] Reload data source network favorite forced \(isForced)")
-
-        isReloadDataSourceNetworkInProgress = true
-        collectionView?.reloadData()
+    override func reloadDataSourceNetwork() {
+        super.reloadDataSourceNetwork()
 
         NextcloudKit.shared.listingFavorites(showHiddenFiles: NCKeychain().showHiddenFiles,
                                              options: NKRequestOptions(queue: NextcloudKit.shared.nkCommonInstance.backgroundQueue)) { account, files, _, error in
 
-            self.isReloadDataSourceNetworkInProgress = false
             if error == .success {
                 NCManageDatabase.shared.convertFilesToMetadatas(files, useMetadataFolder: false) { _, _, metadatas in
                     NCManageDatabase.shared.updateMetadatasFavorite(account: account, metadatas: metadatas)
                     self.reloadDataSource()
                 }
+            } else {
+                self.reloadDataSource(withQueryDB: false)
             }
-            self.reloadDataSource()
         }
     }
 }

+ 4 - 4
iOSClient/Files/NCFiles.storyboard

@@ -1,9 +1,9 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="17156" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="EFX-fO-Oip">
+<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="22505" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="EFX-fO-Oip">
     <device id="retina5_9" orientation="portrait" appearance="light"/>
     <dependencies>
         <deployment identifier="iOS"/>
-        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="17125"/>
+        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="22504"/>
         <capability name="Safe area layout guides" minToolsVersion="9.0"/>
         <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
     </dependencies>
@@ -17,7 +17,7 @@
                         <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
                         <subviews>
                             <collectionView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" dataMode="prototypes" translatesAutoresizingMaskIntoConstraints="NO" id="Zaz-Cl-qpZ">
-                                <rect key="frame" x="0.0" y="0.0" width="375" height="812"/>
+                                <rect key="frame" x="0.0" y="0.0" width="375" height="862"/>
                                 <color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
                                 <collectionViewFlowLayout key="collectionViewLayout" minimumLineSpacing="0.0" minimumInteritemSpacing="0.0" id="fF1-wd-0xN">
                                     <size key="itemSize" width="0.0" height="0.0"/>
@@ -37,7 +37,7 @@
                         <constraints>
                             <constraint firstItem="Zaz-Cl-qpZ" firstAttribute="leading" secondItem="Meh-VD-wWh" secondAttribute="leading" id="1bp-sm-u0X"/>
                             <constraint firstItem="Meh-VD-wWh" firstAttribute="trailing" secondItem="Zaz-Cl-qpZ" secondAttribute="trailing" id="aNd-UL-hmu"/>
-                            <constraint firstItem="Meh-VD-wWh" firstAttribute="bottom" secondItem="Zaz-Cl-qpZ" secondAttribute="bottom" constant="-34" id="aNr-tf-2AH"/>
+                            <constraint firstItem="Meh-VD-wWh" firstAttribute="bottom" secondItem="Zaz-Cl-qpZ" secondAttribute="bottom" constant="-84" id="aNr-tf-2AH"/>
                             <constraint firstItem="Zaz-Cl-qpZ" firstAttribute="top" secondItem="QEs-gO-Cmp" secondAttribute="top" id="tji-wt-R7s"/>
                         </constraints>
                     </view>

+ 87 - 45
iOSClient/Files/NCFiles.swift

@@ -71,9 +71,9 @@ class NCFiles: NCCollectionViewCommon {
                 }
 
                 self.titleCurrentFolder = self.getNavigationTitle()
-                self.setNavigationItem()
+                self.setNavigationItems()
 
-                self.reloadDataSource(isForced: false)
+                self.reloadDataSource()
                 self.reloadDataSourceNetwork()
             }
         }
@@ -86,7 +86,11 @@ class NCFiles: NCCollectionViewCommon {
             titleCurrentFolder = getNavigationTitle()
         }
         super.viewWillAppear(animated)
-        navigationController?.setFileAppreance()
+
+        if dataSource.metadatas.isEmpty {
+            reloadDataSource()
+        }
+        reloadDataSourceNetwork()
     }
 
     override func viewWillDisappear(_ animated: Bool) {
@@ -97,23 +101,16 @@ class NCFiles: NCCollectionViewCommon {
     }
 
     // MARK: - DataSource + NC Endpoint
-    //
-    // forced: do no make the etag of directory test (default)
-    //
 
-    override func queryDB(isForced: Bool) {
+    override func queryDB() {
+        super.queryDB()
 
         let metadatas = NCManageDatabase.shared.getMetadatas(predicate: NSPredicate(format: "account == %@ AND serverUrl == %@", self.appDelegate.account, self.serverUrl))
         let directory = NCManageDatabase.shared.getTableDirectory(predicate: NSPredicate(format: "account == %@ AND serverUrl == %@", self.appDelegate.account, self.serverUrl))
-        let metadataTransfer = NCManageDatabase.shared.getMetadata(predicate: NSPredicate(format: "status != %i AND serverUrl == %@", NCGlobal.shared.metadataStatusNormal, self.serverUrl))
         if self.metadataFolder == nil {
             self.metadataFolder = NCManageDatabase.shared.getMetadataFolder(account: self.appDelegate.account, urlBase: self.appDelegate.urlBase, userId: self.appDelegate.userId, serverUrl: self.serverUrl)
         }
 
-        if !isForced, let directory, directory.etag == self.dataSource.directory?.etag, metadataTransfer == nil, self.fileNameBlink == nil, self.fileNameOpen == nil {
-            return
-        }
-
         self.richWorkspaceText = directory?.richWorkspace
         self.dataSource = NCDataSource(
             metadatas: metadatas,
@@ -128,32 +125,20 @@ class NCFiles: NCCollectionViewCommon {
             searchResults: self.searchResults)
     }
 
-    override func reloadDataSource(isForced: Bool = true) {
-        super.reloadDataSource()
-
-        DispatchQueue.main.async { self.refreshControl.endRefreshing() }
-        DispatchQueue.global().async {
-            guard !self.isSearchingMode, !self.appDelegate.account.isEmpty, !self.appDelegate.urlBase.isEmpty, !self.serverUrl.isEmpty else { return }
+    override func reloadDataSource(withQueryDB: Bool = true) {
+        super.reloadDataSource(withQueryDB: withQueryDB)
 
-            self.queryDB(isForced: isForced)
-
-            DispatchQueue.main.async {
-                self.collectionView.reloadData()
-                if !self.dataSource.metadatas.isEmpty {
-                    self.blinkCell(fileName: self.fileNameBlink)
-                    self.openFile(fileName: self.fileNameOpen)
-                    self.fileNameBlink = nil
-                    self.fileNameOpen = nil
-                }
-            }
+        if !self.dataSource.metadatas.isEmpty {
+            self.blinkCell(fileName: self.fileNameBlink)
+            self.openFile(fileName: self.fileNameOpen)
+            self.fileNameBlink = nil
+            self.fileNameOpen = nil
         }
     }
 
-    override func reloadDataSourceNetwork(isForced: Bool = false) {
-        super.reloadDataSourceNetwork(isForced: isForced)
+    override func reloadDataSourceNetwork() {
         guard !isSearchingMode else {
-            networkSearch()
-            return
+            return networkSearch()
         }
 
         func downloadMetadata(_ metadata: tableMetadata) -> Bool {
@@ -170,29 +155,86 @@ class NCFiles: NCCollectionViewCommon {
             return false
         }
 
-        NextcloudKit.shared.nkCommonInstance.writeLog("[INFO] Reload data source network files forced \(isForced)")
+        super.reloadDataSourceNetwork()
 
-        isReloadDataSourceNetworkInProgress = true
-        collectionView?.reloadData()
-
-        networkReadFolder(isForced: isForced) { tableDirectory, metadatas, metadatasChangedCount, metadatasChanged, error in
+        networkReadFolder { tableDirectory, metadatas, metadatasChangedCount, metadatasChanged, error in
             if error == .success {
                 for metadata in metadatas ?? [] where !metadata.directory && downloadMetadata(metadata) {
                     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))
                     }
                 }
+                self.richWorkspaceText = tableDirectory?.richWorkspace
+
+                if metadatasChangedCount != 0 || metadatasChanged {
+                    self.reloadDataSource()
+                } else {
+                    self.reloadDataSource(withQueryDB: false)
+                }
+            } else {
+                self.reloadDataSource(withQueryDB: false)
             }
+        }
+    }
 
-            self.isReloadDataSourceNetworkInProgress = false
-            self.richWorkspaceText = tableDirectory?.richWorkspace
+    private func networkReadFolder(completion: @escaping(_ tableDirectory: tableDirectory?, _ metadatas: [tableMetadata]?, _ metadatasChangedCount: Int, _ metadatasChanged: Bool, _ error: NKError) -> Void) {
 
-            if metadatasChangedCount != 0 || metadatasChanged || isForced {
-                self.reloadDataSource()
-            } else if self.dataSource.getMetadataSourceForAllSections().isEmpty {
-                DispatchQueue.main.async {
-                    self.collectionView.reloadData()
+        var tableDirectory: tableDirectory?
+
+        NCNetworking.shared.readFile(serverUrlFileName: serverUrl) { account, metadataFolder, error in
+
+            guard error == .success, let metadataFolder else {
+                return completion(nil, nil, 0, false, error)
+            }
+            tableDirectory = NCManageDatabase.shared.setDirectory(serverUrl: self.serverUrl, richWorkspace: metadataFolder.richWorkspace, account: account)
+            // swiftlint:disable empty_string
+            let forceReplaceMetadatas = tableDirectory?.etag == ""
+            // swiftlint:enable empty_string
+
+            if tableDirectory?.etag != metadataFolder.etag || metadataFolder.e2eEncrypted {
+                NCNetworking.shared.readFolder(serverUrl: self.serverUrl,
+                                               account: self.appDelegate.account,
+                                               forceReplaceMetadatas: forceReplaceMetadatas) { _, metadataFolder, metadatas, metadatasChangedCount, metadatasChanged, error in
+                    guard error == .success else {
+                        return completion(tableDirectory, nil, 0, false, error)
+                    }
+                    self.metadataFolder = metadataFolder
+                    // E2EE
+                    if let metadataFolder = metadataFolder,
+                       metadataFolder.e2eEncrypted,
+                       NCKeychain().isEndToEndEnabled(account: self.appDelegate.account),
+                       !NCNetworkingE2EE().isInUpload(account: self.appDelegate.account, serverUrl: self.serverUrl) {
+                        let lock = NCManageDatabase.shared.getE2ETokenLock(account: self.appDelegate.account, serverUrl: self.serverUrl)
+                        NextcloudKit.shared.getE2EEMetadata(fileId: metadataFolder.ocId, e2eToken: lock?.e2eToken, options: NCNetworkingE2EE().getOptions()) { account, e2eMetadata, signature, _, error in
+                            if error == .success, let e2eMetadata = e2eMetadata {
+                                let error = NCEndToEndMetadata().decodeMetadata(e2eMetadata, signature: signature, serverUrl: self.serverUrl, account: account, urlBase: self.appDelegate.urlBase, userId: self.appDelegate.userId)
+                                if error == .success {
+                                    self.reloadDataSource()
+                                } else {
+                                    // Client Diagnostic
+                                    NCManageDatabase.shared.addDiagnostic(account: account, issue: NCGlobal.shared.diagnosticIssueE2eeErrors)
+                                    NCContentPresenter().showError(error: error)
+                                }
+                            } else if error.errorCode == NCGlobal.shared.errorResourceNotFound {
+                                // no metadata found, send a new metadata
+                                Task {
+                                    let serverUrl = metadataFolder.serverUrl + "/" + metadataFolder.fileName
+                                    let error = await NCNetworkingE2EE().uploadMetadata(account: metadataFolder.account, serverUrl: serverUrl, userId: metadataFolder.userId)
+                                    if error != .success {
+                                        NCContentPresenter().showError(error: error)
+                                    }
+                                }
+                            } else {
+                                NCContentPresenter().showError(error: NKError(errorCode: NCGlobal.shared.errorE2EEKeyDecodeMetadata, errorDescription: "_e2e_error_"))
+                            }
+                            completion(tableDirectory, metadatas, metadatasChangedCount, metadatasChanged, error)
+                        }
+                    } else {
+                        completion(tableDirectory, metadatas, metadatasChangedCount, metadatasChanged, error)
+                    }
                 }
+            } else {
+                completion(tableDirectory, nil, 0, false, NKError())
             }
         }
     }

+ 10 - 22
iOSClient/Groupfolders/NCGroupfolders.swift

@@ -44,12 +44,16 @@ class NCGroupfolders: NCCollectionViewCommon {
     override func viewWillAppear(_ animated: Bool) {
         super.viewWillAppear(animated)
 
-        navigationController?.setFileAppreance()
+        if dataSource.metadatas.isEmpty {
+            reloadDataSource()
+        }
+        reloadDataSourceNetwork()
     }
 
     // MARK: - DataSource + NC Endpoint
 
-    override func queryDB(isForced: Bool) {
+    override func queryDB() {
+        super.queryDB()
 
         var metadatas: [tableMetadata] = []
 
@@ -71,24 +75,8 @@ class NCGroupfolders: NCCollectionViewCommon {
             searchResults: self.searchResults)
     }
 
-    override func reloadDataSource(isForced: Bool = true) {
-        super.reloadDataSource()
-
-        self.queryDB(isForced: isForced)
-        DispatchQueue.main.async {
-            self.isReloadDataSourceNetworkInProgress = false
-            self.refreshControl.endRefreshing()
-            self.collectionView.reloadData()
-        }
-    }
-
-    override func reloadDataSourceNetwork(isForced: Bool = false) {
-        super.reloadDataSourceNetwork(isForced: isForced)
-
-        NextcloudKit.shared.nkCommonInstance.writeLog("[INFO] Reload data source network groupfolders forced \(isForced)")
-
-        isReloadDataSourceNetworkInProgress = true
-        collectionView?.reloadData()
+    override func reloadDataSourceNetwork() {
+        super.reloadDataSourceNetwork()
 
         let homeServerUrl = utilityFileSystem.getHomeServer(urlBase: self.appDelegate.urlBase, userId: self.appDelegate.userId)
 
@@ -112,8 +100,8 @@ class NCGroupfolders: NCCollectionViewCommon {
                     }
                     self.reloadDataSource()
                 }
-            } else if error != .success {
-                self.reloadDataSource()
+            } else {
+                self.reloadDataSource(withQueryDB: false)
             }
         }
     }

+ 11 - 10
iOSClient/Login/NCLogin.swift

@@ -400,19 +400,20 @@ class NCLogin: UIViewController, UITextFieldDelegate, NCLoginQRCodeDelegate {
 
                     self.appDelegate.changeAccount(account, userProfile: userProfile)
 
-                    if NCKeychain().intro {
-                        self.dismiss(animated: true)
-                    } else {
-                        NCKeychain().intro = true
-                        if self.presentingViewController == nil {
-                            let viewController = UIStoryboard(name: "Main", bundle: nil).instantiateInitialViewController()
-                            viewController?.modalPresentationStyle = .fullScreen
+                    if self.presentingViewController == nil {
+                        if let viewController = UIStoryboard(name: "Main", bundle: nil).instantiateInitialViewController() {
+                            viewController.modalPresentationStyle = .fullScreen
+                            viewController.view.alpha = 0
                             self.appDelegate.window?.rootViewController = viewController
-                            self.appDelegate.window?.makeKey()
-                        } else {
-                            self.dismiss(animated: true)
+                            self.appDelegate.window?.makeKeyAndVisible()
+                            UIView.animate(withDuration: 0.5) {
+                                viewController.view.alpha = 1
+                            }
                         }
+                    } else {
+                        self.dismiss(animated: true)
                     }
+
                 } else {
 
                     let alertController = UIAlertController(title: NSLocalizedString("_error_", comment: ""), message: error.errorDescription, preferredStyle: .alert)

+ 10 - 15
iOSClient/Login/NCLoginWeb.swift

@@ -298,23 +298,18 @@ extension NCLoginWeb: WKNavigationDelegate {
 
                 self.appDelegate.changeAccount(account, userProfile: userProfile)
 
-                if NCKeychain().intro {
-                    self.dismiss(animated: true)
-                } else {
-                    NCKeychain().intro = true
-                    if self.presentingViewController == nil {
-                        if let viewController = UIStoryboard(name: "Main", bundle: nil).instantiateInitialViewController() {
-                            viewController.modalPresentationStyle = .fullScreen
-                            viewController.view.alpha = 0
-                            self.appDelegate.window?.rootViewController = viewController
-                            self.appDelegate.window?.makeKeyAndVisible()
-                            UIView.animate(withDuration: 0.5) {
-                                viewController.view.alpha = 1
-                            }
+                if self.presentingViewController == nil {
+                    if let viewController = UIStoryboard(name: "Main", bundle: nil).instantiateInitialViewController() {
+                        viewController.modalPresentationStyle = .fullScreen
+                        viewController.view.alpha = 0
+                        self.appDelegate.window?.rootViewController = viewController
+                        self.appDelegate.window?.makeKeyAndVisible()
+                        UIView.animate(withDuration: 0.5) {
+                            viewController.view.alpha = 1
                         }
-                    } else {
-                        self.dismiss(animated: true)
                     }
+                } else {
+                    self.dismiss(animated: true)
                 }
 
             } else {

+ 123 - 177
iOSClient/Main/Collection Common/NCCollectionViewCommon.swift

@@ -48,25 +48,23 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS
     internal var richWorkspaceText: String?
     internal var headerMenu: NCSectionHeaderMenu?
     internal var isSearchingMode: Bool = false
-
     internal var layoutForView: NCDBLayoutForView?
     internal var selectableDataSource: [RealmSwiftObject] { dataSource.getMetadataSourceForAllSections() }
-
     private var autoUploadFileName = ""
     private var autoUploadDirectory = ""
     internal var groupByField = "name"
     internal var providers: [NKSearchProvider]?
     internal var searchResults: [NKSearchResult]?
-
     internal var listLayout: NCListLayout!
     internal var gridLayout: NCGridLayout!
-
     internal var literalSearch: String?
-
     internal var isReloadDataSourceNetworkInProgress: Bool = false
 
-    private var pushed: Bool = false
+    internal var timerNotificationCenter: Timer?
+    internal var notificationReloadDataSource: Int = 0
+    internal var notificationReloadDataSourceNetwork: Int = 0
 
+    private var pushed: Bool = false
     private var tipView: EasyTipView?
     private var isTransitioning: Bool = false
     // DECLARE
@@ -129,7 +127,8 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS
         collectionView.refreshControl = refreshControl
         refreshControl.action(for: .valueChanged) { _ in
             self.dataSource.clearDirectory()
-            self.reloadDataSourceNetwork(isForced: true)
+            NCManageDatabase.shared.cleanEtagDirectory(account: self.appDelegate.account, serverUrl: self.serverUrl)
+            self.reloadDataSourceNetwork()
         }
 
         // Empty
@@ -174,14 +173,15 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS
             collectionView?.collectionViewLayout = gridLayout
         }
 
+        timerNotificationCenter = Timer.scheduledTimer(timeInterval: 1.5, target: self, selector: #selector(notificationCenterEvents), userInfo: nil, repeats: true)
+
         NotificationCenter.default.addObserver(self, selector: #selector(applicationWillResignActive(_:)), name: NSNotification.Name(rawValue: NCGlobal.shared.notificationCenterApplicationWillResignActive), object: nil)
         NotificationCenter.default.addObserver(self, selector: #selector(closeRichWorkspaceWebView), name: NSNotification.Name(rawValue: NCGlobal.shared.notificationCenterCloseRichWorkspaceWebView), object: nil)
         NotificationCenter.default.addObserver(self, selector: #selector(changeStatusFolderE2EE(_:)), name: NSNotification.Name(rawValue: NCGlobal.shared.notificationCenterChangeStatusFolderE2EE), object: nil)
         NotificationCenter.default.addObserver(self, selector: #selector(reloadAvatar(_:)), name: NSNotification.Name(rawValue: NCGlobal.shared.notificationCenterReloadAvatar), object: nil)
 
         NotificationCenter.default.addObserver(self, selector: #selector(reloadDataSource(_:)), name: NSNotification.Name(rawValue: NCGlobal.shared.notificationCenterReloadDataSource), object: nil)
-        NotificationCenter.default.addObserver(self, selector: #selector(reloadDataSourceNetwork), name: NSNotification.Name(rawValue: NCGlobal.shared.notificationCenterReloadDataSourceNetwork), object: nil)
-        NotificationCenter.default.addObserver(self, selector: #selector(reloadDataSourceNetworkForced(_:)), name: NSNotification.Name(rawValue: NCGlobal.shared.notificationCenterReloadDataSourceNetworkForced), object: nil)
+        NotificationCenter.default.addObserver(self, selector: #selector(reloadDataSourceNetwork(_:)), name: NSNotification.Name(rawValue: NCGlobal.shared.notificationCenterReloadDataSourceNetwork), object: nil)
 
         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)
@@ -196,6 +196,7 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS
 
         NotificationCenter.default.addObserver(self, selector: #selector(uploadStartFile(_:)), name: NSNotification.Name(rawValue: NCGlobal.shared.notificationCenterUploadStartFile), object: nil)
         NotificationCenter.default.addObserver(self, selector: #selector(uploadedFile(_:)), name: NSNotification.Name(rawValue: NCGlobal.shared.notificationCenterUploadedFile), object: nil)
+        NotificationCenter.default.addObserver(self, selector: #selector(uploadedLivePhoto(_:)), name: NSNotification.Name(rawValue: NCGlobal.shared.notificationCenterUploadedLivePhoto), object: nil)
         NotificationCenter.default.addObserver(self, selector: #selector(uploadCancelFile(_:)), name: NSNotification.Name(rawValue: NCGlobal.shared.notificationCenterUploadCancelFile), object: nil)
 
         NotificationCenter.default.addObserver(self, selector: #selector(triggerProgressTask(_:)), name: NSNotification.Name(rawValue: NCGlobal.shared.notificationCenterProgressTask), object: nil)
@@ -208,12 +209,8 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS
 
         navigationController?.navigationBar.prefersLargeTitles = true
         navigationController?.setNavigationBarHidden(false, animated: true)
-        setNavigationItem()
-
-        reloadDataSource(isForced: false)
-        if !isSearchingMode {
-            reloadDataSourceNetwork()
-        }
+        navigationController?.setNavigationBarAppearance()
+        setNavigationItems()
 
         // FIXME: iPAD PDF landscape mode iOS 16
         DispatchQueue.main.async {
@@ -231,16 +228,26 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS
 
         NotificationCenter.default.removeObserver(self, name: NSNotification.Name(rawValue: NCGlobal.shared.notificationCenterReloadDataSource), object: nil)
         NotificationCenter.default.removeObserver(self, name: NSNotification.Name(rawValue: NCGlobal.shared.notificationCenterReloadDataSourceNetwork), object: nil)
-        NotificationCenter.default.removeObserver(self, name: NSNotification.Name(rawValue: NCGlobal.shared.notificationCenterReloadDataSourceNetworkForced), object: nil)
 
         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.notificationCenterCreateFolder), object: nil)
+        NotificationCenter.default.removeObserver(self, name: NSNotification.Name(rawValue: NCGlobal.shared.notificationCenterFavoriteFile), object: nil)
+
+        NotificationCenter.default.removeObserver(self, name: NSNotification.Name(rawValue: NCGlobal.shared.notificationCenterDownloadStartFile), object: nil)
+        NotificationCenter.default.removeObserver(self, name: NSNotification.Name(rawValue: NCGlobal.shared.notificationCenterDownloadedFile), object: nil)
+        NotificationCenter.default.removeObserver(self, name: NSNotification.Name(rawValue: NCGlobal.shared.notificationCenterDownloadCancelFile), object: nil)
+
+        NotificationCenter.default.removeObserver(self, name: NSNotification.Name(rawValue: NCGlobal.shared.notificationCenterUploadStartFile), object: nil)
+        NotificationCenter.default.removeObserver(self, name: NSNotification.Name(rawValue: NCGlobal.shared.notificationCenterUploadedFile), object: nil)
+        NotificationCenter.default.removeObserver(self, name: NSNotification.Name(rawValue: NCGlobal.shared.notificationCenterUploadedLivePhoto), object: nil)
+        NotificationCenter.default.removeObserver(self, name: NSNotification.Name(rawValue: NCGlobal.shared.notificationCenterUploadCancelFile), object: nil)
 
         NotificationCenter.default.removeObserver(self, name: NSNotification.Name(rawValue: NCGlobal.shared.notificationCenterProgressTask), object: nil)
 
+        timerNotificationCenter?.invalidate()
         pushed = false
 
         // REQUEST
@@ -276,6 +283,14 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS
 
     // MARK: - NotificationCenter
 
+    @objc func notificationCenterEvents() {
+        if notificationReloadDataSource > 0 {
+            print("notificationReloadDataSource: \(notificationReloadDataSource)")
+            reloadDataSource()
+            notificationReloadDataSource = 0
+        }
+    }
+
     @objc func applicationWillResignActive(_ notification: NSNotification) {
         self.refreshControl.endRefreshing()
     }
@@ -290,7 +305,7 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS
               let error = userInfo["error"] as? NKError,
               error.errorCode != NCGlobal.shared.errorNotModified else { return }
 
-        setNavigationItem()
+        setNavigationItems()
     }
 
     @objc func changeTheming() {
@@ -298,18 +313,17 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS
     }
 
     @objc func reloadDataSource(_ notification: NSNotification) {
-        reloadDataSource()
+        notificationReloadDataSource += 1
     }
 
-    @objc func reloadDataSourceNetworkForced(_ notification: NSNotification) {
-
+    @objc func reloadDataSourceNetwork(_ notification: NSNotification) {
         if !isSearchingMode {
-            reloadDataSourceNetwork(isForced: true)
+            reloadDataSourceNetwork()
         }
     }
 
     @objc func changeStatusFolderE2EE(_ notification: NSNotification) {
-        reloadDataSource()
+        notificationReloadDataSource += 1
     }
 
     @objc func closeRichWorkspaceWebView() {
@@ -321,8 +335,7 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS
         guard let userInfo = notification.userInfo as NSDictionary?,
               let error = userInfo["error"] as? NKError else { return }
 
-        self.queryDB(isForced: true)
-        self.collectionView?.reloadData()
+        notificationReloadDataSource += 1
 
         if error != .success {
             NCContentPresenter().showError(error: error)
@@ -350,7 +363,7 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS
               account == appDelegate.account
         else { return }
 
-        reloadDataSourceNetwork(isForced: true)
+        reloadDataSourceNetwork()
     }
 
     @objc func createFolder(_ notification: NSNotification) {
@@ -364,29 +377,25 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS
               let withPush = userInfo["withPush"] as? Bool
         else { return }
 
-        if let metadata = NCManageDatabase.shared.getMetadataFromOcId(ocId) {
-            reloadDataSource()
-            if withPush {
-                pushMetadata(metadata)
-            }
+        notificationReloadDataSource += 1
+
+        if withPush, let metadata = NCManageDatabase.shared.getMetadataFromOcId(ocId) {
+            pushMetadata(metadata)
         }
     }
 
     @objc func favoriteFile(_ notification: NSNotification) {
 
+        if self is NCFavorite {
+            return notificationReloadDataSource += 1
+        }
+
         guard let userInfo = notification.userInfo as NSDictionary?,
-              let ocId = userInfo["ocId"] as? String,
               let serverUrl = userInfo["serverUrl"] as? String,
               serverUrl == self.serverUrl
-        else {
-            if self is NCFavorite {
-                reloadDataSource()
-            }
-            return
-        }
+        else { return }
 
-        dataSource.reloadMetadata(ocId: ocId)
-        collectionView?.reloadData()
+        notificationReloadDataSource += 1
     }
 
     @objc func downloadStartFile(_ notification: NSNotification) {
@@ -398,7 +407,7 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS
               account == appDelegate.account
         else { return }
 
-        reloadDataSource()
+        self.notificationReloadDataSource += 1
     }
 
     @objc func downloadedFile(_ notification: NSNotification) {
@@ -407,32 +416,27 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS
               let serverUrl = userInfo["serverUrl"] as? String,
               serverUrl == self.serverUrl,
               let account = userInfo["account"] as? String,
-              account == appDelegate.account
+              account == appDelegate.account,
+              let error = userInfo["error"] as? NKError
         else { return }
 
-        reloadDataSource()
+        if error != .success {
+            NCContentPresenter().showError(error: error)
+        }
+
+        notificationReloadDataSource += 1
     }
 
     @objc func downloadCancelFile(_ notification: NSNotification) {
 
         guard let userInfo = notification.userInfo as NSDictionary?,
-              let ocId = userInfo["ocId"] as? String,
               let serverUrl = userInfo["serverUrl"] as? String,
               serverUrl == self.serverUrl,
               let account = userInfo["account"] as? String,
               account == appDelegate.account
         else { return }
 
-        let (indexPath, sameSections) = dataSource.reloadMetadata(ocId: ocId)
-        if let indexPath = indexPath {
-            if sameSections && (indexPath.section < collectionView.numberOfSections && indexPath.row < collectionView.numberOfItems(inSection: indexPath.section)) {
-                collectionView?.reloadItems(at: [indexPath])
-            } else {
-                self.collectionView?.reloadData()
-            }
-        } else {
-            reloadDataSource()
-        }
+        notificationReloadDataSource += 1
     }
 
     @objc func uploadStartFile(_ notification: NSNotification) {
@@ -440,19 +444,19 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS
         guard let userInfo = notification.userInfo as NSDictionary?,
               let ocId = userInfo["ocId"] as? String,
               let serverUrl = userInfo["serverUrl"] as? String,
-              let account = userInfo["account"] as? String
+              let account = userInfo["account"] as? String,
+              !isSearchingMode,
+              let metadata = NCManageDatabase.shared.getMetadataFromOcId(ocId)
         else { return }
 
-        guard !isSearchingMode, let metadata = NCManageDatabase.shared.getMetadataFromOcId(ocId) else { return }
-
         // Header view trasfer
         if metadata.isTransferInForeground {
             NCNetworking.shared.transferInForegorund = NCNetworking.TransferInForegorund(ocId: ocId, progress: 0)
-            self.collectionView?.reloadData()
+            DispatchQueue.main.async { self.collectionView?.reloadData() }
         }
 
         if serverUrl == self.serverUrl, account == appDelegate.account {
-            reloadDataSource()
+            notificationReloadDataSource += 1
         }
     }
 
@@ -466,14 +470,26 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS
 
         if ocIdTemp == NCNetworking.shared.transferInForegorund?.ocId {
             NCNetworking.shared.transferInForegorund = nil
-            self.collectionView?.reloadData()
+            DispatchQueue.main.async { self.collectionView?.reloadData() }
         }
 
         if account == appDelegate.account, serverUrl == self.serverUrl {
-            reloadDataSource()
+            notificationReloadDataSource += 1
         }
     }
 
+    @objc func uploadedLivePhoto(_ notification: NSNotification) {
+
+        guard let userInfo = notification.userInfo as NSDictionary?,
+              let serverUrl = userInfo["serverUrl"] as? String,
+              serverUrl == self.serverUrl,
+              let account = userInfo["account"] as? String,
+              account == appDelegate.account
+        else { return }
+
+        notificationReloadDataSource += 1
+    }
+
     @objc func uploadCancelFile(_ notification: NSNotification) {
 
         guard let userInfo = notification.userInfo as NSDictionary?,
@@ -484,11 +500,11 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS
 
         if ocId == NCNetworking.shared.transferInForegorund?.ocId {
             NCNetworking.shared.transferInForegorund = nil
-            self.collectionView?.reloadData()
+            DispatchQueue.main.async { self.collectionView?.reloadData() }
         }
 
         if account == appDelegate.account, serverUrl == self.serverUrl {
-            reloadDataSource()
+            notificationReloadDataSource += 1
         }
     }
 
@@ -504,22 +520,22 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS
         let chunk: Int = userInfo["chunk"] as? Int ?? 0
         let e2eEncrypted: Bool = userInfo["e2eEncrypted"] as? Bool ?? false
 
-        // Header Transfer
-        if headerMenuTransferView && (chunk > 0 || e2eEncrypted) {
-            if NCNetworking.shared.transferInForegorund?.ocId == ocId {
-                NCNetworking.shared.transferInForegorund?.progress = progressNumber.floatValue
-            } else {
-                NCNetworking.shared.transferInForegorund = NCNetworking.TransferInForegorund(ocId: ocId, progress: progressNumber.floatValue)
-                collectionView.reloadData()
+        if self.headerMenuTransferView && (chunk > 0 || e2eEncrypted) {
+            DispatchQueue.main.async {
+                if NCNetworking.shared.transferInForegorund?.ocId == ocId {
+                    NCNetworking.shared.transferInForegorund?.progress = progressNumber.floatValue
+                } else {
+                    NCNetworking.shared.transferInForegorund = NCNetworking.TransferInForegorund(ocId: ocId, progress: progressNumber.floatValue)
+                    self.collectionView.reloadData()
+                }
+                self.headerMenu?.progressTransfer.progress = progressNumber.floatValue
             }
-            self.headerMenu?.progressTransfer.progress = progressNumber.floatValue
-        }
-
-        let status = userInfo["status"] as? Int ?? NCGlobal.shared.metadataStatusNormal
-
-        if let (indexPath, _) = self.dataSource.getIndexPathMetadata(ocId: ocId) as? (IndexPath, NCMetadataForSection?),
-           let cell = collectionView?.cellForItem(at: indexPath) {
-            if let cell = cell as? NCCellProtocol {
+        } else {
+            guard let indexPath = self.dataSource.getIndexPathMetadata(ocId: ocId).indexPath else { return }
+            let status = userInfo["status"] as? Int ?? NCGlobal.shared.metadataStatusNormal
+            DispatchQueue.main.async {
+                guard let cell = self.collectionView?.cellForItem(at: indexPath),
+                      let cell = cell as? NCCellProtocol else { return }
                 if progressNumber.floatValue == 1 && !(cell is NCTransferCell) {
                     cell.fileProgressView?.isHidden = true
                     cell.fileProgressView?.progress = .zero
@@ -533,13 +549,13 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS
                     cell.fileProgressView?.isHidden = false
                     cell.fileProgressView?.progress = progressNumber.floatValue
                     cell.setButtonMore(named: NCGlobal.shared.buttonMoreStop, image: NCImageCache.images.buttonStop)
-                    if status == NCGlobal.shared.metadataStatusInDownload {
-                        cell.fileInfoLabel?.text = utilityFileSystem.transformedSize(totalBytesExpected) + " - ↓ " + utilityFileSystem.transformedSize(totalBytes)
-                    } else if status == NCGlobal.shared.metadataStatusInUpload {
+                    if status == NCGlobal.shared.metadataStatusDownloading {
+                        cell.fileInfoLabel?.text = self.utilityFileSystem.transformedSize(totalBytesExpected) + " - ↓ " + self.utilityFileSystem.transformedSize(totalBytes)
+                    } else if status == NCGlobal.shared.metadataStatusUploading {
                         if totalBytes > 0 {
-                            cell.fileInfoLabel?.text = utilityFileSystem.transformedSize(totalBytesExpected) + " - ↑ " + utilityFileSystem.transformedSize(totalBytes)
+                            cell.fileInfoLabel?.text = self.utilityFileSystem.transformedSize(totalBytesExpected) + " - ↑ " + self.utilityFileSystem.transformedSize(totalBytes)
                         } else {
-                            cell.fileInfoLabel?.text = utilityFileSystem.transformedSize(totalBytesExpected) + " - ↑ …"
+                            cell.fileInfoLabel?.text = self.utilityFileSystem.transformedSize(totalBytesExpected) + " - ↑ …"
                         }
                     }
                 }
@@ -550,7 +566,6 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS
     // MARK: - Tip
 
     func showTip() {
-
         if self is NCFiles, self.view.window != nil, !NCBrandOptions.shared.disable_multiaccount, !NCBrandOptions.shared.disable_manage_account, self.serverUrl == utilityFileSystem.getHomeServer(urlBase: appDelegate.urlBase, userId: appDelegate.userId), let view = self.navigationItem.leftBarButtonItem?.customView {
             if !NCManageDatabase.shared.tipExists(NCGlobal.shared.tipNCCollectionViewCommonAccountRequest), !NCManageDatabase.shared.getAllAccountOrderAlias().isEmpty {
                 self.tipView?.show(forView: view)
@@ -560,7 +575,7 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS
 
     // MARK: - Layout
 
-    func setNavigationItem() {
+    func setNavigationItems() {
 
         self.setNavigationRightItems()
         navigationItem.title = titleCurrentFolder
@@ -579,20 +594,6 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS
         let button = UIButton(type: .custom)
         button.setImage(image, for: .normal)
 
-        if serverUrl == utilityFileSystem.getHomeServer(urlBase: appDelegate.urlBase, userId: appDelegate.userId) {
-
-            var titleButton = "  "
-
-            if getNavigationTitle() == activeAccount?.alias {
-                titleButton = ""
-            } else {
-                titleButton += activeAccount?.displayName ?? ""
-            }
-
-            button.setTitle(titleButton, for: .normal)
-            button.setTitleColor(.systemBlue, for: .normal)
-        }
-
         button.semanticContentAttribute = .forceLeftToRight
         button.sizeToFit()
         button.action(for: .touchUpInside) { _ in
@@ -622,13 +623,14 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS
                 self.dismissTip()
             }
         }
+
         navigationItem.setLeftBarButton(UIBarButtonItem(customView: button), animated: true)
         navigationItem.leftItemsSupplementBackButton = true
-        if titlePreviusFolder == nil {
-            navigationController?.navigationBar.topItem?.title = getNavigationTitle()
-        } else {
+
+        if titlePreviusFolder != nil {
             navigationController?.navigationBar.topItem?.title = titlePreviusFolder
         }
+
         navigationItem.title = titleCurrentFolder
     }
 
@@ -699,12 +701,10 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS
 
         DispatchQueue.global().async {
             NCNetworking.shared.cancelUnifiedSearchFiles()
-
             self.isSearchingMode = false
             self.literalSearch = ""
             self.providers?.removeAll()
             self.dataSource.clearDataSource()
-
             self.reloadDataSource()
         }
     }
@@ -883,10 +883,10 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS
 
     // MARK: - DataSource + NC Endpoint
 
-    func queryDB(isForced: Bool) { }
+    func queryDB() { }
 
-    @objc func reloadDataSource(isForced: Bool = true) {
-        guard !appDelegate.account.isEmpty else { return }
+    @objc func reloadDataSource(withQueryDB: Bool = true) {
+        guard !appDelegate.account.isEmpty, !self.isSearchingMode else { return }
 
         // get auto upload folder
         autoUploadFileName = NCManageDatabase.shared.getAccountAutoUploadFileName()
@@ -901,16 +901,26 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS
         } else {
             groupByField = "name"
         }
+
+        DispatchQueue.global().async {
+            if withQueryDB { self.queryDB() }
+            DispatchQueue.main.async {
+                self.isReloadDataSourceNetworkInProgress = false
+                self.refreshControl.endRefreshing()
+                self.collectionView.reloadData()
+            }
+        }
     }
 
-    @objc func reloadDataSourceNetwork(isForced: Bool = false) { }
+    @objc func reloadDataSourceNetwork() {
+
+        isReloadDataSourceNetworkInProgress = true
+        collectionView?.reloadData()
+    }
 
     @objc func networkSearch() {
         guard !appDelegate.account.isEmpty, let literalSearch = literalSearch, !literalSearch.isEmpty
-        else {
-            self.refreshControl.endRefreshing()
-            return
-        }
+        else { return self.refreshControl.endRefreshing() }
 
         isReloadDataSourceNetworkInProgress = true
         self.dataSource.clearDataSource()
@@ -982,65 +992,6 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS
         }
     }
 
-    @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, 0, false, error)
-                return
-            }
-
-            if let metadataFolder = metadataFolder {
-                tableDirectory = NCManageDatabase.shared.setDirectory(serverUrl: self.serverUrl, richWorkspace: metadataFolder.richWorkspace, account: account)
-            }
-
-            if isForced || tableDirectory?.etag != metadataFolder?.etag || metadataFolder?.e2eEncrypted ?? true {
-                NCNetworking.shared.readFolder(serverUrl: self.serverUrl, account: self.appDelegate.account) { _, metadataFolder, metadatas, metadatasChangedCount, metadatasChanged, error in
-                    guard error == .success else {
-                        completion(tableDirectory, nil, 0, false, error)
-                        return
-                    }
-                    self.metadataFolder = metadataFolder
-                    // E2EE
-                    if let metadataFolder = metadataFolder,
-                       metadataFolder.e2eEncrypted,
-                       NCKeychain().isEndToEndEnabled(account: self.appDelegate.account),
-                       !NCNetworkingE2EE().isInUpload(account: self.appDelegate.account, serverUrl: self.serverUrl) {
-                        let lock = NCManageDatabase.shared.getE2ETokenLock(account: self.appDelegate.account, serverUrl: self.serverUrl)
-                        NextcloudKit.shared.getE2EEMetadata(fileId: metadataFolder.ocId, e2eToken: lock?.e2eToken) { _, e2eMetadata, signature, _, error in
-                            if error == .success, let e2eMetadata = e2eMetadata {
-                                let error = NCEndToEndMetadata().decodeMetadata(e2eMetadata, signature: signature, serverUrl: self.serverUrl, account: self.appDelegate.account, urlBase: self.appDelegate.urlBase, userId: self.appDelegate.userId)
-                                if error == .success {
-                                    self.reloadDataSource()
-                                } else {
-                                    NCContentPresenter().showError(error: error)
-                                }
-                            } else if error.errorCode == NCGlobal.shared.errorResourceNotFound {
-                                // no metadata found, send a new metadata
-                                Task {
-                                    let serverUrl = metadataFolder.serverUrl + "/" + metadataFolder.fileName
-                                    let error = await NCNetworkingE2EE().uploadMetadata(account: metadataFolder.account, serverUrl: serverUrl, userId: metadataFolder.userId)
-                                    if error != .success {
-                                        NCContentPresenter().showError(error: error)
-                                    }
-                                }
-                            } else {
-                                NCContentPresenter().showError(error: NKError(errorCode: NCGlobal.shared.errorE2EEKeyDecodeMetadata, errorDescription: "_e2e_error_"))
-                            }
-                            completion(tableDirectory, metadatas, metadatasChangedCount, metadatasChanged, error)
-                        }
-                    } else {
-                        completion(tableDirectory, metadatas, metadatasChangedCount, metadatasChanged, error)
-                    }
-                }
-            } else {
-                completion(tableDirectory, nil, 0, false, NKError())
-            }
-        }
-    }
-
     // MARK: - Push metadata
 
     func pushMetadata(_ metadata: tableMetadata) {
@@ -1131,8 +1082,8 @@ extension NCCollectionViewCommon: UICollectionViewDelegate {
 
             if utilityFileSystem.fileProviderStorageExists(metadata) {
                 NCViewer().view(viewController: self, metadata: metadata, metadatas: [metadata], imageIcon: imageIcon)
-            } else if NextcloudKit.shared.isNetworkReachable() {
-                NCNetworking.shared.download(metadata: metadata, selector: NCGlobal.shared.selectorLoadFileView) { _, _ in }
+            } else if NextcloudKit.shared.isNetworkReachable(), let metadata = NCManageDatabase.shared.setMetadataSessionInWaitDownload(ocId: metadata.ocId, selector: NCGlobal.shared.selectorLoadFileView) {
+                NCNetworking.shared.download(metadata: metadata, withNotificationProgressTask: true)
             } else {
                 let error = NKError(errorCode: NCGlobal.shared.errorOffline, errorDescription: "_go_online_")
                 NCContentPresenter().showInfo(error: error)
@@ -1416,16 +1367,11 @@ extension NCCollectionViewCommon: UICollectionViewDataSource {
         switch metadata.status {
         case NCGlobal.shared.metadataStatusWaitDownload:
             cell.fileInfoLabel?.text = utilityFileSystem.transformedSize(metadata.size) + " - " + NSLocalizedString("_status_wait_download_", comment: "")
-        case NCGlobal.shared.metadataStatusInDownload:
-            cell.fileInfoLabel?.text = utilityFileSystem.transformedSize(metadata.size) + " - " + NSLocalizedString("_status_in_download_", comment: "")
         case NCGlobal.shared.metadataStatusDownloading:
             cell.fileInfoLabel?.text = utilityFileSystem.transformedSize(metadata.size) + " - ↓ …"
         case NCGlobal.shared.metadataStatusWaitUpload:
             cell.fileInfoLabel?.text = utilityFileSystem.transformedSize(metadata.size) + " - " + NSLocalizedString("_status_wait_upload_", comment: "")
             cell.fileLocalImage?.image = nil
-        case NCGlobal.shared.metadataStatusInUpload:
-            cell.fileInfoLabel?.text = utilityFileSystem.transformedSize(metadata.size) + " - " + NSLocalizedString("_status_in_upload_", comment: "")
-            cell.fileLocalImage?.image = nil
         case NCGlobal.shared.metadataStatusUploading:
             cell.fileInfoLabel?.text = utilityFileSystem.transformedSize(metadata.size) + " - ↑ …"
             cell.fileLocalImage?.image = nil

+ 33 - 33
iOSClient/Main/Collection Common/NCListCell.xib

@@ -1,9 +1,9 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="21701" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
+<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="22154" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
     <device id="retina6_0" orientation="landscape" appearance="light"/>
     <dependencies>
         <deployment identifier="iOS"/>
-        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="21678"/>
+        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="22130"/>
         <capability name="Safe area layout guides" minToolsVersion="9.0"/>
         <capability name="System colors in document resources" minToolsVersion="11.0"/>
         <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
@@ -19,35 +19,35 @@
                 <autoresizingMask key="autoresizingMask"/>
                 <subviews>
                     <imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFill" translatesAutoresizingMaskIntoConstraints="NO" id="w2m-Vw-hpd" userLabel="ImageItem">
-                        <rect key="frame" x="57" y="43.666666666666664" width="40" height="39.999999999999993"/>
+                        <rect key="frame" x="57" y="54" width="40" height="40"/>
                         <constraints>
                             <constraint firstAttribute="height" constant="40" id="Dpd-Xj-z4U"/>
                             <constraint firstAttribute="width" constant="40" id="v0e-MW-EeE"/>
                         </constraints>
                     </imageView>
                     <imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="7Q9-Tv-9yo" userLabel="imageStatus">
-                        <rect key="frame" x="52" y="73.666666666666671" width="15" height="15"/>
+                        <rect key="frame" x="52" y="84" width="15" height="15"/>
                         <constraints>
                             <constraint firstAttribute="width" constant="15" id="f8p-9B-Rgw"/>
                             <constraint firstAttribute="height" constant="15" id="ndy-wW-xdL"/>
                         </constraints>
                     </imageView>
                     <imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="C4K-Nv-phA" userLabel="imageFavorite">
-                        <rect key="frame" x="87" y="38.666666666666664" width="15" height="15"/>
+                        <rect key="frame" x="87" y="49" width="15" height="15"/>
                         <constraints>
                             <constraint firstAttribute="width" constant="15" id="hXC-b9-Q2V"/>
                             <constraint firstAttribute="height" constant="15" id="mPH-zc-eH5"/>
                         </constraints>
                     </imageView>
                     <imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="AyA-hP-r6w" userLabel="imageSelect">
-                        <rect key="frame" x="57" y="51" width="25" height="25"/>
+                        <rect key="frame" x="10" y="61.666666666666657" width="25" height="25"/>
                         <constraints>
                             <constraint firstAttribute="width" constant="25" id="bIF-gu-6Jj"/>
                             <constraint firstAttribute="height" constant="25" id="nJa-oj-gcQ"/>
                         </constraints>
                     </imageView>
                     <imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="H4E-G2-C1H" userLabel="imageLocal">
-                        <rect key="frame" x="87" y="73.666666666666671" width="15" height="15"/>
+                        <rect key="frame" x="87" y="84" width="15" height="15"/>
                         <constraints>
                             <constraint firstAttribute="width" constant="15" id="BEs-Rd-5Ov"/>
                             <constraint firstAttribute="height" constant="15" id="N8h-3R-JpE"/>
@@ -66,7 +66,7 @@
                         <nil key="highlightedColor"/>
                     </label>
                     <button opaque="NO" alpha="0.29999999999999999" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="o4u-0K-Qpt" userLabel="buttonShare">
-                        <rect key="frame" x="537" y="34.666666666666664" width="40" height="57.999999999999993"/>
+                        <rect key="frame" x="584" y="45" width="40" height="58"/>
                         <color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
                         <constraints>
                             <constraint firstAttribute="height" constant="58" id="WOg-y5-5UA"/>
@@ -77,14 +77,14 @@
                         </connections>
                     </button>
                     <imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="jc6-Vg-TaS" userLabel="imageShared">
-                        <rect key="frame" x="542" y="48.666666666666664" width="30" height="29.999999999999993"/>
+                        <rect key="frame" x="589" y="59" width="30" height="30"/>
                         <constraints>
                             <constraint firstAttribute="height" constant="30" id="Cvy-nZ-zyD"/>
                             <constraint firstAttribute="width" constant="30" id="jfe-Fg-vA8"/>
                         </constraints>
                     </imageView>
                     <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="yhy-xd-w5C" userLabel="buttonMore">
-                        <rect key="frame" x="582" y="33.666666666666664" width="40" height="59.999999999999993"/>
+                        <rect key="frame" x="629" y="44" width="40" height="60"/>
                         <constraints>
                             <constraint firstAttribute="width" constant="40" id="ZgH-mI-l2k"/>
                             <constraint firstAttribute="height" constant="60" id="woC-64-Tyc"/>
@@ -94,17 +94,17 @@
                         </connections>
                     </button>
                     <imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="dgL-g5-Nkc" userLabel="imageMore">
-                        <rect key="frame" x="589.66666666666663" y="51" width="25" height="25"/>
+                        <rect key="frame" x="636.66666666666663" y="61.666666666666657" width="25" height="25"/>
                         <constraints>
                             <constraint firstAttribute="width" constant="25" id="05P-NL-pd8"/>
                             <constraint firstAttribute="height" constant="25" id="Jet-eo-x1M"/>
                         </constraints>
                     </imageView>
                     <progressView hidden="YES" opaque="NO" contentMode="scaleToFill" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="m2p-oJ-j15">
-                        <rect key="frame" x="107" y="116" width="425" height="4"/>
+                        <rect key="frame" x="107" y="137" width="472" height="4"/>
                     </progressView>
                     <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="Egg-cb-EhZ" userLabel="separator">
-                        <rect key="frame" x="97" y="126" width="525" height="1"/>
+                        <rect key="frame" x="97" y="147" width="572" height="1"/>
                         <color key="backgroundColor" white="0.0" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
                         <color key="tintColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
                         <constraints>
@@ -112,7 +112,7 @@
                         </constraints>
                     </view>
                     <label hidden="YES" opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="tag0" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="qnc-hI-Z9r" customClass="PaddedAndBorderedLabel" customModule="Nextcloud" customModuleProvider="target">
-                        <rect key="frame" x="107" y="103.66666666666667" width="26" height="14.333333333333329"/>
+                        <rect key="frame" x="107" y="122.66666666666667" width="36" height="16.333333333333329"/>
                         <fontDescription key="fontDescription" type="system" pointSize="12"/>
                         <color key="textColor" systemColor="systemGrayColor"/>
                         <nil key="highlightedColor"/>
@@ -141,7 +141,7 @@
                         </userDefinedRuntimeAttributes>
                     </label>
                     <label hidden="YES" opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="tag1" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="jUe-8q-VJd" customClass="PaddedAndBorderedLabel" customModule="Nextcloud" customModuleProvider="target">
-                        <rect key="frame" x="138" y="103.66666666666667" width="24" height="14.333333333333329"/>
+                        <rect key="frame" x="148" y="122.66666666666667" width="34" height="16.333333333333329"/>
                         <fontDescription key="fontDescription" type="system" pointSize="12"/>
                         <color key="textColor" systemColor="systemGrayColor"/>
                         <nil key="highlightedColor"/>
@@ -174,38 +174,38 @@
             <viewLayoutGuide key="safeArea" id="Gu8-oz-zWa"/>
             <constraints>
                 <constraint firstItem="jUe-8q-VJd" firstAttribute="centerY" secondItem="qnc-hI-Z9r" secondAttribute="centerY" id="2Z4-Yh-1lR"/>
-                <constraint firstItem="Gu8-oz-zWa" firstAttribute="trailing" secondItem="m2p-oJ-j15" secondAttribute="trailing" constant="90" id="2zI-li-v77"/>
+                <constraint firstAttribute="trailing" secondItem="m2p-oJ-j15" secondAttribute="trailing" constant="90" id="2zI-li-v77"/>
                 <constraint firstItem="H4E-G2-C1H" firstAttribute="leading" secondItem="w2m-Vw-hpd" secondAttribute="trailing" constant="-10" id="6fN-Jc-WID"/>
-                <constraint firstItem="Gu8-oz-zWa" firstAttribute="bottom" secondItem="Egg-cb-EhZ" secondAttribute="bottom" id="81D-sw-EaX"/>
+                <constraint firstAttribute="bottom" secondItem="Egg-cb-EhZ" secondAttribute="bottom" id="81D-sw-EaX"/>
                 <constraint firstItem="AXX-71-9Q6" firstAttribute="leading" secondItem="w2m-Vw-hpd" secondAttribute="trailing" constant="10" id="Bxx-kv-KT3"/>
                 <constraint firstItem="w2m-Vw-hpd" firstAttribute="top" secondItem="C4K-Nv-phA" secondAttribute="bottom" constant="-10" id="DB1-jf-rpE"/>
-                <constraint firstItem="o4u-0K-Qpt" firstAttribute="centerY" secondItem="Gu8-oz-zWa" secondAttribute="centerY" id="HFM-sM-wJr"/>
+                <constraint firstItem="o4u-0K-Qpt" firstAttribute="centerY" secondItem="jxV-Pk-fPt" secondAttribute="centerY" id="HFM-sM-wJr"/>
                 <constraint firstItem="Egg-cb-EhZ" firstAttribute="leading" secondItem="w2m-Vw-hpd" secondAttribute="trailing" id="JCm-UU-Pxu"/>
                 <constraint firstItem="dgL-g5-Nkc" firstAttribute="centerY" secondItem="yhy-xd-w5C" secondAttribute="centerY" id="OMy-Cu-HAx"/>
                 <constraint firstItem="UtT-L6-mgW" firstAttribute="leading" secondItem="w2m-Vw-hpd" secondAttribute="trailing" constant="10" id="PQ8-0b-fLa"/>
-                <constraint firstItem="AyA-hP-r6w" firstAttribute="leading" secondItem="Gu8-oz-zWa" secondAttribute="leading" constant="10" id="RYl-cO-cCN"/>
-                <constraint firstItem="Gu8-oz-zWa" firstAttribute="bottom" secondItem="m2p-oJ-j15" secondAttribute="bottom" constant="7" id="SYv-gc-ahx"/>
+                <constraint firstItem="AyA-hP-r6w" firstAttribute="leading" secondItem="jxV-Pk-fPt" secondAttribute="leading" constant="10" id="RYl-cO-cCN"/>
+                <constraint firstAttribute="bottom" secondItem="m2p-oJ-j15" secondAttribute="bottom" constant="7" id="SYv-gc-ahx"/>
                 <constraint firstItem="C4K-Nv-phA" firstAttribute="leading" secondItem="w2m-Vw-hpd" secondAttribute="trailing" constant="-10" id="Sof-wy-toF"/>
-                <constraint firstItem="Gu8-oz-zWa" firstAttribute="trailing" secondItem="UtT-L6-mgW" secondAttribute="trailing" constant="90" id="Tq4-bB-YMV"/>
+                <constraint firstAttribute="trailing" secondItem="UtT-L6-mgW" secondAttribute="trailing" constant="137" id="Tq4-bB-YMV"/>
                 <constraint firstItem="H4E-G2-C1H" firstAttribute="top" secondItem="w2m-Vw-hpd" secondAttribute="bottom" constant="-10" id="UWI-r9-vcA"/>
                 <constraint firstItem="dgL-g5-Nkc" firstAttribute="centerX" secondItem="yhy-xd-w5C" secondAttribute="centerX" id="VSJ-7R-Srk"/>
-                <constraint firstItem="Gu8-oz-zWa" firstAttribute="bottom" secondItem="qnc-hI-Z9r" secondAttribute="bottom" constant="9" id="XTs-Qg-kiX"/>
+                <constraint firstAttribute="bottom" secondItem="qnc-hI-Z9r" secondAttribute="bottom" constant="9" id="XTs-Qg-kiX"/>
                 <constraint firstItem="7Q9-Tv-9yo" firstAttribute="top" secondItem="w2m-Vw-hpd" secondAttribute="bottom" constant="-10" id="XbB-4a-WpA"/>
-                <constraint firstItem="yhy-xd-w5C" firstAttribute="centerY" secondItem="Gu8-oz-zWa" secondAttribute="centerY" id="ZO7-Ny-L3I"/>
+                <constraint firstItem="yhy-xd-w5C" firstAttribute="centerY" secondItem="jxV-Pk-fPt" secondAttribute="centerY" id="ZO7-Ny-L3I"/>
                 <constraint firstItem="m2p-oJ-j15" firstAttribute="leading" secondItem="w2m-Vw-hpd" secondAttribute="trailing" constant="10" id="Zyr-qM-9qP"/>
                 <constraint firstAttribute="bottom" secondItem="AXX-71-9Q6" secondAttribute="bottom" constant="13" id="d06-sn-I3Y"/>
                 <constraint firstItem="jc6-Vg-TaS" firstAttribute="centerX" secondItem="o4u-0K-Qpt" secondAttribute="centerX" id="fAq-0d-u57"/>
                 <constraint firstItem="jUe-8q-VJd" firstAttribute="leading" secondItem="qnc-hI-Z9r" secondAttribute="trailing" constant="5" id="jMG-V1-hgF"/>
-                <constraint firstItem="Gu8-oz-zWa" firstAttribute="trailing" secondItem="Egg-cb-EhZ" secondAttribute="trailing" id="k8f-bU-D6I"/>
+                <constraint firstAttribute="trailing" secondItem="Egg-cb-EhZ" secondAttribute="trailing" id="k8f-bU-D6I"/>
                 <constraint firstItem="qnc-hI-Z9r" firstAttribute="leading" secondItem="w2m-Vw-hpd" secondAttribute="trailing" constant="10" id="l6K-6H-QIr"/>
-                <constraint firstItem="w2m-Vw-hpd" firstAttribute="leading" secondItem="Gu8-oz-zWa" secondAttribute="leading" constant="10" id="mBb-ff-7HD"/>
+                <constraint firstItem="w2m-Vw-hpd" firstAttribute="leading" secondItem="jxV-Pk-fPt" secondAttribute="leading" constant="57" id="mBb-ff-7HD"/>
                 <constraint firstItem="w2m-Vw-hpd" firstAttribute="leading" secondItem="7Q9-Tv-9yo" secondAttribute="trailing" constant="-10" id="mon-aq-gcP"/>
-                <constraint firstItem="UtT-L6-mgW" firstAttribute="top" secondItem="Gu8-oz-zWa" secondAttribute="top" constant="13" id="nrY-2F-QZ2"/>
-                <constraint firstItem="Gu8-oz-zWa" firstAttribute="trailing" secondItem="AXX-71-9Q6" secondAttribute="trailing" constant="90" id="p0M-zU-aDG"/>
-                <constraint firstItem="w2m-Vw-hpd" firstAttribute="centerY" secondItem="Gu8-oz-zWa" secondAttribute="centerY" id="qKl-4Y-m5t"/>
-                <constraint firstItem="Gu8-oz-zWa" firstAttribute="trailing" secondItem="yhy-xd-w5C" secondAttribute="trailing" id="s2S-RP-cw5"/>
-                <constraint firstItem="AyA-hP-r6w" firstAttribute="centerY" secondItem="Gu8-oz-zWa" secondAttribute="centerY" id="sJp-0x-bdC"/>
-                <constraint firstItem="Gu8-oz-zWa" firstAttribute="trailing" secondItem="o4u-0K-Qpt" secondAttribute="trailing" constant="45" id="tOD-Sd-Uhy"/>
+                <constraint firstItem="UtT-L6-mgW" firstAttribute="top" secondItem="jxV-Pk-fPt" secondAttribute="top" constant="13" id="nrY-2F-QZ2"/>
+                <constraint firstAttribute="trailing" secondItem="AXX-71-9Q6" secondAttribute="trailing" constant="137" id="p0M-zU-aDG"/>
+                <constraint firstItem="w2m-Vw-hpd" firstAttribute="centerY" secondItem="jxV-Pk-fPt" secondAttribute="centerY" id="qKl-4Y-m5t"/>
+                <constraint firstAttribute="trailing" secondItem="yhy-xd-w5C" secondAttribute="trailing" id="s2S-RP-cw5"/>
+                <constraint firstItem="AyA-hP-r6w" firstAttribute="centerY" secondItem="jxV-Pk-fPt" secondAttribute="centerY" id="sJp-0x-bdC"/>
+                <constraint firstAttribute="trailing" secondItem="o4u-0K-Qpt" secondAttribute="trailing" constant="45" id="tOD-Sd-Uhy"/>
                 <constraint firstItem="jc6-Vg-TaS" firstAttribute="centerY" secondItem="o4u-0K-Qpt" secondAttribute="centerY" id="xnq-6u-TXH"/>
             </constraints>
             <size key="customSize" width="719" height="146"/>
@@ -235,10 +235,10 @@
     </objects>
     <designables>
         <designable name="jUe-8q-VJd">
-            <size key="intrinsicContentSize" width="24" height="14.333333333333334"/>
+            <size key="intrinsicContentSize" width="34" height="16.333333333333336"/>
         </designable>
         <designable name="qnc-hI-Z9r">
-            <size key="intrinsicContentSize" width="26" height="14.333333333333334"/>
+            <size key="intrinsicContentSize" width="36" height="16.333333333333336"/>
         </designable>
     </designables>
     <resources>

+ 5 - 5
iOSClient/Main/Collection Common/NCSelectableNavigationView.swift

@@ -47,8 +47,8 @@ protocol NCSelectableNavigationView: AnyObject {
     var layoutKey: String { get }
     var selectActions: [NCMenuAction] { get }
 
-    func reloadDataSource(isForced: Bool)
-    func setNavigationItem()
+    func reloadDataSource(withQueryDB: Bool)
+    func setNavigationItems()
 
     func tapSelectMenu()
     func tapSelect()
@@ -56,7 +56,7 @@ protocol NCSelectableNavigationView: AnyObject {
 
 extension NCSelectableNavigationView {
 
-    func setNavigationItem() {
+    func setNavigationItems() {
         setNavigationRightItems()
     }
 
@@ -79,7 +79,7 @@ extension NCSelectableNavigationView {
         isEditMode = !isEditMode
         selectOcId.removeAll()
         selectIndexPath.removeAll()
-        self.setNavigationItem()
+        self.setNavigationItems()
         self.collectionView.reloadData()
     }
 
@@ -154,7 +154,7 @@ extension NCSelectableNavigationView where Self: UIViewController {
             actions.append(.saveMediaAction(selectedMediaMetadatas: selectedMediaMetadatas, completion: tapSelect))
         }
         actions.append(.setAvailableOfflineAction(selectedMetadatas: selectedMetadatas, isAnyOffline: isAnyOffline, viewController: self, completion: {
-            self.reloadDataSource(isForced: true)
+            self.reloadDataSource(withQueryDB: true)
             self.tapSelect()
         }))
 

+ 2 - 6
iOSClient/Main/Create cloud/NCCreateFormUploadDocuments.swift

@@ -212,9 +212,7 @@ import XLForm
 
     func dismissSelect(serverUrl: String?, metadata: tableMetadata?, type: String, items: [Any], indexPath: [IndexPath], overwrite: Bool, copy: Bool, move: Bool) {
 
-        guard let serverUrl = serverUrl else {
-            return
-        }
+        guard let serverUrl = serverUrl else { return }
 
         self.serverUrl = serverUrl
         if serverUrl == utilityFileSystem.getHomeServer(urlBase: appDelegate.urlBase, userId: appDelegate.userId) {
@@ -246,9 +244,7 @@ import XLForm
 
     @objc func save() {
 
-        guard let selectTemplate = self.selectTemplate else {
-            return
-        }
+        guard let selectTemplate = self.selectTemplate else { return }
         templateIdentifier = selectTemplate.identifier
 
         let rowFileName: XLFormRowDescriptor = self.form.formRow(withTag: "fileName")!

+ 1 - 1
iOSClient/Main/Create cloud/NCCreateFormUploadVoiceNote.swift

@@ -225,7 +225,7 @@ class NCCreateFormUploadVoiceNote: XLFormViewController, NCSelectDelegate, AVAud
 
         let metadataForUpload = NCManageDatabase.shared.createMetadata(account: self.appDelegate.account, user: self.appDelegate.user, userId: self.appDelegate.userId, fileName: fileNameSave, fileNameView: fileNameSave, ocId: UUID().uuidString, serverUrl: self.serverUrl, urlBase: self.appDelegate.urlBase, url: "", contentType: "")
 
-        metadataForUpload.session = NCNetworking.shared.sessionIdentifierBackground
+        metadataForUpload.session = NCNetworking.shared.sessionUploadBackground
         metadataForUpload.sessionSelector = NCGlobal.shared.selectorUploadFile
         metadataForUpload.status = NCGlobal.shared.metadataStatusWaitUpload
         metadataForUpload.size = utilityFileSystem.getFileSize(filePath: fileNamePath)

+ 1 - 1
iOSClient/Main/Create cloud/NCUploadAssets.swift

@@ -263,7 +263,7 @@ struct UploadAssetsView: View {
                 metadata.livePhotoFile = (metadata.fileName as NSString).deletingPathExtension + ".mov"
             }
             metadata.assetLocalIdentifier = asset.localIdentifier
-            metadata.session = NCNetworking.shared.sessionIdentifierBackground
+            metadata.session = NCNetworking.shared.sessionUploadBackground
             metadata.sessionSelector = NCGlobal.shared.selectorUploadFile
             metadata.status = NCGlobal.shared.metadataStatusWaitUpload
 

+ 2 - 2
iOSClient/Main/Main.storyboard

@@ -1,9 +1,9 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="21701" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="FkP-Lh-8zt">
+<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="22505" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="FkP-Lh-8zt">
     <device id="retina6_1" orientation="portrait" appearance="light"/>
     <dependencies>
         <deployment identifier="iOS"/>
-        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="21678"/>
+        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="22504"/>
         <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
     </dependencies>
     <scenes>

+ 51 - 38
iOSClient/Main/NCActionCenter.swift

@@ -41,18 +41,18 @@ class NCActionCenter: NSObject, UIDocumentInteractionControllerDelegate, NCSelec
     var documentController: UIDocumentInteractionController?
     let utilityFileSystem = NCUtilityFileSystem()
     let utility = NCUtility()
+    let appDelegate = UIApplication.shared.delegate as? AppDelegate
 
     // MARK: - Download
 
     @objc func downloadedFile(_ notification: NSNotification) {
 
-        guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else { return }
         guard let userInfo = notification.userInfo as NSDictionary?,
               let ocId = userInfo["ocId"] as? String,
               let selector = userInfo["selector"] as? String,
               let error = userInfo["error"] as? NKError,
               let account = userInfo["account"] as? String,
-              account == appDelegate.account
+              account == appDelegate?.account
         else { return }
 
         guard error == .success else {
@@ -72,42 +72,47 @@ class NCActionCenter: NSObject, UIDocumentInteractionControllerDelegate, NCSelec
 
         switch selector {
         case NCGlobal.shared.selectorLoadFileQuickLook:
-            let fileNamePath = utilityFileSystem.getDirectoryProviderStorageOcId(metadata.ocId, fileNameView: metadata.fileNameView)
-            let fileNameTemp = NSTemporaryDirectory() + metadata.fileNameView
-            let viewerQuickLook = NCViewerQuickLook(with: URL(fileURLWithPath: fileNameTemp), isEditingEnabled: true, metadata: metadata)
-            if let image = UIImage(contentsOfFile: fileNamePath) {
-                if let data = image.jpegData(compressionQuality: 1) {
-                    do {
-                        try data.write(to: URL(fileURLWithPath: fileNameTemp))
-                    } catch {
-                        return
+            DispatchQueue.main.async {
+                let fileNamePath = self.utilityFileSystem.getDirectoryProviderStorageOcId(metadata.ocId, fileNameView: metadata.fileNameView)
+                let fileNameTemp = NSTemporaryDirectory() + metadata.fileNameView
+                let viewerQuickLook = NCViewerQuickLook(with: URL(fileURLWithPath: fileNameTemp), isEditingEnabled: true, metadata: metadata)
+                if let image = UIImage(contentsOfFile: fileNamePath) {
+                    if let data = image.jpegData(compressionQuality: 1) {
+                        do {
+                            try data.write(to: URL(fileURLWithPath: fileNameTemp))
+                        } catch {
+                            return
+                        }
                     }
+                    let navigationController = UINavigationController(rootViewController: viewerQuickLook)
+                    navigationController.modalPresentationStyle = .fullScreen
+                    self.appDelegate?.window?.rootViewController?.present(navigationController, animated: true)
+                } else {
+                    self.utilityFileSystem.copyFile(atPath: fileNamePath, toPath: fileNameTemp)
+                    self.appDelegate?.window?.rootViewController?.present(viewerQuickLook, animated: true)
                 }
-                let navigationController = UINavigationController(rootViewController: viewerQuickLook)
-                navigationController.modalPresentationStyle = .fullScreen
-                appDelegate.window?.rootViewController?.present(navigationController, animated: true)
-            } else {
-                utilityFileSystem.copyFile(atPath: fileNamePath, toPath: fileNameTemp)
-                appDelegate.window?.rootViewController?.present(viewerQuickLook, animated: true)
             }
 
         case NCGlobal.shared.selectorLoadFileView:
-            guard UIApplication.shared.applicationState == .active else { break }
-
-            if metadata.contentType.contains("opendocument") && !utility.isRichDocument(metadata) {
-                self.openDocumentController(metadata: metadata)
-            } else if metadata.classFile == NKCommon.TypeClassFile.compress.rawValue || metadata.classFile == NKCommon.TypeClassFile.unknow.rawValue {
-                self.openDocumentController(metadata: metadata)
-            } else {
-                if let viewController = appDelegate.activeViewController {
-                    let imageIcon = UIImage(contentsOfFile: utilityFileSystem.getDirectoryProviderStorageIconOcId(metadata.ocId, etag: metadata.etag))
-                    NCViewer().view(viewController: viewController, metadata: metadata, metadatas: [metadata], imageIcon: imageIcon)
+            DispatchQueue.main.async {
+                guard UIApplication.shared.applicationState == .active else { return }
+                if metadata.contentType.contains("opendocument") && !self.utility.isRichDocument(metadata) {
+                    self.openDocumentController(metadata: metadata)
+                } else if metadata.classFile == NKCommon.TypeClassFile.compress.rawValue || metadata.classFile == NKCommon.TypeClassFile.unknow.rawValue {
+                    self.openDocumentController(metadata: metadata)
+                } else {
+                    if let viewController = self.appDelegate?.activeViewController {
+                        let imageIcon = UIImage(contentsOfFile: self.utilityFileSystem.getDirectoryProviderStorageIconOcId(metadata.ocId, etag: metadata.etag))
+                        NCViewer().view(viewController: viewController, metadata: metadata, metadatas: [metadata], imageIcon: imageIcon)
+                    }
                 }
             }
 
         case NCGlobal.shared.selectorOpenIn:
-            if UIApplication.shared.applicationState == .active {
-                self.openDocumentController(metadata: metadata)
+            DispatchQueue.main.async {
+                if UIApplication.shared.applicationState == .active {
+                    self.openDocumentController(metadata: metadata)
+                }
             }
 
         case NCGlobal.shared.selectorLoadOffline:
@@ -121,10 +126,14 @@ class NCActionCenter: NSObject, UIDocumentInteractionControllerDelegate, NCSelec
             }
 
         case NCGlobal.shared.selectorSaveAlbum:
-            saveAlbum(metadata: metadata)
+            DispatchQueue.main.async {
+                self.saveAlbum(metadata: metadata)
+            }
 
         case NCGlobal.shared.selectorSaveAsScan:
-            saveAsScan(metadata: metadata)
+            DispatchQueue.main.async {
+                self.saveAsScan(metadata: metadata)
+            }
 
         case NCGlobal.shared.selectorOpenDetail:
             NotificationCenter.default.postOnMainThread(name: NCGlobal.shared.notificationCenterOpenMediaDetail, userInfo: ["ocId": metadata.ocId])
@@ -146,11 +155,12 @@ class NCActionCenter: NSObject, UIDocumentInteractionControllerDelegate, NCSelec
             }
         } else if metadata.directory {
             NCManageDatabase.shared.setDirectory(serverUrl: serverUrl, offline: true, account: appDelegate.account)
-            NCNetworking.shared.synchronizationServerUrl(serverUrl, account: metadata.account, selector: NCGlobal.shared.selectorSynchronizationOffline)
+            NCNetworking.shared.synchronization(account: metadata.account, serverUrl: serverUrl, selector: NCGlobal.shared.selectorSynchronizationOffline)
         } else {
-            NCNetworking.shared.download(metadata: metadata, selector: NCGlobal.shared.selectorLoadOffline) { _, _ in }
-            if let metadataLivePhoto = NCManageDatabase.shared.getMetadataLivePhoto(metadata: metadata) {
-                NCNetworking.shared.download(metadata: metadataLivePhoto, selector: NCGlobal.shared.selectorLoadOffline) { _, _ in }
+            guard let metadata = NCManageDatabase.shared.setMetadataSessionInWaitDownload(ocId: metadata.ocId, selector: NCGlobal.shared.selectorLoadOffline) else { return }
+            NCNetworking.shared.download(metadata: metadata, withNotificationProgressTask: true)
+            if let metadata = NCManageDatabase.shared.getMetadataLivePhoto(metadata: metadata), let metadata = NCManageDatabase.shared.setMetadataSessionInWaitDownload(ocId: metadata.ocId, selector: NCGlobal.shared.selectorLoadOffline) {
+                NCNetworking.shared.download(metadata: metadata, withNotificationProgressTask: true)
             }
         }
     }
@@ -321,7 +331,8 @@ class NCActionCenter: NSObject, UIDocumentInteractionControllerDelegate, NCSelec
         let processor = ParallelWorker(n: 5, titleKey: "_downloading_", totalTasks: downloadMetadata.count, hudView: appDelegate.window?.rootViewController?.view)
         for (metadata, url) in downloadMetadata {
             processor.execute { completion in
-                NCNetworking.shared.download(metadata: metadata, selector: "", notificationCenterProgressTask: false) { _ in
+                guard let metadata = NCManageDatabase.shared.setMetadataSessionInWaitDownload(ocId: metadata.ocId, selector: "") else { return completion() }
+                NCNetworking.shared.download(metadata: metadata, withNotificationProgressTask: false) {
                 } progressHandler: { progress in
                     processor.hud?.progress = Float(progress.fractionCompleted)
                 } completion: { _, _ in
@@ -467,7 +478,9 @@ class NCActionCenter: NSObject, UIDocumentInteractionControllerDelegate, NCSelec
 
             for metadata in downloadMetadatas {
                 processor.execute { completion in
-                    NCNetworking.shared.download(metadata: metadata, selector: "", notificationCenterProgressTask: false) { _ in
+                    guard let metadata = NCManageDatabase.shared.setMetadataSessionInWaitDownload(ocId: metadata.ocId, selector: "") else { return completion() }
+                    NCNetworking.shared.download(metadata: metadata, withNotificationProgressTask: false) {
+                    } requestHandler: { _ in
                     } progressHandler: { progress in
                         if Float(progress.fractionCompleted) > fractionCompleted || fractionCompleted == 0 {
                             processor.hud?.progress = Float(progress.fractionCompleted)
@@ -506,7 +519,7 @@ class NCActionCenter: NSObject, UIDocumentInteractionControllerDelegate, NCSelec
                     let toPath = self.utilityFileSystem.getDirectoryProviderStorageOcId(ocId!, fileNameView: fileName)
                     self.utilityFileSystem.moveFile(atPath: fileNameLocalPath, toPath: toPath)
                     NCManageDatabase.shared.addLocalFile(account: account, etag: etag!, ocId: ocId!, fileName: fileName)
-                    NotificationCenter.default.postOnMainThread(name: NCGlobal.shared.notificationCenterReloadDataSourceNetworkForced)
+                    NotificationCenter.default.postOnMainThread(name: NCGlobal.shared.notificationCenterReloadDataSourceNetwork)
                 } else if afError?.isExplicitlyCancelledError ?? false {
                     print("cancel")
                 } else {

+ 53 - 35
iOSClient/Main/NCMainTabBar.swift

@@ -45,10 +45,7 @@ class NCMainTabBar: UITabBar {
         appDelegate.mainTabBar = self
 
         NotificationCenter.default.addObserver(self, selector: #selector(changeTheming), name: NSNotification.Name(rawValue: NCGlobal.shared.notificationCenterChangeTheming), object: nil)
-        NotificationCenter.default.addObserver(self, selector: #selector(updateBadgeNumber(_:)), name: NSNotification.Name(rawValue: NCGlobal.shared.notificationCenterUpdateBadgeNumber), object: nil)
-
-        barTintColor = .secondarySystemBackground
-        backgroundColor = .secondarySystemBackground
+        NotificationCenter.default.addObserver(self, selector: #selector(updateBadgeNumber), name: NSNotification.Name(rawValue: NCGlobal.shared.notificationCenterUpdateBadgeNumber), object: nil)
 
         changeTheming()
     }
@@ -79,34 +76,31 @@ class NCMainTabBar: UITabBar {
         }
     }
 
-    override func layoutSubviews() {
-        super.layoutSubviews()
-
-        layer.shadowPath = createPath()
-        layer.shadowRadius = 5
-        layer.shadowOffset = .zero
-        layer.shadowOpacity = 0.25
-    }
-
     override func draw(_ rect: CGRect) {
+        self.subviews.forEach({ $0.removeFromSuperview() })
+
         addShape()
         createButtons()
     }
 
     private func addShape() {
+        let blurEffect = UIBlurEffect(style: .systemThinMaterial)
 
-        let shapeLayer = CAShapeLayer()
-        shapeLayer.path = createPath()
-        shapeLayer.fillColor = backgroundColor?.cgColor
-        shapeLayer.strokeColor = UIColor.clear.cgColor
+        let blurView = UIVisualEffectView(effect: blurEffect)
+        blurView.frame = self.bounds
 
-        if let oldShapeLayer = self.shapeLayer {
-            self.layer.replaceSublayer(oldShapeLayer, with: shapeLayer)
-        } else {
-            self.layer.insertSublayer(shapeLayer, at: 0)
-        }
+        let maskLayer = CAShapeLayer()
+        maskLayer.path = createPath()
+
+        blurView.layer.mask = maskLayer
+
+        var border = CALayer()
+        border.backgroundColor = UIColor.separator.cgColor
+        border.frame = CGRect(x: 0, y: 0, width: blurView.frame.width, height: 0.5)
 
-        self.shapeLayer = shapeLayer
+        blurView.layer.addSublayer(border)
+
+        self.addSubview(blurView)
     }
 
     private func createPath() -> CGPath {
@@ -205,31 +199,55 @@ class NCMainTabBar: UITabBar {
         self.addSubview(centerButton)
     }
 
-    @objc func updateBadgeNumber(_ notification: NSNotification) {
+    @objc func updateBadgeNumber() {
+
+        DispatchQueue.global().async {
+
+            var counterDownload = 0
+            var counterUpload = 0
 
-        guard let userInfo = notification.userInfo as NSDictionary?,
-              let counterDownload = userInfo["counterDownload"] as? Int,
-              let counterUpload = userInfo["counterUpload"] as? Int
-        else { return }
+            if let results = NCManageDatabase.shared.getResultsMetadatas(predicate: NSPredicate(format: "status < 0")) {
+                counterDownload = results.count
+            }
+            if let results = NCManageDatabase.shared.getResultsMetadatas(predicate: NSPredicate(format: "status > 0")) {
+                counterUpload = results.count
+            }
+
+            DispatchQueue.main.async {
+                self.updateBadgeNumberUI(counterDownload: counterDownload, counterUpload: counterUpload)
+            }
+        }
+    }
+
+    func updateBadgeNumberUI(counterDownload: Int, counterUpload: Int) {
 
         UIApplication.shared.applicationIconBadgeNumber = counterUpload
+
         if let item = self.items?[0] {
             if counterDownload == 0, counterUpload == 0 {
                 item.badgeValue = nil
             } else if counterDownload > 0, counterUpload == 0 {
                 var badgeValue = String("↓ \(counterDownload)")
-                if counterDownload >= NCGlobal.shared.maxConcurrentOperationCountDownload {
-                    badgeValue = String("↓ 10+")
+                if counterDownload >= NCBrandOptions.shared.maxConcurrentOperationDownload {
+                    badgeValue = String("↓ \(NCBrandOptions.shared.maxConcurrentOperationDownload)+")
                 }
                 item.badgeValue = badgeValue
             } else if counterDownload == 0, counterUpload > 0 {
-                item.badgeValue = String("↑ \(counterUpload)")
-            } else {
-                var badgeValue = String("↓ \(counterDownload) ↑ \(counterUpload)")
-                if counterDownload >= NCGlobal.shared.maxConcurrentOperationCountDownload {
-                    badgeValue = String("↓ 10+ ↑ \(counterUpload)")
+                var badgeValue = String("↑ \(counterUpload)")
+                if counterUpload >= NCBrandOptions.shared.maxConcurrentOperationUpload {
+                    badgeValue = String("↑ \(NCBrandOptions.shared.maxConcurrentOperationUpload)+")
                 }
                 item.badgeValue = badgeValue
+            } else {
+                var badgeValueDownload = String("↓ \(counterDownload)")
+                if counterDownload >= NCBrandOptions.shared.maxConcurrentOperationDownload {
+                    badgeValueDownload = String("↓ \(NCBrandOptions.shared.maxConcurrentOperationDownload)+")
+                }
+                var badgeValueUpload = String("↑ \(counterUpload)")
+                if counterUpload >= NCBrandOptions.shared.maxConcurrentOperationUpload {
+                    badgeValueUpload = String("↑ \(NCBrandOptions.shared.maxConcurrentOperationUpload)+")
+                }
+                item.badgeValue = badgeValueDownload + " " + badgeValueUpload
             }
         }
     }

+ 1 - 1
iOSClient/Main/NCPickerViewController.swift

@@ -170,7 +170,7 @@ class NCDocumentPickerViewController: NSObject, UIDocumentPickerDelegate {
 
                 let metadataForUpload = NCManageDatabase.shared.createMetadata(account: appDelegate.account, user: appDelegate.user, userId: appDelegate.userId, fileName: fileName, fileNameView: fileName, ocId: ocId, serverUrl: serverUrl, urlBase: appDelegate.urlBase, url: "", contentType: "")
 
-                metadataForUpload.session = NCNetworking.shared.sessionIdentifierBackground
+                metadataForUpload.session = NCNetworking.shared.sessionUploadBackground
                 metadataForUpload.sessionSelector = NCGlobal.shared.selectorUploadFile
                 metadataForUpload.size = utilityFileSystem.getFileSize(filePath: toPath)
                 metadataForUpload.status = NCGlobal.shared.metadataStatusWaitUpload

+ 12 - 9
iOSClient/Main/Section Header Footer/NCSectionFooter.xib

@@ -1,9 +1,9 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="20037" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
-    <device id="retina4_7" orientation="portrait" appearance="light"/>
+<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="22505" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
+    <device id="retina5_9" orientation="landscape" appearance="light"/>
     <dependencies>
         <deployment identifier="iOS"/>
-        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="20020"/>
+        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="22504"/>
         <capability name="Safe area layout guides" minToolsVersion="9.0"/>
         <capability name="System colors in document resources" minToolsVersion="11.0"/>
         <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
@@ -16,7 +16,7 @@
             <autoresizingMask key="autoresizingMask"/>
             <subviews>
                 <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="TK1-KX-Qe0">
-                    <rect key="frame" x="10" y="0.0" width="355" height="30"/>
+                    <rect key="frame" x="60" y="0.0" width="255" height="30"/>
                     <constraints>
                         <constraint firstAttribute="height" constant="30" id="Qvv-k4-hfY"/>
                     </constraints>
@@ -30,10 +30,10 @@
                     </connections>
                 </button>
                 <activityIndicatorView opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" style="gray" translatesAutoresizingMaskIntoConstraints="NO" id="qWG-SR-Qly">
-                    <rect key="frame" x="177.5" y="5" width="20" height="20"/>
+                    <rect key="frame" x="177.66666666666666" y="5" width="20" height="20"/>
                 </activityIndicatorView>
                 <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="s2m-yO-4x0" userLabel="separator">
-                    <rect key="frame" x="10" y="30" width="365" height="1"/>
+                    <rect key="frame" x="60" y="30" width="265" height="1"/>
                     <color key="backgroundColor" white="0.0" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
                     <color key="tintColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
                     <constraints>
@@ -41,7 +41,7 @@
                     </constraints>
                 </view>
                 <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="gzy-cT-Gjn" userLabel="LabelFooter">
-                    <rect key="frame" x="10" y="43.5" width="355" height="16"/>
+                    <rect key="frame" x="10" y="13.666666666666664" width="355" height="16"/>
                     <fontDescription key="fontDescription" type="system" pointSize="13"/>
                     <nil key="textColor"/>
                     <nil key="highlightedColor"/>
@@ -53,7 +53,10 @@
                 <constraint firstItem="EFn-SN-cxu" firstAttribute="trailing" secondItem="TK1-KX-Qe0" secondAttribute="trailing" constant="10" id="PoY-CD-99O"/>
                 <constraint firstAttribute="trailing" secondItem="gzy-cT-Gjn" secondAttribute="trailing" constant="10" id="QzY-ac-CRO"/>
                 <constraint firstItem="EFn-SN-cxu" firstAttribute="leading" secondItem="s2m-yO-4x0" secondAttribute="leading" constant="-10" id="ai4-Qy-YWi"/>
-                <constraint firstItem="gzy-cT-Gjn" firstAttribute="centerY" secondItem="Vin-9E-7nW" secondAttribute="centerY" id="avP-sX-JB5"/>
+                <constraint firstItem="gzy-cT-Gjn" firstAttribute="centerY" secondItem="Vin-9E-7nW" secondAttribute="centerY" constant="-30" id="avP-sX-JB5">
+                    <variation key="heightClass=compact-widthClass=regular" constant="-15"/>
+                    <variation key="heightClass=regular-widthClass=compact" constant="-15"/>
+                </constraint>
                 <constraint firstItem="qWG-SR-Qly" firstAttribute="centerY" secondItem="TK1-KX-Qe0" secondAttribute="centerY" id="baS-g9-E8a"/>
                 <constraint firstItem="EFn-SN-cxu" firstAttribute="trailing" secondItem="s2m-yO-4x0" secondAttribute="trailing" id="dWj-wQ-cfb"/>
                 <constraint firstItem="TK1-KX-Qe0" firstAttribute="bottom" secondItem="s2m-yO-4x0" secondAttribute="bottom" constant="-1" id="ekM-Ii-N58"/>
@@ -74,7 +77,7 @@
     </objects>
     <resources>
         <systemColor name="linkColor">
-            <color red="0.0" green="0.47843137254901963" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
+            <color red="0.0" green="0.47843137250000001" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
         </systemColor>
     </resources>
 </document>

+ 3 - 3
iOSClient/Main/Section Header Footer/NCSectionHeaderMenu.swift

@@ -320,9 +320,9 @@ class NCSectionFooter: UICollectionReusableView, NCSectionFooterDelegate {
         }
 
         if files > 1 {
-            filesText = "\(files) " + NSLocalizedString("_files_", comment: "") + " " + utilityFileSystem.transformedSize(size)
+            filesText = "\(files) " + NSLocalizedString("_files_", comment: "") + " " + utilityFileSystem.transformedSize(size)
         } else if files == 1 {
-            filesText = "1 " + NSLocalizedString("_file_", comment: "") + " " + utilityFileSystem.transformedSize(size)
+            filesText = "1 " + NSLocalizedString("_file_", comment: "") + " " + utilityFileSystem.transformedSize(size)
         }
 
         if foldersText.isEmpty {
@@ -330,7 +330,7 @@ class NCSectionFooter: UICollectionReusableView, NCSectionFooterDelegate {
         } else if filesText.isEmpty {
             labelSection.text = foldersText
         } else {
-            labelSection.text = foldersText + ", " + filesText
+            labelSection.text = foldersText + " " + filesText
         }
     }
 

+ 4 - 4
iOSClient/Media/NCMedia.storyboard

@@ -1,9 +1,9 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="17156" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="EFX-fO-Oip">
+<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="22505" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="EFX-fO-Oip">
     <device id="retina5_9" orientation="portrait" appearance="light"/>
     <dependencies>
         <deployment identifier="iOS"/>
-        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="17125"/>
+        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="22504"/>
         <capability name="Safe area layout guides" minToolsVersion="9.0"/>
         <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
     </dependencies>
@@ -17,7 +17,7 @@
                         <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
                         <subviews>
                             <collectionView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" showsHorizontalScrollIndicator="NO" dataMode="prototypes" translatesAutoresizingMaskIntoConstraints="NO" id="Zaz-Cl-qpZ">
-                                <rect key="frame" x="0.0" y="0.0" width="375" height="813"/>
+                                <rect key="frame" x="0.0" y="0.0" width="375" height="862"/>
                                 <color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
                                 <collectionViewFlowLayout key="collectionViewLayout" minimumLineSpacing="0.0" minimumInteritemSpacing="0.0" id="fF1-wd-0xN">
                                     <size key="itemSize" width="0.0" height="0.0"/>
@@ -37,7 +37,7 @@
                         <constraints>
                             <constraint firstItem="Zaz-Cl-qpZ" firstAttribute="leading" secondItem="Meh-VD-wWh" secondAttribute="leading" id="1bp-sm-u0X"/>
                             <constraint firstItem="Meh-VD-wWh" firstAttribute="trailing" secondItem="Zaz-Cl-qpZ" secondAttribute="trailing" id="aNd-UL-hmu"/>
-                            <constraint firstItem="Meh-VD-wWh" firstAttribute="bottom" secondItem="Zaz-Cl-qpZ" secondAttribute="bottom" constant="-35" id="aNr-tf-2AH"/>
+                            <constraint firstItem="Meh-VD-wWh" firstAttribute="bottom" secondItem="Zaz-Cl-qpZ" secondAttribute="bottom" constant="-84" id="aNr-tf-2AH"/>
                             <constraint firstItem="Zaz-Cl-qpZ" firstAttribute="top" secondItem="QEs-gO-Cmp" secondAttribute="top" id="nIB-3t-o2I"/>
                         </constraints>
                     </view>

+ 128 - 588
iOSClient/Media/NCMedia.swift

@@ -23,44 +23,40 @@
 
 import UIKit
 import NextcloudKit
-import JGProgressHUD
-import Queuer
 import RealmSwift
 
-class NCMedia: UIViewController, NCEmptyDataSetDelegate, NCSelectDelegate {
+class NCMedia: UIViewController, NCEmptyDataSetDelegate {
 
     @IBOutlet weak var collectionView: UICollectionView!
 
-    private var emptyDataSet: NCEmptyDataSet?
-    private var mediaCommandView: NCMediaCommandView?
-    private var gridLayout: NCGridMediaLayout!
-    internal var documentPickerViewController: NCDocumentPickerViewController?
+    var emptyDataSet: NCEmptyDataSet?
+    var mediaCommandView: NCMediaCommandView?
+    var layout: NCMediaGridLayout!
+    var documentPickerViewController: NCDocumentPickerViewController?
 
-    internal let appDelegate = (UIApplication.shared.delegate as? AppDelegate)!
-    internal let utilityFileSystem = NCUtilityFileSystem()
-    internal let utility = NCUtility()
+    let appDelegate = (UIApplication.shared.delegate as? AppDelegate)!
+    let utilityFileSystem = NCUtilityFileSystem()
+    let utility = NCUtility()
 
-    @ThreadSafe internal var metadatas: Results<tableMetadata>?
-    internal var isEditMode = false
-    internal var selectOcId: [String] = []
-    internal var selectIndexPath: [IndexPath] = []
+    var metadatas: ThreadSafeArray<tableMetadata>?
+    var isEditMode = false
+    var selectOcId: [String] = []
 
-    internal var showOnlyImages = false
-    internal var showOnlyVideos = false
+    var showOnlyImages = false
+    var showOnlyVideos = false
 
-    private let maxImageGrid: CGFloat = 7
-    private var cellHeigth: CGFloat = 0
+    let maxImageGrid: CGFloat = 7
+    var cellHeigth: CGFloat = 0
 
-    private var oldInProgress = false
-    private var newInProgress = false
+    var loadingTask: Task<Void, any Error>?
 
-    private var lastContentOffsetY: CGFloat = 0
-    private var mediaPath = ""
+    var lastContentOffsetY: CGFloat = 0
+    var mediaPath = ""
 
-    private var timeIntervalSearchNewMedia: TimeInterval = 3.0
-    private var timerSearchNewMedia: Timer?
+    var timeIntervalSearchNewMedia: TimeInterval = 3.0
+    var timerSearchNewMedia: Timer?
 
-    private let insetsTop: CGFloat = 75
+    let insetsTop: CGFloat = 75
 
     struct cacheImages {
         static var cellLivePhotoImage = UIImage()
@@ -75,35 +71,27 @@ class NCMedia: UIViewController, NCEmptyDataSetDelegate, NCSelectDelegate {
 
         view.backgroundColor = .systemBackground
 
-        collectionView.register(UINib(nibName: "NCGridMediaCell", bundle: nil), forCellWithReuseIdentifier: "gridCell")
+        layout = NCMediaGridLayout()
+        layout.itemForLine = CGFloat(NCKeychain().mediaItemForLine)
+        layout.sectionHeadersPinToVisibleBounds = true
 
+        collectionView.register(UINib(nibName: "NCGridMediaCell", bundle: nil), forCellWithReuseIdentifier: "gridCell")
         collectionView.alwaysBounceVertical = true
         collectionView.contentInset = UIEdgeInsets(top: insetsTop, left: 0, bottom: 50, right: 0)
         collectionView.backgroundColor = .systemBackground
+        collectionView.prefetchDataSource = self
+        collectionView.collectionViewLayout = layout
 
-        gridLayout = NCGridMediaLayout()
-        gridLayout.itemForLine = CGFloat(min(NCKeychain().mediaWidthImage, 5))
-        gridLayout.sectionHeadersPinToVisibleBounds = true
-
-        collectionView.collectionViewLayout = gridLayout
-
-        // Empty
         emptyDataSet = NCEmptyDataSet(view: collectionView, offset: 0, delegate: self)
 
         mediaCommandView = Bundle.main.loadNibNamed("NCMediaCommandView", owner: self, options: nil)?.first as? NCMediaCommandView
         self.view.addSubview(mediaCommandView!)
         mediaCommandView?.mediaView = self
-        mediaCommandView?.zoomInButton.isEnabled = !(gridLayout.itemForLine == 1)
-        mediaCommandView?.zoomOutButton.isEnabled = !(gridLayout.itemForLine == maxImageGrid - 1)
-        mediaCommandView?.collapseControlButtonView(true)
         mediaCommandView?.translatesAutoresizingMaskIntoConstraints = false
         mediaCommandView?.topAnchor.constraint(equalTo: view.topAnchor, constant: 0).isActive = true
         mediaCommandView?.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 0).isActive = true
         mediaCommandView?.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: 0).isActive = true
         mediaCommandView?.heightAnchor.constraint(equalToConstant: 150).isActive = true
-        self.updateMediaControlVisibility()
-
-        collectionView.prefetchDataSource = self
 
         cacheImages.cellLivePhotoImage = utility.loadImage(named: "livephoto", color: .white)
         cacheImages.cellPlayImage = utility.loadImage(named: "play.fill", color: .white)
@@ -119,34 +107,35 @@ 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(uploadedFile(_:)), name: NSNotification.Name(rawValue: NCGlobal.shared.notificationCenterUploadedFile), object: nil)
+
+        timerSearchNewMedia?.invalidate()
+        timerSearchNewMedia = Timer.scheduledTimer(timeInterval: timeIntervalSearchNewMedia, target: self, selector: #selector(searchMediaUI), userInfo: nil, repeats: false)
 
         if let metadatas = NCImageCache.shared.initialMetadatas() {
             self.metadatas = metadatas
+            self.mediaCommandView?.setMoreButton()
         }
-        timerSearchNewMedia?.invalidate()
-        timerSearchNewMedia = Timer.scheduledTimer(timeInterval: timeIntervalSearchNewMedia, target: self, selector: #selector(searchNewMediaTimer), userInfo: nil, repeats: false)
-
         collectionView.reloadData()
     }
 
     override func viewDidAppear(_ animated: Bool) {
         super.viewDidAppear(animated)
 
-        mediaCommandTitle()
+        mediaCommandView?.setMediaCommand()
+        mediaCommandView?.createMenu()
     }
 
     override func viewWillDisappear(_ animated: Bool) {
         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.notificationCenterUploadedFile), object: nil)
     }
 
     override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
         super.viewWillTransition(to: size, with: coordinator)
 
-        self.collectionView?.collectionViewLayout.invalidateLayout()
+        collectionView?.collectionViewLayout.invalidateLayout()
+        mediaCommandView?.setMediaCommand()
     }
 
     override var preferredStatusBarStyle: UIStatusBarStyle {
@@ -158,89 +147,29 @@ class NCMedia: UIViewController, NCEmptyDataSetDelegate, NCSelectDelegate {
     @objc func deleteFile(_ notification: NSNotification) {
 
         guard let userInfo = notification.userInfo as NSDictionary?,
+              let ocIds = userInfo["ocId"] as? [String],
               let error = userInfo["error"] as? NKError else { return }
 
-        self.reloadDataSource()
-
-        if error != .success {
-            NCContentPresenter().showError(error: error)
-        }
-    }
-
-    @objc func uploadedFile(_ notification: NSNotification) {
-
-        guard let userInfo = notification.userInfo as NSDictionary?,
-              let error = userInfo["error"] as? NKError,
-              error == .success,
-              let account = userInfo["account"] as? String,
-              account == appDelegate.account
-        else { return }
-
-        self.reloadDataSource()
-    }
-
-    // MARK: - Command
-
-    func mediaCommandTitle() {
-
-        mediaCommandView?.title.text = ""
-        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 {
-                mediaCommandView?.title.text = ""
-                if let date = cell.date {
-                    mediaCommandView?.title.text = utility.getTitleFromDate(date)
+        if !ocIds.isEmpty {
+            var items: [IndexPath] = []
+            self.metadatas = self.metadatas?.filter({ !ocIds.contains($0.ocId )})
+            if let visibleCells = self.collectionView?.indexPathsForVisibleItems.sorted(by: { $0.row < $1.row }).compactMap({ self.collectionView?.cellForItem(at: $0) }) {
+                for case let cell as NCGridMediaCell in visibleCells {
+                    if let ocId = cell.fileObjectId, ocIds.contains(ocId) {
+                        items.append(cell.indexPath)
+                    }
                 }
+                collectionView?.performBatchUpdates({
+                    collectionView?.deleteItems(at: items)
+                }, completion: { _ in
+                    self.collectionView?.reloadData()
+                })
             }
         }
-    }
-
-    @objc func zoomOutGrid() {
-
-        UIView.animate(withDuration: 0.0, animations: {
-            if self.gridLayout.itemForLine + 1 < self.maxImageGrid {
-                self.gridLayout.itemForLine += 1
-                self.mediaCommandView?.zoomInButton.isEnabled = true
-            }
-            if self.gridLayout.itemForLine == self.maxImageGrid - 1 {
-                self.mediaCommandView?.zoomOutButton.isEnabled = false
-            }
-
-            self.collectionView.collectionViewLayout.invalidateLayout()
-            NCKeychain().mediaWidthImage = Int(self.gridLayout.itemForLine)
-        })
-    }
-
-    @objc func zoomInGrid() {
-
-        UIView.animate(withDuration: 0.0, animations: {
-            if self.gridLayout.itemForLine - 1 > 0 {
-                self.gridLayout.itemForLine -= 1
-                self.mediaCommandView?.zoomOutButton.isEnabled = true
-            }
-            if self.gridLayout.itemForLine == 1 {
-                self.mediaCommandView?.zoomInButton.isEnabled = false
-            }
-
-            self.collectionView.collectionViewLayout.invalidateLayout()
-            NCKeychain().mediaWidthImage = Int(self.gridLayout.itemForLine)
-        })
-    }
-
-    @objc func openMenuButtonMore(_ sender: Any) {
-
-        toggleMenu()
-    }
 
-    // MARK: Select Path
-
-    func dismissSelect(serverUrl: String?, metadata: tableMetadata?, type: String, items: [Any], indexPath: [IndexPath], overwrite: Bool, copy: Bool, move: Bool) {
-
-        guard let serverUrl = serverUrl else { return }
-        let home = utilityFileSystem.getHomeServer(urlBase: appDelegate.urlBase, userId: appDelegate.userId)
-        mediaPath = serverUrl.replacingOccurrences(of: home, with: "")
-        NCManageDatabase.shared.setAccountMediaPath(mediaPath, account: appDelegate.account)
-        reloadDataSource()
-        searchNewMedia()
+        if error != .success {
+            NCContentPresenter().showError(error: error)
+        }
     }
 
     // MARK: - Empty
@@ -248,13 +177,34 @@ class NCMedia: UIViewController, NCEmptyDataSetDelegate, NCSelectDelegate {
     func emptyDataSetView(_ view: NCEmptyView) {
 
         view.emptyImage.image = UIImage(named: "media")?.image(color: .gray, size: UIScreen.main.bounds.width)
-        if oldInProgress || newInProgress {
+        if loadingTask != nil {
             view.emptyTitle.text = NSLocalizedString("_search_in_progress_", comment: "")
         } else {
             view.emptyTitle.text = NSLocalizedString("_tutorial_photo_view_", comment: "")
         }
         view.emptyDescription.text = ""
     }
+
+    // MARK: - Image
+
+    func getImage(metadata: tableMetadata) -> UIImage? {
+
+        if let cachedImage = NCImageCache.shared.getMediaImage(ocId: metadata.ocId, etag: metadata.etag), case let .actual(image) = cachedImage {
+            return 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)) {
+                NCImageCache.shared.setMediaImage(ocId: metadata.ocId, etag: metadata.etag, image: .actual(image))
+                return image
+            }
+        } else {
+            if metadata.hasPreview && metadata.status == NCGlobal.shared.metadataStatusNormal && (!utilityFileSystem.fileProviderStoragePreviewIconExists(metadata.ocId, etag: metadata.etag)) {
+                if NCNetworking.shared.downloadThumbnailQueue.operations.filter({ ($0 as? NCMediaDownloadThumbnaill)?.metadata.ocId == metadata.ocId }).isEmpty {
+                    NCNetworking.shared.downloadThumbnailQueue.addOperation(NCMediaDownloadThumbnaill(metadata: metadata, collectionView: collectionView))
+                }
+            }
+        }
+        return nil
+    }
 }
 
 // MARK: - Collection View
@@ -267,20 +217,18 @@ extension NCMedia: UICollectionViewDelegate {
             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 {
+            } else {
                 // 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)
+                if let metadatas = self.metadatas?.getArray() {
+                    NCViewer().view(viewController: self, metadata: metadata, metadatas: metadatas, imageIcon: getImage(metadata: metadata))
+                }
             }
         }
     }
@@ -317,15 +265,18 @@ extension NCMedia: UICollectionViewDataSourcePrefetching {
 extension NCMedia: UICollectionViewDataSource {
 
     func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
-        guard let metadatas = self.metadatas else { return 0 }
-        emptyDataSet?.numberOfItemsInSection(metadatas.count, section: section)
-        return metadatas.count
+        var numberOfItemsInSection = 0
+        if let metadatas {
+            numberOfItemsInSection = metadatas.count
+        }
+        emptyDataSet?.numberOfItemsInSection(numberOfItemsInSection, section: section)
+        return numberOfItemsInSection
     }
 
     func collectionView(_ collectionView: UICollectionView, didEndDisplaying cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
-        guard let metadatas = self.metadatas else { return }
+        guard let metadatas else { return }
         if !collectionView.indexPathsForVisibleItems.contains(indexPath) && indexPath.row < metadatas.count {
-            let metadata = metadatas[indexPath.row]
+            guard let metadata = metadatas[indexPath.row] else { return }
             for case let operation as NCMediaDownloadThumbnaill in NCNetworking.shared.downloadThumbnailQueue.operations where operation.metadata.ocId == metadata.ocId {
                 operation.cancel()
             }
@@ -338,260 +289,47 @@ extension NCMedia: UICollectionViewDataSource {
     func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> 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 < metadatas.count {
-
-            let metadata = metadatas[indexPath.row]
-
-            self.cellHeigth = cell.frame.size.height
-
-            cell.date = metadata.date as Date
-            cell.fileObjectId = metadata.ocId
-            cell.indexPath = indexPath
-            cell.fileUser = metadata.ownerId
-
-            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, etag: metadata.etag, image: .actual(image))
-                }
-            } else {
-                if metadata.hasPreview && metadata.status == NCGlobal.shared.metadataStatusNormal && (!utilityFileSystem.fileProviderStoragePreviewIconExists(metadata.ocId, etag: metadata.etag)) {
-                    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 {
-                cell.imageStatus.image = cacheImages.cellLivePhotoImage
-            } else {
-                cell.imageStatus.image = nil
-            }
-
-            if isEditMode {
-                cell.selectMode(true)
-                if selectOcId.contains(metadata.ocId) {
-                    cell.selected(true)
-                } else {
-                    cell.selected(false)
-                }
-            } else {
-                cell.selectMode(false)
-            }
-
-            return cell
-
-        } else {
-
-            return cell
-        }
-    }
-}
-
-extension NCMedia: UICollectionViewDelegateFlowLayout {
-
-    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize {
-        return CGSize(width: collectionView.frame.width, height: 0)
-    }
-
-    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForFooterInSection section: Int) -> CGSize {
-        return CGSize(width: collectionView.frame.width, height: 0)
-    }
-}
-
-extension NCMedia {
-
-    func getPredicate(showAll: Bool = false) -> NSPredicate {
-
-        let startServerUrl = NCUtilityFileSystem().getHomeServer(urlBase: appDelegate.urlBase, userId: appDelegate.userId) + mediaPath
-
-        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)
-        } else {
-           return NSPredicate(format: NCImageCache.shared.showBothPredicateMediaString, appDelegate.account, startServerUrl)
-        }
-    }
-
-    @objc func reloadDataSource() {
-        guard !appDelegate.account.isEmpty else { return }
-
-        metadatas = NCImageCache.shared.getMediaMetadatas(account: self.appDelegate.account, predicate: self.getPredicate())
-        DispatchQueue.main.async {
-            self.collectionView?.reloadData()
-            self.updateMediaControlVisibility()
-            self.mediaCommandTitle()
-        }
-    }
-
-    func updateMediaControlVisibility() {
-
-        if let metadatas = self.metadatas, metadatas.isEmpty {
-            if !self.showOnlyImages && !self.showOnlyVideos {
-                self.mediaCommandView?.toggleEmptyView(isEmpty: true)
-                self.mediaCommandView?.isHidden = false
-            } else {
-                self.mediaCommandView?.toggleEmptyView(isEmpty: true)
-                self.mediaCommandView?.isHidden = false
-            }
-        } else {
-            self.mediaCommandView?.toggleEmptyView(isEmpty: false)
-            self.mediaCommandView?.isHidden = false
-        }
-    }
+              let metadatas = self.metadatas,
+              let metadata = metadatas[indexPath.row] else { return UICollectionViewCell() }
 
-    // MARK: - Search media
+        self.cellHeigth = cell.frame.size.height
 
-    private func searchOldMedia(value: Int = -30, limit: Int = 300) {
+        cell.date = metadata.date as Date
+        cell.fileObjectId = metadata.ocId
+        cell.indexPath = indexPath
+        cell.fileUser = metadata.ownerId
+        cell.imageStatus.image = nil
 
-        if oldInProgress { return } else { oldInProgress = true }
-        DispatchQueue.main.async {
-            self.collectionView.reloadData()
-            var bottom: CGFloat = 0
-            if let mainTabBar = self.tabBarController?.tabBar as? NCMainTabBar {
-                bottom = -mainTabBar.getHeight()
-            }
-            NCActivityIndicator.shared.start(backgroundView: self.view, bottom: bottom - 5, style: .medium)
+        if let image = getImage(metadata: metadata) {
+            cell.imageItem.backgroundColor = nil
+            cell.imageItem.image = image
         }
 
-        var lessDate = Date()
-        let predicate = getPredicate()
-        if let metadata = NCManageDatabase.shared.getMetadata(predicate: predicate, sorted: "date", ascending: true) {
-            lessDate = metadata.date as Date
+        // Convert OLD Live Photo
+        if NCGlobal.shared.isLivePhotoServerAvailable, metadata.isLivePhoto, metadata.isNotFlaggedAsLivePhotoByServer {
+            NCNetworking.shared.convertLivePhoto(metadata: metadata)
         }
 
-        var greaterDate: Date
-        if value == -999 {
-            greaterDate = Date.distantPast
+        if metadata.isAudioOrVideo {
+            cell.imageStatus.image = cacheImages.cellPlayImage
+        } else if metadata.isLivePhoto {
+            cell.imageStatus.image = cacheImages.cellLivePhotoImage
         } else {
-            greaterDate = Calendar.current.date(byAdding: .day, value: value, to: lessDate)!
+            cell.imageStatus.image = nil
         }
 
-        let options = NKRequestOptions(timeout: 300, queue: NextcloudKit.shared.nkCommonInstance.backgroundQueue)
-
-        NextcloudKit.shared.searchMedia(path: mediaPath, lessDate: lessDate, greaterDate: greaterDate, elementDate: "d:getlastmodified/", limit: limit, showHiddenFiles: NCKeychain().showHiddenFiles, options: options) { account, files, _, error in
-
-            self.oldInProgress = false
-            DispatchQueue.main.async {
-                NCActivityIndicator.shared.stop()
-                self.collectionView.reloadData()
-            }
-
-            if error == .success && account == self.appDelegate.account {
-                if !files.isEmpty {
-                    NCManageDatabase.shared.convertFilesToMetadatas(files, useMetadataFolder: false) { _, _, metadatas in
-                        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 if results.metadatasChanged {
-                            self.reloadDataSource()
-                        }
-                    }
-                } else {
-                    self.researchOldMedia(value: value, limit: limit, withElseReloadDataSource: false)
-                }
-            } else if error != .success {
-                NextcloudKit.shared.nkCommonInstance.writeLog("[INFO] Media search old media error code \(error.errorCode) " + error.errorDescription)
+        if isEditMode {
+            cell.selectMode(true)
+            if selectOcId.contains(metadata.ocId) {
+                cell.selected(true)
+            } else {
+                cell.selected(false)
             }
-        }
-    }
-
-    private func researchOldMedia(value: Int, limit: Int, withElseReloadDataSource: Bool) {
-
-        if value == -30 {
-            searchOldMedia(value: -90)
-        } else if value == -90 {
-            searchOldMedia(value: -180)
-        } else if value == -180 {
-            searchOldMedia(value: -999)
-        } else if value == -999 && limit > 0 {
-            searchOldMedia(value: -999, limit: 0)
         } else {
-            if withElseReloadDataSource {
-                self.reloadDataSource()
-            }
-        }
-    }
-
-    @objc func searchNewMediaTimer() {
-
-        self.searchNewMedia()
-    }
-
-    @objc func searchNewMedia() {
-
-        if newInProgress { return } else {
-            newInProgress = true
-            mediaCommandView?.activityIndicator.startAnimating()
-        }
-
-        var limit: Int = 1000
-        guard var lessDate = Calendar.current.date(byAdding: .second, value: 1, to: Date()) else { return }
-        guard var greaterDate = Calendar.current.date(byAdding: .day, value: -30, to: Date()) else { return }
-
-        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? {
-                        lessDate = Calendar.current.date(byAdding: .second, value: 1, to: cell.date!)!
-                        limit = 0
-                    }
-                }
-            }
-            if let cell = visibleCells.last as? NCGridMediaCell {
-                if cell.date != nil {
-                    greaterDate = Calendar.current.date(byAdding: .second, value: -1, to: cell.date!)!
-                }
-            }
+            cell.selectMode(false)
         }
 
-        collectionView?.reloadData()
-
-        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
-
-            self.newInProgress = false
-            DispatchQueue.main.async {
-                self.mediaCommandView?.activityIndicator.stopAnimating()
-            }
-
-            if error == .success, account == self.appDelegate.account {
-                if !files.isEmpty {
-                    NCManageDatabase.shared.convertFilesToMetadatas(files, useMetadataFolder: false) { _, _, metadatas in
-                        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 {
-                    self.searchOldMedia()
-                }
-            } else if error != .success {
-                NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] Media search new media error code \(error.errorCode) " + error.errorDescription)
-            }
-        }
+        return cell
     }
 }
 
@@ -600,246 +338,48 @@ extension NCMedia {
 extension NCMedia: UIScrollViewDelegate {
 
     func scrollViewDidScroll(_ scrollView: UIScrollView) {
-
         if lastContentOffsetY == 0 || lastContentOffsetY + cellHeigth / 2 <= scrollView.contentOffset.y || lastContentOffsetY - cellHeigth / 2 >= scrollView.contentOffset.y {
-
-            mediaCommandTitle()
+            mediaCommandView?.setMediaCommand()
             lastContentOffsetY = scrollView.contentOffset.y
         }
     }
 
     func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
 
-        mediaCommandView?.collapseControlButtonView(true)
     }
 
     func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
-
         if !decelerate {
-            timerSearchNewMedia?.invalidate()
-            timerSearchNewMedia = Timer.scheduledTimer(timeInterval: timeIntervalSearchNewMedia, target: self, selector: #selector(searchNewMediaTimer), userInfo: nil, repeats: false)
-
-            if scrollView.contentOffset.y >= (scrollView.contentSize.height - scrollView.frame.size.height) {
-                searchOldMedia()
+            if !decelerate {
+                timerSearchNewMedia?.invalidate()
+                timerSearchNewMedia = Timer.scheduledTimer(timeInterval: timeIntervalSearchNewMedia, target: self, selector: #selector(searchMediaUI), userInfo: nil, repeats: false)
             }
         }
     }
 
     func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
-
         timerSearchNewMedia?.invalidate()
-        timerSearchNewMedia = Timer.scheduledTimer(timeInterval: timeIntervalSearchNewMedia, target: self, selector: #selector(searchNewMediaTimer), userInfo: nil, repeats: false)
-
-        if scrollView.contentOffset.y >= (scrollView.contentSize.height - scrollView.frame.size.height) {
-            searchOldMedia()
-        }
+        timerSearchNewMedia = Timer.scheduledTimer(timeInterval: timeIntervalSearchNewMedia, target: self, selector: #selector(searchMediaUI), userInfo: nil, repeats: false)
     }
 
     func scrollViewDidScrollToTop(_ scrollView: UIScrollView) {
-
         let y = view.safeAreaInsets.top
         scrollView.contentOffset.y = -(insetsTop + y)
     }
 }
 
-// MARK: - Media Command View
-
-class NCMediaCommandView: UIView {
-
-    @IBOutlet weak var moreView: UIVisualEffectView!
-    @IBOutlet weak var gridSwitchButton: UIButton!
-    @IBOutlet weak var separatorView: UIView!
-    @IBOutlet weak var buttonControlWidthConstraint: NSLayoutConstraint!
-    @IBOutlet weak var zoomInButton: UIButton!
-    @IBOutlet weak var zoomOutButton: UIButton!
-    @IBOutlet weak var moreButton: UIButton!
-    @IBOutlet weak var controlButtonView: UIVisualEffectView!
-    @IBOutlet weak var title: UILabel!
-    @IBOutlet weak var activityIndicator: UIActivityIndicatorView!
-
-    var mediaView: NCMedia?
-    private let gradient: CAGradientLayer = CAGradientLayer()
-
-    override func awakeFromNib() {
-        moreView.layer.cornerRadius = 20
-        moreView.layer.masksToBounds = true
-        controlButtonView.layer.cornerRadius = 20
-        controlButtonView.layer.masksToBounds = true
-        controlButtonView.effect = UIBlurEffect(style: .dark)
-        gradient.frame = bounds
-        gradient.startPoint = CGPoint(x: 0, y: 0.5)
-        gradient.endPoint = CGPoint(x: 0, y: 1)
-        gradient.colors = [UIColor.black.withAlphaComponent(UIAccessibility.isReduceTransparencyEnabled ? 0.8 : 0.4).cgColor, UIColor.clear.cgColor]
-        layer.insertSublayer(gradient, at: 0)
-        moreButton.setImage(UIImage(named: "more")!.image(color: .white, size: 25), for: .normal)
-        title.text = ""
-    }
-
-    func toggleEmptyView(isEmpty: Bool) {
-        if isEmpty {
-            UIView.animate(withDuration: 0.3) {
-                self.moreView.effect = UIBlurEffect(style: .dark)
-                self.gradient.isHidden = true
-                self.controlButtonView.isHidden = true
-            }
-        } else {
-            UIView.animate(withDuration: 0.3) {
-                self.moreView.effect = UIBlurEffect(style: .dark)
-                self.gradient.isHidden = false
-                self.controlButtonView.isHidden = false
-            }
-        }
-    }
-
-    @IBAction func moreButtonPressed(_ sender: UIButton) {
-        mediaView?.openMenuButtonMore(sender)
-    }
-
-    @IBAction func zoomInPressed(_ sender: UIButton) {
-        mediaView?.zoomInGrid()
-    }
-
-    @IBAction func zoomOutPressed(_ sender: UIButton) {
-        mediaView?.zoomOutGrid()
-    }
-
-    @IBAction func gridSwitchButtonPressed(_ sender: Any) {
-        self.collapseControlButtonView(false)
-    }
-
-    func collapseControlButtonView(_ collapse: Bool) {
-        if collapse {
-            self.buttonControlWidthConstraint.constant = 40
-            UIView.animate(withDuration: 0.25) {
-                self.zoomOutButton.isHidden = true
-                self.zoomInButton.isHidden = true
-                self.separatorView.isHidden = true
-                self.gridSwitchButton.isHidden = false
-                self.layoutIfNeeded()
-            }
-        } else {
-            self.buttonControlWidthConstraint.constant = 80
-            UIView.animate(withDuration: 0.25) {
-                self.zoomOutButton.isHidden = false
-                self.zoomInButton.isHidden = false
-                self.separatorView.isHidden = false
-                self.gridSwitchButton.isHidden = true
-                self.layoutIfNeeded()
-            }
-        }
-    }
-
-    override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
-        return moreView.frame.contains(point) || controlButtonView.frame.contains(point)
-    }
-
-    override func layoutSublayers(of layer: CALayer) {
-        super.layoutSublayers(of: layer)
-        gradient.frame = bounds
-    }
-}
-
-// MARK: - Media Grid Layout
-
-class NCGridMediaLayout: UICollectionViewFlowLayout {
-
-    var marginLeftRight: CGFloat = 2
-    var itemForLine: CGFloat = 3
-
-    override init() {
-        super.init()
-
-        sectionHeadersPinToVisibleBounds = false
-
-        minimumInteritemSpacing = 0
-        minimumLineSpacing = marginLeftRight
-
-        self.scrollDirection = .vertical
-        self.sectionInset = UIEdgeInsets(top: 0, left: marginLeftRight, bottom: 0, right: marginLeftRight)
-    }
-
-    required init?(coder aDecoder: NSCoder) {
-        fatalError("init(coder:) has not been implemented")
-    }
-
-    override var itemSize: CGSize {
-        get {
-            if let collectionView = collectionView {
-
-                let itemWidth: CGFloat = (collectionView.frame.width - marginLeftRight * 2 - marginLeftRight * (itemForLine - 1)) / itemForLine
-                let itemHeight: CGFloat = itemWidth
-
-                return CGSize(width: itemWidth, height: itemHeight)
-            }
-
-            // Default fallback
-            return CGSize(width: 100, height: 100)
-        }
-        set {
-            super.itemSize = newValue
-        }
-    }
-
-    override func targetContentOffset(forProposedContentOffset proposedContentOffset: CGPoint) -> CGPoint {
-        return proposedContentOffset
-    }
-}
-
-// MARK: -
-
-class NCMediaDownloadThumbnaill: ConcurrentOperation {
+// MARK: - NCSelect Delegate
 
-    var metadata: tableMetadata
-    var cell: NCCellProtocol?
-    var collectionView: UICollectionView?
-    var fileNamePath: String
-    var fileNamePreviewLocalPath: String
-    var fileNameIconLocalPath: String
-    let utilityFileSystem = NCUtilityFileSystem()
-
-    init(metadata: tableMetadata, cell: NCCellProtocol?, collectionView: UICollectionView?) {
-        self.metadata = tableMetadata.init(value: metadata)
-        self.cell = cell
-        self.collectionView = collectionView
-        self.fileNamePath = utilityFileSystem.getFileNamePath(metadata.fileName, serverUrl: metadata.serverUrl, urlBase: metadata.urlBase, userId: metadata.userId)
-        self.fileNamePreviewLocalPath = utilityFileSystem.getDirectoryProviderStoragePreviewOcId(metadata.ocId, etag: metadata.etag)
-        self.fileNameIconLocalPath = utilityFileSystem.getDirectoryProviderStorageIconOcId(metadata.ocId, etag: metadata.etag)
-    }
+extension NCMedia: NCSelectDelegate {
 
-    override func start() {
-
-        guard !isCancelled else { return self.finish() }
-
-        var etagResource: String?
-        if FileManager.default.fileExists(atPath: fileNameIconLocalPath) && FileManager.default.fileExists(atPath: fileNamePreviewLocalPath) {
-            etagResource = metadata.etagResource
-        }
+    func dismissSelect(serverUrl: String?, metadata: tableMetadata?, type: String, items: [Any], indexPath: [IndexPath], overwrite: Bool, copy: Bool, move: Bool) {
 
-        NextcloudKit.shared.downloadPreview(fileNamePathOrFileId: fileNamePath,
-                                            fileNamePreviewLocalPath: fileNamePreviewLocalPath,
-                                            widthPreview: NCGlobal.shared.sizePreview,
-                                            heightPreview: NCGlobal.shared.sizePreview,
-                                            fileNameIconLocalPath: fileNameIconLocalPath,
-                                            sizeIcon: NCGlobal.shared.sizeIcon,
-                                            etag: etagResource,
-                                            options: NKRequestOptions(queue: NextcloudKit.shared.nkCommonInstance.backgroundQueue)) { _, imagePreview, _, _, etag, error in
-
-            if error == .success, let image = imagePreview {
-                NCManageDatabase.shared.setMetadataEtagResource(ocId: self.metadata.ocId, etagResource: etag)
-                DispatchQueue.main.async {
-                    if self.metadata.ocId == self.cell?.fileObjectId, let filePreviewImageView = self.cell?.filePreviewImageView {
-                        UIView.transition(with: filePreviewImageView,
-                                          duration: 0.75,
-                                          options: .transitionCrossDissolve,
-                                          animations: { filePreviewImageView.image = image },
-                                          completion: nil)
-                    } else {
-                        self.collectionView?.reloadData()
-                    }
-                }
-                NCImageCache.shared.setMediaImage(ocId: self.metadata.ocId, etag: self.metadata.etag, image: .actual(image))
-            }
-            self.finish()
-        }
+        guard let serverUrl = serverUrl else { return }
+        let home = utilityFileSystem.getHomeServer(urlBase: appDelegate.urlBase, userId: appDelegate.userId)
+        mediaPath = serverUrl.replacingOccurrences(of: home, with: "")
+        NCManageDatabase.shared.setAccountMediaPath(mediaPath, account: appDelegate.account)
+        reloadDataSource()
+        timerSearchNewMedia?.invalidate()
+        timerSearchNewMedia = Timer.scheduledTimer(timeInterval: timeIntervalSearchNewMedia, target: self, selector: #selector(self.searchMediaUI), userInfo: nil, repeats: false)
     }
 }

+ 252 - 0
iOSClient/Media/NCMediaCommandView.swift

@@ -0,0 +1,252 @@
+//
+//  NCMediaCommandView.swift
+//  Nextcloud
+//
+//  Created by Marino Faggiana on 25/01/24.
+//  Copyright © 2024 Marino Faggiana. All rights reserved.
+//
+//  Author Marino Faggiana <marino.faggiana@nextcloud.com>
+//
+//  This program is free software: you can redistribute it and/or modify
+//  it under the terms of the GNU General Public License as published by
+//  the Free Software Foundation, either version 3 of the License, or
+//  (at your option) any later version.
+//
+//  This program is distributed in the hope that it will be useful,
+//  but WITHOUT ANY WARRANTY; without even the implied warranty of
+//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+//  GNU General Public License for more details.
+//
+//  You should have received a copy of the GNU General Public License
+//  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+//
+
+import UIKit
+import NextcloudKit
+
+class NCMediaCommandView: UIView {
+
+    @IBOutlet weak var title: UILabel!
+    @IBOutlet weak var selectButton: UIButton!
+    @IBOutlet weak var activityIndicator: UIActivityIndicatorView!
+    @IBOutlet weak var moreButton: UIButton!
+
+    var mediaView: NCMedia!
+    var attributesZoomIn: UIMenuElement.Attributes = []
+    var attributesZoomOut: UIMenuElement.Attributes = []
+    let gradient: CAGradientLayer = CAGradientLayer()
+
+    override func awakeFromNib() {
+        super.awakeFromNib()
+
+        title.text = ""
+
+        selectButton.backgroundColor = .systemGray4.withAlphaComponent(0.6)
+        selectButton.layer.cornerRadius = 15
+        selectButton.layer.masksToBounds = true
+        selectButton.setTitle( NSLocalizedString("_select_", comment: ""), for: .normal)
+
+        moreButton.changesSelectionAsPrimaryAction = false
+
+        gradient.frame = bounds
+        gradient.startPoint = CGPoint(x: 0, y: 0.5)
+        gradient.endPoint = CGPoint(x: 0, y: 1)
+        gradient.colors = [UIColor.black.withAlphaComponent(UIAccessibility.isReduceTransparencyEnabled ? 0.8 : 0.4).cgColor, UIColor.clear.cgColor]
+        layer.insertSublayer(gradient, at: 0)
+    }
+
+    override func layoutSublayers(of layer: CALayer) {
+        super.layoutSublayers(of: layer)
+        gradient.frame = bounds
+    }
+
+    @IBAction func selectButtonPressed(_ sender: UIButton) {
+        if mediaView.isEditMode {
+            setMoreButton()
+        } else {
+            setMoreButtonDelete()
+        }
+    }
+
+    @IBAction func trashButtonPressed(_ sender: UIButton) {
+
+        if !mediaView.selectOcId.isEmpty {
+            let selectOcId = mediaView.selectOcId
+            var title = NSLocalizedString("_delete_", comment: "")
+            if selectOcId.count > 1 {
+                title = NSLocalizedString("_delete_selected_files_", comment: "")
+            }
+            let alertController = UIAlertController(
+                title: title,
+                message: "",
+                preferredStyle: .alert)
+            alertController.addAction(UIAlertAction(title: NSLocalizedString("_yes_delete_", comment: ""), style: .default) { (_: UIAlertAction) in
+
+                self.setMoreButton()
+
+                Task {
+                    var error = NKError()
+                    var ocIds: [String] = []
+                    for ocId in selectOcId where error == .success {
+                        if let metadata = NCManageDatabase.shared.getMetadataFromOcId(ocId) {
+                            error = await NCNetworking.shared.deleteMetadata(metadata, onlyLocalCache: false)
+                            if error == .success {
+                                ocIds.append(metadata.ocId)
+                            }
+                        }
+                    }
+                    NotificationCenter.default.postOnMainThread(name: NCGlobal.shared.notificationCenterDeleteFile, userInfo: ["ocId": ocIds, "onlyLocalCache": false, "error": error])
+                }
+            })
+            alertController.addAction(UIAlertAction(title: NSLocalizedString("_no_delete_", comment: ""), style: .default) { (_: UIAlertAction) in })
+
+            mediaView.present(alertController, animated: true, completion: { })
+        }
+    }
+
+    func setMoreButton() {
+
+        moreButton.backgroundColor = .systemGray4.withAlphaComponent(0.6)
+        moreButton.layer.cornerRadius = 15
+        moreButton.layer.masksToBounds = true
+        moreButton.showsMenuAsPrimaryAction = true
+        moreButton.configuration = UIButton.Configuration.plain()
+        let image = UIImage(systemName: "ellipsis")
+        moreButton.setImage(image, for: .normal)
+
+        mediaView.isEditMode = false
+        mediaView.selectOcId.removeAll()
+        mediaView.collectionView?.reloadData()
+
+        selectButton.setTitle( NSLocalizedString("_select_", comment: ""), for: .normal)
+    }
+
+    func setMoreButtonDelete() {
+
+        moreButton.backgroundColor = .clear
+        moreButton.showsMenuAsPrimaryAction = false
+        moreButton.configuration = UIButton.Configuration.plain()
+        let image = UIImage(systemName: "trash.circle", withConfiguration: UIImage.SymbolConfiguration(pointSize: 25))?.withTintColor(.red, renderingMode: .alwaysOriginal)
+        moreButton.setImage(image, for: .normal)
+
+        mediaView.isEditMode = true
+
+        selectButton.setTitle( NSLocalizedString("_cancel_", comment: ""), for: .normal)
+    }
+
+    func createMenu() {
+
+        if let itemForLine = mediaView?.layout.itemForLine, let maxImageGrid = mediaView?.maxImageGrid {
+            if itemForLine >= maxImageGrid - 1 {
+                self.attributesZoomIn = []
+                self.attributesZoomOut = .disabled
+            } else if itemForLine <= 1 {
+                self.attributesZoomIn = .disabled
+                self.attributesZoomOut = []
+            } else {
+                self.attributesZoomIn = []
+                self.attributesZoomOut = []
+            }
+        }
+
+        let topAction = UIMenu(title: "", options: .displayInline, children: [
+            UIMenu(title: NSLocalizedString("_zoom_", comment: ""), children: [
+                UIAction(title: NSLocalizedString("_zoom_out_", comment: ""), image: UIImage(systemName: "minus.magnifyingglass"), attributes: self.attributesZoomOut) { _ in
+                    guard let mediaView = self.mediaView else { return }
+                    UIView.animate(withDuration: 0.0, animations: {
+                        mediaView.layout.itemForLine += 1
+                        self.createMenu()
+                        mediaView.collectionView.collectionViewLayout.invalidateLayout()
+                        NCKeychain().mediaItemForLine = Int(mediaView.layout.itemForLine)
+                    })
+                },
+                UIAction(title: NSLocalizedString("_zoom_in_", comment: ""), image: UIImage(systemName: "plus.magnifyingglass"), attributes: self.attributesZoomIn) { _ in
+                    UIView.animate(withDuration: 0.0, animations: {
+                        self.mediaView.layout.itemForLine -= 1
+                        self.createMenu()
+                        self.mediaView.collectionView.collectionViewLayout.invalidateLayout()
+                        NCKeychain().mediaItemForLine = Int(self.mediaView.layout.itemForLine)
+                    })
+                }
+            ]),
+            UIMenu(title: NSLocalizedString("_media_view_options_", comment: ""), children: [
+                UIAction(title: NSLocalizedString("_media_viewimage_show_", comment: ""), image: UIImage(systemName: "photo")) { _ in
+                    self.mediaView.showOnlyImages = true
+                    self.mediaView.showOnlyVideos = false
+                    self.mediaView.reloadDataSource()
+                },
+                UIAction(title: NSLocalizedString("_media_viewvideo_show_", comment: ""), image: UIImage(systemName: "video")) { _ in
+                    self.mediaView.showOnlyImages = false
+                    self.mediaView.showOnlyVideos = true
+                    self.mediaView.reloadDataSource()
+                },
+                UIAction(title: NSLocalizedString("_media_show_all_", comment: ""), image: UIImage(systemName: "photo.on.rectangle")) { _ in
+                    self.mediaView.showOnlyImages = false
+                    self.mediaView.showOnlyVideos = false
+                    self.mediaView.reloadDataSource()
+                }
+            ]),
+            UIAction(title: NSLocalizedString("_select_media_folder_", comment: ""), image: UIImage(systemName: "folder"), handler: { _ in
+                guard let navigationController = UIStoryboard(name: "NCSelect", bundle: nil).instantiateInitialViewController() as? UINavigationController,
+                      let viewController = navigationController.topViewController as? NCSelect else { return }
+                viewController.delegate = self.mediaView
+                viewController.typeOfCommandView = .select
+                viewController.type = "mediaFolder"
+                self.mediaView.present(navigationController, animated: true, completion: nil)
+            })
+        ])
+
+        let playFile = UIAction(title: NSLocalizedString("_play_from_files_", comment: ""), image: UIImage(systemName: "play.circle")) { _ in
+            guard let tabBarController = self.mediaView.appDelegate.window?.rootViewController as? UITabBarController else { return }
+            self.mediaView.documentPickerViewController = NCDocumentPickerViewController(tabBarController: tabBarController, isViewerMedia: true, allowsMultipleSelection: false, viewController: self.mediaView)
+        }
+        let playURL = UIAction(title: NSLocalizedString("_play_from_url_", comment: ""), image: UIImage(systemName: "link")) { _ in
+            let alert = UIAlertController(title: NSLocalizedString("_valid_video_url_", comment: ""), message: nil, preferredStyle: .alert)
+            alert.addAction(UIAlertAction(title: NSLocalizedString("_cancel_", comment: ""), style: .cancel, handler: nil))
+            alert.addTextField(configurationHandler: { textField in
+                textField.placeholder = "http://myserver.com/movie.mkv"
+            })
+            alert.addAction(UIAlertAction(title: NSLocalizedString("_ok_", comment: ""), style: .default, handler: { _ in
+                guard let stringUrl = alert.textFields?.first?.text, !stringUrl.isEmpty, let url = URL(string: stringUrl) else { return }
+                let fileName = url.lastPathComponent
+                let appDelegate = (UIApplication.shared.delegate as? AppDelegate)!
+                let metadata = NCManageDatabase.shared.createMetadata(account: appDelegate.account, user: appDelegate.user, userId: appDelegate.userId, fileName: fileName, fileNameView: fileName, ocId: NSUUID().uuidString, serverUrl: "", urlBase: appDelegate.urlBase, url: stringUrl, contentType: "")
+                NCManageDatabase.shared.addMetadata(metadata)
+                NCViewer().view(viewController: self.mediaView, metadata: metadata, metadatas: [metadata], imageIcon: nil)
+            }))
+            self.mediaView.present(alert, animated: true)
+        }
+
+        moreButton.menu = UIMenu(title: "", children: [topAction, playFile, playURL])
+    }
+
+    func setMediaCommand() {
+
+        DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
+            self.title.text = ""
+            if let visibleCells = self.mediaView.collectionView?.indexPathsForVisibleItems.sorted(by: { $0.row < $1.row }).compactMap({ self.mediaView.collectionView?.cellForItem(at: $0) }) {
+                if let cell = visibleCells.first as? NCGridMediaCell {
+                    self.title.text = ""
+                    if let date = cell.date {
+                        self.title.text = self.mediaView.utility.getTitleFromDate(date)
+                    }
+                }
+            }
+            if let metadatas = self.mediaView.metadatas, !metadatas.isEmpty {
+                self.selectButton.isHidden = false
+                if self.gradient.isHidden {
+                    UIView.animate(withDuration: 0.3) {
+                        self.gradient.isHidden = false
+                    }
+                }
+            } else {
+                self.selectButton.isHidden = true
+                if !self.gradient.isHidden {
+                    UIView.animate(withDuration: 0.3) {
+                        self.gradient.isHidden = true
+                    }
+                }
+            }
+        }
+    }
+}

+ 37 - 137
iOSClient/Media/NCMediaCommandView.xib

@@ -4,7 +4,6 @@
     <dependencies>
         <deployment identifier="iOS"/>
         <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="22130"/>
-        <capability name="Image references" minToolsVersion="12.0"/>
         <capability name="Safe area layout guides" minToolsVersion="9.0"/>
         <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
     </dependencies>
@@ -15,164 +14,65 @@
             <rect key="frame" x="0.0" y="0.0" width="393" height="852"/>
             <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
             <subviews>
-                <visualEffectView opaque="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="i1s-Qa-dG1">
-                    <rect key="frame" x="297" y="69" width="40" height="40"/>
-                    <view key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" insetsLayoutMarginsFromSafeArea="NO" id="e3Q-e0-SQa">
-                        <rect key="frame" x="0.0" y="0.0" width="40" height="40"/>
-                        <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
-                        <subviews>
-                            <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="jSO-Vb-jcU">
-                                <rect key="frame" x="0.0" y="0.0" width="40" height="40"/>
-                                <constraints>
-                                    <constraint firstAttribute="width" constant="40" id="5zz-h5-hx9"/>
-                                    <constraint firstAttribute="height" constant="40" id="lzK-bd-NlR"/>
-                                </constraints>
-                                <color key="tintColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
-                                <inset key="imageEdgeInsets" minX="12" minY="12" maxX="12" maxY="12"/>
-                                <state key="normal" image="plus.forwardslash.minus" catalog="system">
-                                    <color key="titleColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
-                                </state>
-                                <state key="disabled">
-                                    <color key="titleColor" white="0.66666666666666663" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
-                                </state>
-                                <connections>
-                                    <action selector="gridSwitchButtonPressed:" destination="iN0-l3-epB" eventType="touchUpInside" id="0jB-EV-iMe"/>
-                                </connections>
-                            </button>
-                            <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="ZkU-dK-sp1">
-                                <rect key="frame" x="0.0" y="0.0" width="19.666666666666668" height="40"/>
-                                <color key="tintColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
-                                <inset key="imageEdgeInsets" minX="12" minY="12" maxX="12" maxY="12"/>
-                                <state key="normal" image="plus" catalog="system">
-                                    <color key="titleColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
-                                </state>
-                                <state key="disabled">
-                                    <color key="titleColor" white="0.66666666666666663" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
-                                </state>
-                                <connections>
-                                    <action selector="zoomInPressed:" destination="iN0-l3-epB" eventType="touchUpInside" id="YPO-oL-Gf5"/>
-                                </connections>
-                            </button>
-                            <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="NwM-bk-QAK">
-                                <rect key="frame" x="19.666666666666686" y="8" width="1" height="24"/>
-                                <color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
-                                <constraints>
-                                    <constraint firstAttribute="width" constant="1" id="1cE-rd-mYI"/>
-                                </constraints>
-                            </view>
-                            <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="swp-Cd-CbD">
-                                <rect key="frame" x="20.666666666666686" y="0.0" width="19.333333333333329" height="40"/>
-                                <color key="tintColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
-                                <inset key="imageEdgeInsets" minX="12" minY="12" maxX="12" maxY="12"/>
-                                <state key="normal">
-                                    <color key="titleColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
-                                    <imageReference key="image" image="minus" catalog="system" symbolScale="default"/>
-                                </state>
-                                <state key="disabled">
-                                    <color key="titleColor" white="0.66666666666666663" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
-                                </state>
-                                <connections>
-                                    <action selector="zoomOutPressed:" destination="iN0-l3-epB" eventType="touchUpInside" id="xIo-6p-l7h"/>
-                                </connections>
-                            </button>
-                        </subviews>
-                        <constraints>
-                            <constraint firstItem="NwM-bk-QAK" firstAttribute="top" secondItem="e3Q-e0-SQa" secondAttribute="top" constant="8" id="3V7-Bl-HeS"/>
-                            <constraint firstItem="jSO-Vb-jcU" firstAttribute="centerY" secondItem="e3Q-e0-SQa" secondAttribute="centerY" id="5ap-Yp-VlG"/>
-                            <constraint firstItem="NwM-bk-QAK" firstAttribute="centerX" secondItem="e3Q-e0-SQa" secondAttribute="centerX" id="70K-vU-VR6"/>
-                            <constraint firstItem="NwM-bk-QAK" firstAttribute="leading" secondItem="ZkU-dK-sp1" secondAttribute="trailing" id="Lcu-89-Iys"/>
-                            <constraint firstAttribute="bottom" secondItem="NwM-bk-QAK" secondAttribute="bottom" constant="8" id="O8t-uT-Dhh"/>
-                            <constraint firstItem="jSO-Vb-jcU" firstAttribute="centerX" secondItem="e3Q-e0-SQa" secondAttribute="centerX" id="YFf-Kh-lAp"/>
-                            <constraint firstAttribute="bottom" secondItem="swp-Cd-CbD" secondAttribute="bottom" id="dgT-Np-dTd"/>
-                            <constraint firstItem="ZkU-dK-sp1" firstAttribute="leading" secondItem="e3Q-e0-SQa" secondAttribute="leading" id="fyg-tQ-tfR"/>
-                            <constraint firstItem="ZkU-dK-sp1" firstAttribute="top" secondItem="e3Q-e0-SQa" secondAttribute="top" id="o2e-4h-ylb"/>
-                            <constraint firstAttribute="trailing" secondItem="swp-Cd-CbD" secondAttribute="trailing" id="oaa-Rc-bYC"/>
-                            <constraint firstItem="swp-Cd-CbD" firstAttribute="leading" secondItem="NwM-bk-QAK" secondAttribute="trailing" id="omu-Hf-g4C"/>
-                            <constraint firstAttribute="bottom" secondItem="ZkU-dK-sp1" secondAttribute="bottom" id="pfE-6t-fCg"/>
-                            <constraint firstItem="swp-Cd-CbD" firstAttribute="top" secondItem="e3Q-e0-SQa" secondAttribute="top" id="yoe-G6-vaw"/>
-                        </constraints>
-                    </view>
-                    <constraints>
-                        <constraint firstAttribute="height" constant="40" id="mEu-PL-xc2"/>
-                        <constraint firstAttribute="width" constant="40" id="wkm-j1-5v4"/>
-                    </constraints>
-                    <blurEffect style="regular"/>
-                </visualEffectView>
                 <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="IxY-xH-yZQ">
-                    <rect key="frame" x="8" y="69" width="281" height="40"/>
+                    <rect key="frame" x="8" y="67" width="213" height="40"/>
                     <constraints>
                         <constraint firstAttribute="height" constant="40" id="S6o-Pa-sxy"/>
                     </constraints>
-                    <fontDescription key="fontDescription" type="boldSystem" pointSize="24"/>
+                    <fontDescription key="fontDescription" type="boldSystem" pointSize="20"/>
                     <color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
                     <nil key="highlightedColor"/>
                 </label>
-                <visualEffectView opaque="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="enp-xt-Y5y">
-                    <rect key="frame" x="345" y="69" width="40" height="40"/>
-                    <view key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" insetsLayoutMarginsFromSafeArea="NO" id="KKR-Hr-av8">
-                        <rect key="frame" x="0.0" y="0.0" width="40" height="40"/>
-                        <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
-                        <subviews>
-                            <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="3qs-Hm-qLL">
-                                <rect key="frame" x="0.0" y="0.0" width="40" height="40"/>
-                                <color key="tintColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
-                                <inset key="imageEdgeInsets" minX="4" minY="4" maxX="4" maxY="4"/>
-                                <state key="normal" image="ellipsis" catalog="system"/>
-                                <connections>
-                                    <action selector="moreButtonPressed:" destination="iN0-l3-epB" eventType="touchUpInside" id="EeF-Eg-Pqi"/>
-                                </connections>
-                            </button>
-                        </subviews>
-                        <constraints>
-                            <constraint firstAttribute="bottom" secondItem="3qs-Hm-qLL" secondAttribute="bottom" id="B32-ni-kC4"/>
-                            <constraint firstItem="3qs-Hm-qLL" firstAttribute="leading" secondItem="KKR-Hr-av8" secondAttribute="leading" id="NSe-Qy-mmd"/>
-                            <constraint firstItem="3qs-Hm-qLL" firstAttribute="top" secondItem="KKR-Hr-av8" secondAttribute="top" id="URa-a2-sfx"/>
-                            <constraint firstAttribute="trailing" secondItem="3qs-Hm-qLL" secondAttribute="trailing" id="fjn-eP-8DS"/>
-                        </constraints>
-                    </view>
-                    <constraints>
-                        <constraint firstAttribute="height" constant="40" id="Ftv-5S-RKc"/>
-                        <constraint firstAttribute="width" constant="40" id="JJ2-fQ-ew9"/>
-                    </constraints>
-                    <blurEffect style="regular"/>
-                </visualEffectView>
                 <activityIndicatorView hidden="YES" opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" hidesWhenStopped="YES" style="white" translatesAutoresizingMaskIntoConstraints="NO" id="XVj-jD-9KA">
-                    <rect key="frame" x="186.66666666666666" y="50" width="20" height="20"/>
+                    <rect key="frame" x="229" y="77" width="20" height="20"/>
                     <color key="color" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
                 </activityIndicatorView>
+                <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" adjustsImageWhenHighlighted="NO" adjustsImageWhenDisabled="NO" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="3qs-Hm-qLL">
+                    <rect key="frame" x="355" y="72" width="30" height="30"/>
+                    <constraints>
+                        <constraint firstAttribute="height" constant="30" id="l6a-hf-7l2"/>
+                        <constraint firstAttribute="width" constant="30" id="uVw-bC-TZq"/>
+                    </constraints>
+                    <color key="tintColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
+                    <inset key="imageEdgeInsets" minX="4" minY="4" maxX="4" maxY="4"/>
+                    <connections>
+                        <action selector="trashButtonPressed:" destination="iN0-l3-epB" eventType="touchUpInside" id="7mo-8p-bep"/>
+                    </connections>
+                </button>
+                <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="EFV-eb-pFF">
+                    <rect key="frame" x="257" y="72" width="90" height="30"/>
+                    <constraints>
+                        <constraint firstAttribute="width" constant="90" id="Hf1-Hv-Jpi"/>
+                        <constraint firstAttribute="height" constant="30" id="tTh-bW-DMw"/>
+                    </constraints>
+                    <fontDescription key="fontDescription" type="system" pointSize="14"/>
+                    <inset key="imageEdgeInsets" minX="0.0" minY="0.0" maxX="2.2250738585072014e-308" maxY="0.0"/>
+                    <state key="normal" title="Select"/>
+                    <connections>
+                        <action selector="selectButtonPressed:" destination="iN0-l3-epB" eventType="touchUpInside" id="YfJ-ms-0G1"/>
+                    </connections>
+                </button>
             </subviews>
             <viewLayoutGuide key="safeArea" id="vUN-kp-3ea"/>
             <color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
             <constraints>
-                <constraint firstItem="XVj-jD-9KA" firstAttribute="top" secondItem="vUN-kp-3ea" secondAttribute="top" constant="-9" id="Cpb-Vg-sov"/>
-                <constraint firstItem="i1s-Qa-dG1" firstAttribute="leading" secondItem="IxY-xH-yZQ" secondAttribute="trailing" constant="8" id="Owq-aa-Sr6"/>
-                <constraint firstItem="enp-xt-Y5y" firstAttribute="leading" secondItem="i1s-Qa-dG1" secondAttribute="trailing" constant="8" id="XcU-G8-k0a"/>
+                <constraint firstItem="XVj-jD-9KA" firstAttribute="centerY" secondItem="IxY-xH-yZQ" secondAttribute="centerY" id="4nZ-Ea-KMB"/>
+                <constraint firstItem="3qs-Hm-qLL" firstAttribute="centerY" secondItem="IxY-xH-yZQ" secondAttribute="centerY" id="AgJ-WD-mqU"/>
+                <constraint firstItem="vUN-kp-3ea" firstAttribute="trailing" secondItem="3qs-Hm-qLL" secondAttribute="trailing" constant="8" id="FtF-ES-dyl"/>
+                <constraint firstItem="EFV-eb-pFF" firstAttribute="leading" secondItem="XVj-jD-9KA" secondAttribute="trailing" constant="8" id="I7N-mB-wyc"/>
                 <constraint firstItem="IxY-xH-yZQ" firstAttribute="leading" secondItem="vUN-kp-3ea" secondAttribute="leading" constant="8" id="ZhO-pY-Qwi"/>
-                <constraint firstItem="i1s-Qa-dG1" firstAttribute="top" secondItem="vUN-kp-3ea" secondAttribute="top" constant="10" id="cWc-xe-8un"/>
-                <constraint firstItem="XVj-jD-9KA" firstAttribute="centerX" secondItem="vUN-kp-3ea" secondAttribute="centerX" id="lqb-pC-WUR"/>
-                <constraint firstItem="IxY-xH-yZQ" firstAttribute="centerY" secondItem="i1s-Qa-dG1" secondAttribute="centerY" id="mJJ-pT-yA3"/>
-                <constraint firstItem="vUN-kp-3ea" firstAttribute="trailing" secondItem="enp-xt-Y5y" secondAttribute="trailing" constant="8" id="rSU-Dz-YXW"/>
-                <constraint firstItem="enp-xt-Y5y" firstAttribute="top" secondItem="vUN-kp-3ea" secondAttribute="top" constant="10" id="z6I-sd-mmD"/>
+                <constraint firstItem="IxY-xH-yZQ" firstAttribute="top" secondItem="vUN-kp-3ea" secondAttribute="top" constant="8" id="mX3-Fm-K1m"/>
+                <constraint firstItem="EFV-eb-pFF" firstAttribute="centerY" secondItem="IxY-xH-yZQ" secondAttribute="centerY" id="ozT-m6-dct"/>
+                <constraint firstItem="3qs-Hm-qLL" firstAttribute="leading" secondItem="EFV-eb-pFF" secondAttribute="trailing" constant="8" id="t2r-N2-lJR"/>
+                <constraint firstItem="XVj-jD-9KA" firstAttribute="leading" secondItem="IxY-xH-yZQ" secondAttribute="trailing" constant="8" id="ztz-0d-9Mr"/>
             </constraints>
             <connections>
                 <outlet property="activityIndicator" destination="XVj-jD-9KA" id="cSB-RJ-RCZ"/>
-                <outlet property="buttonControlWidthConstraint" destination="wkm-j1-5v4" id="SHm-F2-NjB"/>
-                <outlet property="controlButtonView" destination="i1s-Qa-dG1" id="5q4-uE-oC5"/>
-                <outlet property="gridSwitchButton" destination="jSO-Vb-jcU" id="geS-eg-obV"/>
                 <outlet property="moreButton" destination="3qs-Hm-qLL" id="OPi-J6-wkl"/>
-                <outlet property="moreView" destination="enp-xt-Y5y" id="Fqg-Wl-V7h"/>
-                <outlet property="separatorView" destination="NwM-bk-QAK" id="wRe-37-oxD"/>
+                <outlet property="selectButton" destination="EFV-eb-pFF" id="AJu-vY-GS3"/>
                 <outlet property="title" destination="IxY-xH-yZQ" id="ZNZ-Jy-JbH"/>
-                <outlet property="zoomInButton" destination="ZkU-dK-sp1" id="bC0-Wo-2as"/>
-                <outlet property="zoomOutButton" destination="swp-Cd-CbD" id="wvm-jt-8nI"/>
             </connections>
             <point key="canvasLocation" x="138.75" y="152.5"/>
         </view>
     </objects>
-    <resources>
-        <image name="ellipsis" catalog="system" width="128" height="37"/>
-        <image name="minus" catalog="system" width="128" height="26"/>
-        <image name="plus" catalog="system" width="128" height="113"/>
-        <image name="plus.forwardslash.minus" catalog="system" width="128" height="115"/>
-    </resources>
 </document>

+ 137 - 0
iOSClient/Media/NCMediaDataSource.swift

@@ -0,0 +1,137 @@
+//
+//  NCMediaDataSource.swift
+//  Nextcloud
+//
+//  Created by Marino Faggiana on 25/01/24.
+//  Copyright © 2024 Marino Faggiana. All rights reserved.
+//
+//  Author Marino Faggiana <marino.faggiana@nextcloud.com>
+//
+//  This program is free software: you can redistribute it and/or modify
+//  it under the terms of the GNU General Public License as published by
+//  the Free Software Foundation, either version 3 of the License, or
+//  (at your option) any later version.
+//
+//  This program is distributed in the hope that it will be useful,
+//  but WITHOUT ANY WARRANTY; without even the implied warranty of
+//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+//  GNU General Public License for more details.
+//
+//  You should have received a copy of the GNU General Public License
+//  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+//
+
+import UIKit
+import NextcloudKit
+
+extension NCMedia {
+
+    func getPredicate(showAll: Bool = false) -> NSPredicate {
+
+        let startServerUrl = NCUtilityFileSystem().getHomeServer(urlBase: appDelegate.urlBase, userId: appDelegate.userId) + mediaPath
+
+        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)
+        } else {
+           return NSPredicate(format: NCImageCache.shared.showBothPredicateMediaString, appDelegate.account, startServerUrl)
+        }
+    }
+
+    @objc func reloadDataSource() {
+        guard !appDelegate.account.isEmpty else { return }
+
+        self.metadatas = NCImageCache.shared.getMediaMetadatas(account: self.appDelegate.account, predicate: self.getPredicate())
+        DispatchQueue.main.async {
+            self.collectionView?.reloadData()
+            self.mediaCommandView?.setMediaCommand()
+        }
+    }
+
+    // MARK: - Search media
+
+    @objc func searchMediaUI() {
+
+        var lessDate: Date?
+        var greaterDate: Date?
+        let firstMetadataDate = metadatas?.first?.date as? Date
+        let lastMetadataDate = metadatas?.last?.date as? Date
+
+        guard loadingTask == nil, !isEditMode else {
+            return
+        }
+
+        if let visibleCells = self.collectionView?.indexPathsForVisibleItems.sorted(by: { $0.row < $1.row }).compactMap({ self.collectionView?.cellForItem(at: $0) }) {
+            // first date
+            let firstCellDate = (visibleCells.first as? NCGridMediaCell)?.date
+            if firstCellDate == firstMetadataDate {
+                lessDate = Date.distantFuture
+            } else {
+                if let date = firstCellDate {
+                    lessDate = Calendar.current.date(byAdding: .second, value: 1, to: date)!
+                } else {
+                    lessDate = Date.distantFuture
+                }
+            }
+            // last date
+            let lastCellDate = (visibleCells.last as? NCGridMediaCell)?.date
+            if lastCellDate == lastMetadataDate {
+                greaterDate = Date.distantPast
+            } else {
+                if let date = lastCellDate {
+                    greaterDate = Calendar.current.date(byAdding: .second, value: -1, to: date)!
+                } else {
+                    greaterDate = Date.distantPast
+                }
+            }
+
+            if let lessDate, let greaterDate {
+                mediaCommandView?.activityIndicator.startAnimating()
+                loadingTask = Task.detached {
+                    await self.collectionView.reloadData()
+                    let results = await self.searchMedia(account: self.appDelegate.account, lessDate: lessDate, greaterDate: greaterDate)
+                    print("Media results changed items: \(results.isChanged)")
+                    if results.error != .success {
+                        NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] Media search new media error code \(results.error.errorCode) " + results.error.errorDescription)
+                    }
+                    await self.mediaCommandView?.activityIndicator.stopAnimating()
+                    Task { @MainActor in
+                        self.loadingTask = nil
+                    }
+                    if results.error == .success, results.lessDate == Date.distantFuture, results.greaterDate == Date.distantPast, !results.isChanged, results.metadatasCount == 0 {
+                        Task { @MainActor in
+                            self.metadatas = nil
+                        }
+                        await self.collectionView.reloadData()
+                    }
+                    if results.isChanged {
+                        await self.reloadDataSource()
+                    }
+                }
+            }
+        }
+    }
+
+    func searchMedia(account: String, lessDate: Date, greaterDate: Date, limit: Int = 300, timeout: TimeInterval = 60) async -> (account: String, lessDate: Date?, greaterDate: Date?, metadatasCount: Int, isChanged: Bool, error: NKError) {
+
+        guard let mediaPath = NCManageDatabase.shared.getActiveAccount()?.mediaPath else {
+            return(account, lessDate, greaterDate, 0, false, NKError())
+        }
+        let options = NKRequestOptions(timeout: timeout, queue: NextcloudKit.shared.nkCommonInstance.backgroundQueue)
+        let results = await NextcloudKit.shared.searchMedia(path: mediaPath, lessDate: lessDate, greaterDate: greaterDate, elementDate: "d:getlastmodified/", limit: limit, showHiddenFiles: NCKeychain().showHiddenFiles, includeHiddenFiles: [], options: options)
+
+        if results.account == account, results.error == .success {
+            let metadatas = await NCManageDatabase.shared.convertFilesToMetadatas(results.files, useMetadataFolder: false).metadatas
+            var predicate = NSPredicate(format: "date > %@ AND date < %@", greaterDate as NSDate, lessDate as NSDate)
+            predicate = NSCompoundPredicate(andPredicateWithSubpredicates: [predicate, self.getPredicate(showAll: true)])
+            let resultsUpdate = NCManageDatabase.shared.updateMetadatas(metadatas, predicate: predicate)
+            let isChaged: Bool = resultsUpdate.metadatasChanged || resultsUpdate.metadatasChangedCount != 0
+            return(account, lessDate, greaterDate, metadatas.count, isChaged, results.error)
+        } else {
+            return(account, lessDate, greaterDate, 0, false, results.error)
+        }
+    }
+}

+ 84 - 0
iOSClient/Media/NCMediaDownloadThumbnaill.swift

@@ -0,0 +1,84 @@
+//
+//  NCMediaDownloadThumbnaill.swift
+//  Nextcloud
+//
+//  Created by Marino Faggiana on 25/01/24.
+//  Copyright © 2024 Marino Faggiana. All rights reserved.
+//
+//  Author Marino Faggiana <marino.faggiana@nextcloud.com>
+//
+//  This program is free software: you can redistribute it and/or modify
+//  it under the terms of the GNU General Public License as published by
+//  the Free Software Foundation, either version 3 of the License, or
+//  (at your option) any later version.
+//
+//  This program is distributed in the hope that it will be useful,
+//  but WITHOUT ANY WARRANTY; without even the implied warranty of
+//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+//  GNU General Public License for more details.
+//
+//  You should have received a copy of the GNU General Public License
+//  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+//
+
+import UIKit
+import NextcloudKit
+import Queuer
+
+class NCMediaDownloadThumbnaill: ConcurrentOperation {
+
+    var metadata: tableMetadata
+    var collectionView: UICollectionView?
+    var fileNamePath: String
+    var fileNamePreviewLocalPath: String
+    var fileNameIconLocalPath: String
+    let utilityFileSystem = NCUtilityFileSystem()
+
+    init(metadata: tableMetadata, collectionView: UICollectionView?) {
+        self.metadata = tableMetadata.init(value: metadata)
+        self.collectionView = collectionView
+        self.fileNamePath = utilityFileSystem.getFileNamePath(metadata.fileName, serverUrl: metadata.serverUrl, urlBase: metadata.urlBase, userId: metadata.userId)
+        self.fileNamePreviewLocalPath = utilityFileSystem.getDirectoryProviderStoragePreviewOcId(metadata.ocId, etag: metadata.etag)
+        self.fileNameIconLocalPath = utilityFileSystem.getDirectoryProviderStorageIconOcId(metadata.ocId, etag: metadata.etag)
+    }
+
+    override func start() {
+
+        guard !isCancelled else { return self.finish() }
+
+        var etagResource: String?
+        if FileManager.default.fileExists(atPath: fileNameIconLocalPath) && FileManager.default.fileExists(atPath: fileNamePreviewLocalPath) {
+            etagResource = metadata.etagResource
+        }
+
+        NextcloudKit.shared.downloadPreview(fileNamePathOrFileId: fileNamePath,
+                                            fileNamePreviewLocalPath: fileNamePreviewLocalPath,
+                                            widthPreview: NCGlobal.shared.sizePreview,
+                                            heightPreview: NCGlobal.shared.sizePreview,
+                                            fileNameIconLocalPath: fileNameIconLocalPath,
+                                            sizeIcon: NCGlobal.shared.sizeIcon,
+                                            etag: etagResource,
+                                            options: NKRequestOptions(queue: NextcloudKit.shared.nkCommonInstance.backgroundQueue)) { _, imagePreview, _, _, etag, error in
+
+            if error == .success, let image = imagePreview {
+                NCManageDatabase.shared.setMetadataEtagResource(ocId: self.metadata.ocId, etagResource: etag)
+                DispatchQueue.main.async {
+                    if let visibleCells = self.collectionView?.indexPathsForVisibleItems.sorted(by: { $0.row < $1.row }).compactMap({ self.collectionView?.cellForItem(at: $0) }) {
+                        for case let cell as NCGridMediaCell in visibleCells {
+                            if cell.fileObjectId == self.metadata.ocId, let filePreviewImageView = cell.filePreviewImageView {
+                                UIView.transition(with: filePreviewImageView,
+                                                  duration: 0.75,
+                                                  options: .transitionCrossDissolve,
+                                                  animations: { filePreviewImageView.image = image },
+                                                  completion: nil)
+                                break
+                            }
+                        }
+                    }
+                }
+                NCImageCache.shared.setMediaImage(ocId: self.metadata.ocId, etag: self.metadata.etag, image: .actual(image))
+            }
+            self.finish()
+        }
+    }
+}

+ 64 - 0
iOSClient/Media/NCMediaGridLayout.swift

@@ -0,0 +1,64 @@
+//
+//  NCMediaGridLayout.swift
+//  Nextcloud
+//
+//  Created by Marino Faggiana on 27/01/24.
+//  Copyright © 2024 Marino Faggiana. All rights reserved.
+//
+
+import UIKit
+
+class NCMediaGridLayout: UICollectionViewFlowLayout {
+
+    var marginLeftRight: CGFloat = 2
+    var itemForLine: CGFloat = 3
+
+    override init() {
+        super.init()
+
+        sectionHeadersPinToVisibleBounds = false
+
+        minimumInteritemSpacing = 0
+        minimumLineSpacing = marginLeftRight
+
+        self.scrollDirection = .vertical
+        self.sectionInset = UIEdgeInsets(top: 0, left: marginLeftRight, bottom: 0, right: marginLeftRight)
+    }
+
+    required init?(coder aDecoder: NSCoder) {
+        fatalError("init(coder:) has not been implemented")
+    }
+
+    override var itemSize: CGSize {
+        get {
+            if let collectionView = collectionView {
+
+                let itemWidth: CGFloat = (collectionView.frame.width - marginLeftRight * 2 - marginLeftRight * (itemForLine - 1)) / itemForLine
+                let itemHeight: CGFloat = itemWidth
+
+                return CGSize(width: itemWidth, height: itemHeight)
+            }
+
+            // Default fallback
+            return CGSize(width: 100, height: 100)
+        }
+        set {
+            super.itemSize = newValue
+        }
+    }
+
+    override func targetContentOffset(forProposedContentOffset proposedContentOffset: CGPoint) -> CGPoint {
+        return proposedContentOffset
+    }
+}
+
+extension NCMedia: UICollectionViewDelegateFlowLayout {
+
+    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize {
+        return CGSize(width: collectionView.frame.width, height: 0)
+    }
+
+    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForFooterInSection section: Int) -> CGSize {
+        return CGSize(width: collectionView.frame.width, height: 0)
+    }
+}

+ 18 - 4
iOSClient/Menu/NCCollectionViewCommon+Menu.swift

@@ -287,9 +287,16 @@ extension NCCollectionViewCommon {
                     order: 110,
                     action: { _ in
                         if self.utilityFileSystem.fileProviderStorageExists(metadata) {
-                            NotificationCenter.default.postOnMainThread(name: NCGlobal.shared.notificationCenterDownloadedFile, userInfo: ["ocId": metadata.ocId, "selector": NCGlobal.shared.selectorSaveAsScan, "error": NKError(), "account": metadata.account])
+                            NotificationCenter.default.post(
+                                name: Notification.Name(rawValue: NCGlobal.shared.notificationCenterDownloadedFile),
+                                object: nil,
+                                userInfo: ["ocId": metadata.ocId,
+                                           "selector": NCGlobal.shared.selectorSaveAsScan,
+                                           "error": NKError(),
+                                           "account": metadata.account])
                         } else {
-                            NCNetworking.shared.download(metadata: metadata, selector: NCGlobal.shared.selectorSaveAsScan) { _, _ in }
+                            guard let metadata = NCManageDatabase.shared.setMetadataSessionInWaitDownload(ocId: metadata.ocId, selector: NCGlobal.shared.selectorSaveAsScan) else { return }
+                            NCNetworking.shared.download(metadata: metadata, withNotificationProgressTask: true)
                         }
                     }
                 )
@@ -347,9 +354,16 @@ extension NCCollectionViewCommon {
                     order: 150,
                     action: { _ in
                         if self.utilityFileSystem.fileProviderStorageExists(metadata) {
-                            NotificationCenter.default.postOnMainThread(name: NCGlobal.shared.notificationCenterDownloadedFile, userInfo: ["ocId": metadata.ocId, "selector": NCGlobal.shared.selectorLoadFileQuickLook, "error": NKError(), "account": metadata.account])
+                            NotificationCenter.default.post(
+                                name: Notification.Name(rawValue: NCGlobal.shared.notificationCenterDownloadedFile),
+                                object: nil,
+                                userInfo: ["ocId": metadata.ocId,
+                                           "selector": NCGlobal.shared.selectorLoadFileQuickLook,
+                                           "error": NKError(),
+                                           "account": metadata.account])
                         } else {
-                            NCNetworking.shared.download(metadata: metadata, selector: NCGlobal.shared.selectorLoadFileQuickLook) { _, _ in }
+                            guard let metadata = NCManageDatabase.shared.setMetadataSessionInWaitDownload(ocId: metadata.ocId, selector: NCGlobal.shared.selectorLoadFileQuickLook) else { return }
+                            NCNetworking.shared.download(metadata: metadata, withNotificationProgressTask: true)
                         }
                     }
                 )

+ 23 - 5
iOSClient/Menu/NCContextMenu.swift

@@ -74,10 +74,18 @@ class NCContextMenu: NSObject {
         let openIn = UIAction(title: NSLocalizedString("_open_in_", comment: ""),
                               image: UIImage(systemName: "square.and.arrow.up") ) { _ in
             if self.utilityFileSystem.fileProviderStorageExists(metadata) {
-                NotificationCenter.default.postOnMainThread(name: NCGlobal.shared.notificationCenterDownloadedFile, userInfo: ["ocId": metadata.ocId, "selector": NCGlobal.shared.selectorOpenIn, "error": NKError(), "account": metadata.account])
+                NotificationCenter.default.post(
+                    name: Notification.Name(rawValue: NCGlobal.shared.notificationCenterDownloadedFile),
+                    object: nil,
+                    userInfo: ["ocId": metadata.ocId,
+                               "selector": NCGlobal.shared.selectorOpenIn,
+                               "error": NKError(),
+                               "account": metadata.account])
             } else {
+                guard let metadata = NCManageDatabase.shared.setMetadataSessionInWaitDownload(ocId: metadata.ocId, selector: NCGlobal.shared.selectorOpenIn) else { return }
                 hud.show(in: viewController.view)
-                NCNetworking.shared.download(metadata: metadata, selector: NCGlobal.shared.selectorOpenIn, notificationCenterProgressTask: false) { request in
+                NCNetworking.shared.download(metadata: metadata, withNotificationProgressTask: false) {
+                } requestHandler: { request in
                     downloadRequest = request
                 } progressHandler: { progress in
                     hud.progress = Float(progress.fractionCompleted)
@@ -106,8 +114,10 @@ class NCContextMenu: NSObject {
                 if self.utilityFileSystem.fileProviderStorageExists(metadata) {
                     NCActionCenter.shared.saveAlbum(metadata: metadata)
                 } else {
+                    guard let metadata = NCManageDatabase.shared.setMetadataSessionInWaitDownload(ocId: metadata.ocId, selector: NCGlobal.shared.selectorSaveAlbum) else { return }
                     hud.show(in: viewController.view)
-                    NCNetworking.shared.download(metadata: metadata, selector: NCGlobal.shared.selectorSaveAlbum, notificationCenterProgressTask: false) { request in
+                    NCNetworking.shared.download(metadata: metadata, withNotificationProgressTask: false) {
+                    } requestHandler: { request in
                         downloadRequest = request
                     } progressHandler: { progress in
                         hud.progress = Float(progress.fractionCompleted)
@@ -127,10 +137,18 @@ class NCContextMenu: NSObject {
         let modify = UIAction(title: NSLocalizedString("_modify_", comment: ""),
                               image: UIImage(systemName: "pencil.tip.crop.circle")) { _ in
             if self.utilityFileSystem.fileProviderStorageExists(metadata) {
-                NotificationCenter.default.postOnMainThread(name: NCGlobal.shared.notificationCenterDownloadedFile, userInfo: ["ocId": metadata.ocId, "selector": NCGlobal.shared.selectorLoadFileQuickLook, "error": NKError(), "account": metadata.account])
+                NotificationCenter.default.post(
+                    name: Notification.Name(rawValue: NCGlobal.shared.notificationCenterDownloadedFile),
+                    object: nil,
+                    userInfo: ["ocId": metadata.ocId,
+                               "selector": NCGlobal.shared.selectorLoadFileQuickLook,
+                               "error": NKError(),
+                               "account": metadata.account])
             } else {
+                guard let metadata = NCManageDatabase.shared.setMetadataSessionInWaitDownload(ocId: metadata.ocId, selector: NCGlobal.shared.selectorLoadFileQuickLook) else { return }
                 hud.show(in: viewController.view)
-                NCNetworking.shared.download(metadata: metadata, selector: NCGlobal.shared.selectorLoadFileQuickLook, notificationCenterProgressTask: false) { request in
+                NCNetworking.shared.download(metadata: metadata, withNotificationProgressTask: false) {
+                } requestHandler: { request in
                     downloadRequest = request
                 } progressHandler: { progress in
                     hud.progress = Float(progress.fractionCompleted)

+ 0 - 205
iOSClient/Menu/NCMedia+Menu.swift

@@ -1,205 +0,0 @@
-//
-//  NCMedia+Menu.swift
-//  Nextcloud
-//
-//  Created by Marino Faggiana on 03/03/2021.
-//  Copyright © 2021 Marino Faggiana. All rights reserved.
-//
-//  Author Marino Faggiana <marino.faggiana@nextcloud.com>
-//
-//  This program is free software: you can redistribute it and/or modify
-//  it under the terms of the GNU General Public License as published by
-//  the Free Software Foundation, either version 3 of the License, or
-//  (at your option) any later version.
-//
-//  This program is distributed in the hope that it will be useful,
-//  but WITHOUT ANY WARRANTY; without even the implied warranty of
-//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-//  GNU General Public License for more details.
-//
-//  You should have received a copy of the GNU General Public License
-//  along with this program.  If not, see <http://www.gnu.org/licenses/>.
-//
-
-import UIKit
-import FloatingPanel
-import NextcloudKit
-
-extension NCMedia {
-    func tapSelect() {
-        self.isEditMode = false
-        self.selectOcId.removeAll()
-        self.selectIndexPath.removeAll()
-        self.collectionView?.reloadData()
-    }
-
-    func toggleMenu() {
-
-        var actions: [NCMenuAction] = []
-
-        defer { presentMenu(with: actions) }
-
-        if !isEditMode {
-            if let metadatas = self.metadatas, !metadatas.isEmpty {
-                actions.append(
-                    NCMenuAction(
-                        title: NSLocalizedString("_select_", comment: ""),
-                        icon: utility.loadImage(named: "checkmark.circle.fill"),
-                        action: { _ in
-                            self.isEditMode = true
-                        }
-                    )
-                )
-            }
-
-            actions.append(.seperator(order: 0))
-
-            actions.append(
-                NCMenuAction(
-                    title: NSLocalizedString("_select_media_folder_", comment: ""),
-                    icon: utility.loadImage(named: "folder"),
-                    action: { _ in
-                        if let navigationController = UIStoryboard(name: "NCSelect", bundle: nil).instantiateInitialViewController() as? UINavigationController,
-                           let viewController = navigationController.topViewController as? NCSelect {
-
-                            viewController.delegate = self
-                            viewController.typeOfCommandView = .select
-                            viewController.type = "mediaFolder"
-                            viewController.selectIndexPath = self.selectIndexPath
-
-                            self.present(navigationController, animated: true, completion: nil)
-                        }
-                    }
-                )
-            )
-
-            actions.append(.seperator(order: 0))
-
-            actions.append(
-                NCMenuAction(
-                    title: NSLocalizedString("_media_viewimage_show_", comment: ""),
-                    icon: utility.loadImage(named: "photo"),
-                    selected: showOnlyImages,
-                    on: true,
-                    action: { _ in
-                        self.showOnlyImages = true
-                        self.showOnlyVideos = false
-                        self.reloadDataSource()
-                    }
-                )
-            )
-
-            actions.append(
-                NCMenuAction(
-                    title: NSLocalizedString("_media_viewvideo_show_", comment: ""),
-                    icon: utility.loadImage(named: "video"),
-                    selected: showOnlyVideos,
-                    on: true,
-                    action: { _ in
-                        self.showOnlyImages = false
-                        self.showOnlyVideos = true
-                        self.reloadDataSource()
-                    }
-                )
-            )
-
-            actions.append(
-                NCMenuAction(
-                    title: NSLocalizedString("_media_show_all_", comment: ""),
-                    icon: utility.loadImage(named: "photo.on.rectangle.angled"),
-                    selected: !showOnlyImages && !showOnlyVideos,
-                    on: true,
-                    action: { _ in
-                        self.showOnlyImages = false
-                        self.showOnlyVideos = false
-                        self.reloadDataSource()
-                    }
-                )
-            )
-
-            actions.append(.seperator(order: 0))
-
-            actions.append(
-                NCMenuAction(
-                    title: NSLocalizedString("_play_from_files_", comment: ""),
-                    icon: utility.loadImage(named: "play.circle"),
-                    action: { _ in
-                        if let tabBarController = self.appDelegate.window?.rootViewController as? UITabBarController {
-                            self.documentPickerViewController = NCDocumentPickerViewController(tabBarController: tabBarController, isViewerMedia: true, allowsMultipleSelection: false, viewController: self)
-                        }
-                    }
-                )
-            )
-
-            actions.append(
-                NCMenuAction(
-                    title: NSLocalizedString("_play_from_url_", comment: ""),
-                    icon: utility.loadImage(named: "network"),
-                    action: { _ in
-
-                        let alert = UIAlertController(title: NSLocalizedString("_valid_video_url_", comment: ""), message: nil, preferredStyle: .alert)
-                        alert.addAction(UIAlertAction(title: NSLocalizedString("_cancel_", comment: ""), style: .cancel, handler: nil))
-
-                        alert.addTextField(configurationHandler: { textField in
-                            textField.placeholder = "http://myserver.com/movie.mkv"
-                        })
-
-                        alert.addAction(UIAlertAction(title: NSLocalizedString("_ok_", comment: ""), style: .default, handler: { _ in
-                            guard let stringUrl = alert.textFields?.first?.text, !stringUrl.isEmpty, let url = URL(string: stringUrl) else { return }
-                            let fileName = url.lastPathComponent
-                            let metadata = NCManageDatabase.shared.createMetadata(account: self.appDelegate.account, user: self.appDelegate.user, userId: self.appDelegate.userId, fileName: fileName, fileNameView: fileName, ocId: NSUUID().uuidString, serverUrl: "", urlBase: self.appDelegate.urlBase, url: stringUrl, contentType: "")
-                            NCManageDatabase.shared.addMetadata(metadata)
-                            NCViewer().view(viewController: self, metadata: metadata, metadatas: [metadata], imageIcon: nil)
-                        }))
-
-                        self.present(alert, animated: true)
-
-                    }
-                )
-            )
-
-        } else {
-
-            //
-            // CANCEL
-            //
-            actions.append(
-                NCMenuAction(
-                    title: NSLocalizedString("_cancel_", comment: ""),
-                    icon: utility.loadImage(named: "xmark"),
-                    action: { _ in self.tapSelect() }
-                )
-            )
-
-            guard !selectOcId.isEmpty else { return }
-            let selectedMetadatas = selectOcId.compactMap(NCManageDatabase.shared.getMetadataFromOcId)
-
-            //
-            // OPEN IN
-            //
-            actions.append(.openInAction(selectedMetadatas: selectedMetadatas, viewController: self, completion: tapSelect))
-
-            //
-            // SAVE TO PHOTO GALLERY
-            //
-            actions.append(.saveMediaAction(selectedMediaMetadatas: selectedMetadatas, completion: tapSelect))
-
-            //
-            // COPY - MOVE
-            //
-            actions.append(.moveOrCopyAction(selectedMetadatas: selectedMetadatas, indexPath: selectIndexPath, completion: tapSelect))
-
-            //
-            // COPY
-            //
-            actions.append(.copyAction(selectOcId: selectOcId, completion: tapSelect))
-
-            //
-            // DELETE
-            // can't delete from cache because is needed for NCMedia view, and if locked can't delete from server either.
-            if !selectedMetadatas.contains(where: { $0.lock && $0.lockOwner != appDelegate.userId }) {
-                actions.append(.deleteAction(selectedMetadatas: selectedMetadatas, indexPath: selectIndexPath, metadataFolder: nil, viewController: self, completion: tapSelect))
-            }
-        }
-    }
-}

+ 9 - 2
iOSClient/Menu/NCMenuAction.swift

@@ -279,9 +279,16 @@ extension NCMenuAction {
             order: order,
             action: { _ in
                 if NCUtilityFileSystem().fileProviderStorageExists(metadata) {
-                    NotificationCenter.default.postOnMainThread(name: NCGlobal.shared.notificationCenterDownloadedFile, userInfo: ["ocId": metadata.ocId, "selector": NCGlobal.shared.selectorPrint, "error": NKError(), "account": metadata.account])
+                    NotificationCenter.default.post(
+                        name: Notification.Name(rawValue: NCGlobal.shared.notificationCenterDownloadedFile),
+                        object: nil,
+                        userInfo: ["ocId": metadata.ocId,
+                                   "selector": NCGlobal.shared.selectorPrint,
+                                   "error": NKError(),
+                                   "account": metadata.account])
                 } else {
-                    NCNetworking.shared.download(metadata: metadata, selector: NCGlobal.shared.selectorPrint) { _, _ in }
+                    guard let metadata = NCManageDatabase.shared.setMetadataSessionInWaitDownload(ocId: metadata.ocId, selector: NCGlobal.shared.selectorPrint) else { return }
+                    NCNetworking.shared.download(metadata: metadata, withNotificationProgressTask: true)
                 }
             }
         )

+ 7 - 3
iOSClient/Menu/NCOperationSaveLivePhoto.swift

@@ -24,6 +24,7 @@
 import UIKit
 import Queuer
 import JGProgressHUD
+import NextcloudKit
 
 class NCOperationSaveLivePhoto: ConcurrentOperation {
 
@@ -39,7 +40,9 @@ class NCOperationSaveLivePhoto: ConcurrentOperation {
     }
 
     override func start() {
-        guard !isCancelled else { return self.finish() }
+        guard !isCancelled,
+            let metadata = NCManageDatabase.shared.setMetadataSessionInWaitDownload(ocId: metadata.ocId, selector: ""),
+            let metadataLive = NCManageDatabase.shared.setMetadataSessionInWaitDownload(ocId: self.metadataMOV.ocId, selector: "") else { return self.finish() }
 
         DispatchQueue.main.async {
             self.hud.indicatorView = JGProgressHUDRingIndicatorView()
@@ -51,7 +54,8 @@ class NCOperationSaveLivePhoto: ConcurrentOperation {
             self.hud.show(in: (self.appDelegate?.window?.rootViewController?.view)!)
         }
 
-        NCNetworking.shared.download(metadata: metadata, selector: "", notificationCenterProgressTask: false, checkfileProviderStorageExists: true) { _ in
+        NCNetworking.shared.download(metadata: metadata, withNotificationProgressTask: false) {
+        } requestHandler: { _ in
         } progressHandler: { progress in
             self.hud.progress = Float(progress.fractionCompleted)
         } completion: { _, error in
@@ -63,7 +67,7 @@ class NCOperationSaveLivePhoto: ConcurrentOperation {
                 }
                 return self.finish()
             }
-            NCNetworking.shared.download(metadata: self.metadataMOV, selector: "", notificationCenterProgressTask: false, checkfileProviderStorageExists: true) { _ in
+            NCNetworking.shared.download(metadata: metadataLive, withNotificationProgressTask: false, checkfileProviderStorageExists: true) {
                 DispatchQueue.main.async {
                     self.hud.textLabel.text = NSLocalizedString("_download_video_", comment: "")
                     self.hud.detailTextLabel.text = self.metadataMOV.fileName

+ 29 - 7
iOSClient/Menu/NCViewer+Menu.swift

@@ -111,9 +111,16 @@ extension NCViewer {
                     icon: utility.loadImage(named: "printer"),
                     action: { _ in
                         if self.utilityFileSystem.fileProviderStorageExists(metadata) {
-                            NotificationCenter.default.postOnMainThread(name: NCGlobal.shared.notificationCenterDownloadedFile, userInfo: ["ocId": metadata.ocId, "selector": NCGlobal.shared.selectorPrint, "error": NKError(), "account": metadata.account])
+                            NotificationCenter.default.post(
+                                name: Notification.Name(rawValue: NCGlobal.shared.notificationCenterDownloadedFile),
+                                object: nil,
+                                userInfo: ["ocId": metadata.ocId,
+                                           "selector": NCGlobal.shared.selectorPrint,
+                                           "error": NKError(),
+                                           "account": metadata.account])
                         } else {
-                            NCNetworking.shared.download(metadata: metadata, selector: NCGlobal.shared.selectorPrint) { _, _ in }
+                            guard let metadata = NCManageDatabase.shared.setMetadataSessionInWaitDownload(ocId: metadata.ocId, selector: NCGlobal.shared.selectorPrint) else { return }
+                            NCNetworking.shared.download(metadata: metadata, withNotificationProgressTask: true)
                         }
                     }
                 )
@@ -158,9 +165,16 @@ extension NCViewer {
                     icon: utility.loadImage(named: "viewfinder.circle"),
                     action: { _ in
                         if self.utilityFileSystem.fileProviderStorageExists(metadata) {
-                            NotificationCenter.default.postOnMainThread(name: NCGlobal.shared.notificationCenterDownloadedFile, userInfo: ["ocId": metadata.ocId, "selector": NCGlobal.shared.selectorSaveAsScan, "error": NKError(), "account": metadata.account])
+                            NotificationCenter.default.post(
+                                name: Notification.Name(rawValue: NCGlobal.shared.notificationCenterDownloadedFile),
+                                object: nil,
+                                userInfo: ["ocId": metadata.ocId,
+                                           "selector": NCGlobal.shared.selectorSaveAsScan,
+                                           "error": NKError(),
+                                           "account": metadata.account])
                         } else {
-                            NCNetworking.shared.download(metadata: metadata, selector: NCGlobal.shared.selectorSaveAsScan) { _, _ in }
+                            guard let metadata = NCManageDatabase.shared.setMetadataSessionInWaitDownload(ocId: metadata.ocId, selector: NCGlobal.shared.selectorSaveAsScan) else { return }
+                            NCNetworking.shared.download(metadata: metadata, withNotificationProgressTask: true)
                         }
                     }
                 )
@@ -216,7 +230,8 @@ extension NCViewer {
                     title: NSLocalizedString("_download_locally_", comment: ""),
                     icon: utility.loadImage(named: "icloud.and.arrow.down"),
                     action: { _ in
-                        NCNetworking.shared.download(metadata: metadata, selector: "") { _, _ in }
+                        guard let metadata = NCManageDatabase.shared.setMetadataSessionInWaitDownload(ocId: metadata.ocId, selector: "") else { return }
+                        NCNetworking.shared.download(metadata: metadata, withNotificationProgressTask: true)
                     }
                 )
             )
@@ -257,9 +272,16 @@ extension NCViewer {
                     icon: utility.loadImage(named: "pencil.tip.crop.circle"),
                     action: { _ in
                         if self.utilityFileSystem.fileProviderStorageExists(metadata) {
-                            NotificationCenter.default.postOnMainThread(name: NCGlobal.shared.notificationCenterDownloadedFile, userInfo: ["ocId": metadata.ocId, "selector": NCGlobal.shared.selectorLoadFileQuickLook, "error": NKError(), "account": metadata.account])
+                            NotificationCenter.default.post(
+                                name: Notification.Name(rawValue: NCGlobal.shared.notificationCenterDownloadedFile),
+                                object: nil,
+                                userInfo: ["ocId": metadata.ocId,
+                                           "selector": NCGlobal.shared.selectorLoadFileQuickLook,
+                                           "error": NKError(),
+                                           "account": metadata.account])
                         } else {
-                            NCNetworking.shared.download(metadata: metadata, selector: NCGlobal.shared.selectorLoadFileQuickLook) { _, _ in }
+                            guard let metadata = NCManageDatabase.shared.setMetadataSessionInWaitDownload(ocId: metadata.ocId, selector: NCGlobal.shared.selectorLoadFileQuickLook) else { return }
+                            NCNetworking.shared.download(metadata: metadata, withNotificationProgressTask: true)
                         }
                     }
                 )

+ 25 - 12
iOSClient/NCGlobal.swift

@@ -128,7 +128,7 @@ class NCGlobal: NSObject {
     // E2EE
     //
     let e2eePassphraseTest                          = "more over television factory tendency independence international intellectual impress interest sentence pony"
-    @objc let e2eeVersions                          = ["1.1", "1.2"] // ["1.1", "1.2", "2.0"]
+    @objc let e2eeVersions                          = ["1.1", "1.2", "2.0"]
     let e2eeVersionV11                              = "1.1"
     let e2eeVersionV12                              = "1.2"
     let e2eeVersionV20                              = "2.0"
@@ -218,6 +218,8 @@ class NCGlobal: NSObject {
     @objc let errorMethodNotSupported: Int          = 405
     @objc let errorConflict: Int                    = 409
     @objc let errorPreconditionFailed: Int          = 412
+    @objc let errorUnsupportedMediaType: Int        = 415
+    @objc let errorInternalServerError: Int         = 500
     @objc let errorQuota: Int                       = 507
     @objc let errorUnauthorized997: Int             = 997
     @objc let errorExplicitlyCancelled: Int         = -999
@@ -322,17 +324,12 @@ class NCGlobal: NSObject {
     let metadataStatusNormal: Int                   = 0
 
     let metadataStatusWaitDownload: Int             = -1
-    let metadataStatusInDownload: Int               = -2
-    let metadataStatusDownloading: Int              = -3
-    let metadataStatusDownloadError: Int            = -4
+    let metadataStatusDownloading: Int              = -2
+    let metadataStatusDownloadError: Int            = -3
 
     let metadataStatusWaitUpload: Int               = 1
-    let metadataStatusInUpload: Int                 = 2
-    let metadataStatusUploading: Int                = 3
-    let metadataStatusUploadError: Int              = 4
-
-    // Queue Concurrent Operation Download
-    let maxConcurrentOperationCountDownload: Int    = 10
+    let metadataStatusUploading: Int                = 2
+    let metadataStatusUploadError: Int              = 3
 
     //  Hidden files included in the read
     //
@@ -356,12 +353,10 @@ class NCGlobal: NSObject {
     let notificationCenterRichdocumentGrabFocus                 = "richdocumentGrabFocus"
     let notificationCenterReloadDataNCShare                     = "reloadDataNCShare"
     let notificationCenterCloseRichWorkspaceWebView             = "closeRichWorkspaceWebView"
-    let notificationCenterUpdateBadgeNumber                     = "updateBadgeNumber"               // userInfo: counterDownload, counterUpload
     let notificationCenterReloadAvatar                          = "reloadAvatar"
 
     @objc let notificationCenterReloadDataSource                = "reloadDataSource"
     let notificationCenterReloadDataSourceNetwork               = "reloadDataSourceNetwork"
-    let notificationCenterReloadDataSourceNetworkForced         = "reloadDataSourceNetworkForced"
 
     let notificationCenterChangeStatusFolderE2EE                = "changeStatusFolderE2EE"          // userInfo: serverUrl
 
@@ -371,10 +366,14 @@ class NCGlobal: NSObject {
 
     let notificationCenterUploadStartFile                       = "uploadStartFile"                 // userInfo: ocId, serverUrl, account, fileName, sessionSelector
     @objc let notificationCenterUploadedFile                    = "uploadedFile"                    // userInfo: ocId, serverUrl, account, fileName, ocIdTemp, error
+    let notificationCenterUploadedLivePhoto                     = "uploadedLivePhoto"               // userInfo: ocId, serverUrl, account, fileName, ocIdTemp, error
+
     let notificationCenterUploadCancelFile                      = "uploadCancelFile"                // userInfo: ocId, serverUrl, account
 
     let notificationCenterProgressTask                          = "progressTask"                    // userInfo: account, ocId, serverUrl, status, chunk, e2eEncrypted, progress, totalBytes, totalBytesExpected
 
+    let notificationCenterUpdateBadgeNumber                     = "updateBadgeNumber"
+
     let notificationCenterCreateFolder                          = "createFolder"                    // userInfo: ocId, serverUrl, account, withPush
     let notificationCenterDeleteFile                            = "deleteFile"                      // userInfo: [ocId], [indexPath], onlyLocalCache, error
     let notificationCenterMoveFile                              = "moveFile"                        // userInfo: [ocId], [indexPath], error
@@ -476,6 +475,8 @@ class NCGlobal: NSObject {
         return capabilityServerVersionMajor >= nextcloudVersion28
     }
 
+    var capabilitySecurityGuardDiagnostics                      = false
+
     // MORE NEXTCLOUD APPS
     let talkSchemeUrl                                           = "nextcloudtalk://"
     let notesSchemeUrl                                          = "nextcloudnotes://"
@@ -490,4 +491,16 @@ class NCGlobal: NSObject {
     // FORBIDDEN CHARACTERS
     //
     let forbiddenCharacters = ["/", "\\", ":", "\"", "|", "?", "*", "<", ">"]
+
+    // DIAGNOSTICS CLIENTS
+    //
+    let diagnosticIssueSyncConflicts        = "sync_conflicts"
+    let diagnosticIssueProblems             = "problems"
+    let diagnosticIssueVirusDetected        = "virus_detected"
+    let diagnosticIssueE2eeErrors           = "e2ee_errors"
+
+    let diagnosticProblemsForbidden         = "CHARACTERS_FORBIDDEN"
+    let diagnosticProblemsBadResponse       = "BAD_SERVER_RESPONSE"
+    let diagnosticProblemsUploadServerError = "UploadError.SERVER_ERROR"
+
 }

+ 18 - 13
iOSClient/NCImageCache.swift

@@ -43,12 +43,17 @@ import RealmSwift
         case actual(_ image: UIImage)
     }
 
+    struct metadataInfo {
+        var etag: String
+        var date: NSDate
+    }
+
     private typealias ThumbnailLRUCache = LRUCache<String, ImageType>
     private lazy var cache: ThumbnailLRUCache = {
         return ThumbnailLRUCache(countLimit: limit)
     }()
-    private var ocIdEtag: [String: String] = [:]
-    @ThreadSafe private var metadatas: Results<tableMetadata>?
+    private var metadatasInfo: [String: metadataInfo] = [:]
+    private var metadatas: ThreadSafeArray<tableMetadata>?
 
     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)')"
@@ -61,7 +66,7 @@ import RealmSwift
         guard account != self.account, !account.isEmpty else { return }
         self.account = account
 
-        ocIdEtag.removeAll()
+        self.metadatasInfo.removeAll()
         self.metadatas = nil
         self.metadatas = getMediaMetadatas(account: account)
         guard let metadatas = self.metadatas, !metadatas.isEmpty else { return }
@@ -76,8 +81,8 @@ import RealmSwift
         var files: [FileInfo] = []
         let startDate = Date()
 
-        for metadata in metadatas {
-            ocIdEtag[metadata.ocId] = metadata.etag
+        metadatas.forEach { metadata in
+            metadatasInfo[metadata.ocId] = metadataInfo(etag: metadata.etag, date: metadata.date)
         }
 
         if let enumerator = manager.enumerator(at: URL(fileURLWithPath: NCUtilityFileSystem().directoryProviderStorage), includingPropertiesForKeys: [.isRegularFileKey], options: [.skipsHiddenFiles]) {
@@ -87,10 +92,10 @@ import RealmSwift
                 guard let resourceValues = try? fileURL.resourceValues(forKeys: resourceKeys),
                       let size = resourceValues.fileSize,
                       size > 0,
-                      let date = resourceValues.creationDate,
-                      let etag = ocIdEtag[ocId],
+                      let date = metadatasInfo[ocId]?.date,
+                      let etag = metadatasInfo[ocId]?.etag,
                       fileName == etag + ext else { continue }
-                files.append(FileInfo(path: fileURL, ocIdEtag: ocId + etag, date: date))
+                files.append(FileInfo(path: fileURL, ocIdEtag: ocId + etag, date: date as Date))
             }
         }
 
@@ -104,7 +109,7 @@ import RealmSwift
         var counter: Int = 0
         for file in files {
             counter += 1
-            if counter > limit { break }
+            if counter > (limit - 100) { break }
             autoreleasepool {
                 if let image = UIImage(contentsOfFile: file.path.path) {
                     cache.setValue(.actual(image), forKey: file.ocIdEtag)
@@ -120,7 +125,7 @@ import RealmSwift
         NextcloudKit.shared.nkCommonInstance.writeLog("--------- ThumbnailLRUCache image process ---------")
     }
 
-    func initialMetadatas() -> Results<tableMetadata>? {
+    func initialMetadatas() -> ThreadSafeArray<tableMetadata>? {
         defer { self.metadatas = nil }
         return self.metadatas
     }
@@ -134,16 +139,16 @@ import RealmSwift
     }
 
     @objc func clearMediaCache() {
-        self.ocIdEtag.removeAll()
+        self.metadatasInfo.removeAll()
         self.metadatas = nil
         cache.removeAllValues()
     }
 
-    func getMediaMetadatas(account: String, predicate: NSPredicate? = nil) -> Results<tableMetadata>? {
+    func getMediaMetadatas(account: String, predicate: NSPredicate? = nil) -> ThreadSafeArray<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 predicateBoth = NSPredicate(format: showBothPredicateMediaString, account.account, startServerUrl)
-        return NCManageDatabase.shared.getResultsMetadatas(predicate: predicate ?? predicateBoth, sorted: "date")
+        return NCManageDatabase.shared.getMediaMetadatas(predicate: predicate ?? predicateBoth)
     }
 
     // MARK: -

+ 2 - 3
iOSClient/Networking/E2EE/NCEndToEndMetadataV1.swift

@@ -146,7 +146,7 @@ extension NCEndToEndMetadata {
                 var encryptedInitializationVector: NSString?
                 var encryptedTag: NSString?
 
-                if let metadataKeyFiledrop = (e2eEncryption.metadataKeyFiledrop.data(using: .utf8)?.base64EncodedString().data(using: .utf8)),
+                if let metadataKeyFiledrop = (e2eEncryption.metadataKey.data(using: .utf8)?.base64EncodedString().data(using: .utf8)),
                    let metadataKeyEncrypted = NCEndToEndEncryption.sharedManager().encryptAsymmetricData(metadataKeyFiledrop, privateKey: privateKey) {
                     encryptedKey = metadataKeyEncrypted.base64EncodedString()
                 }
@@ -155,7 +155,7 @@ extension NCEndToEndMetadata {
                     // Create "encrypted"
                     var json = try encoder.encode(encrypted)
                     json = json.base64EncodedString().data(using: .utf8)!
-                    if let encrypted = NCEndToEndEncryption.sharedManager().encryptPayloadFile(json, key: e2eEncryption.metadataKeyFiledrop, initializationVector: &encryptedInitializationVector, authenticationTag: &encryptedTag) {
+                    if let encrypted = NCEndToEndEncryption.sharedManager().encryptPayloadFile(json, key: e2eEncryption.metadataKey, initializationVector: &encryptedInitializationVector, authenticationTag: &encryptedTag) {
                         let record = E2eeV12.Filedrop(initializationVector: e2eEncryption.initializationVector, authenticationTag: e2eEncryption.authenticationTag, encrypted: encrypted, encryptedKey: encryptedKey, encryptedTag: encryptedTag as? String, encryptedInitializationVector: encryptedInitializationVector as? String)
                         filedrop.updateValue(record, forKey: e2eEncryption.fileNameIdentifier)
                     }
@@ -315,7 +315,6 @@ extension NCEndToEndMetadata {
                                 object.blob = "filedrop"
                                 object.fileName = encrypted.filename
                                 object.key = encrypted.key
-                                object.metadataKeyFiledrop = metadataKeyFiledrop ?? ""
                                 object.initializationVector = filedrop.initializationVector
                                 object.metadataKey = metadataKey
                                 object.metadataVersion = metadataVersion

+ 97 - 99
iOSClient/Networking/E2EE/NCEndToEndMetadataV20.swift

@@ -29,26 +29,26 @@ extension NCEndToEndMetadata {
 
     struct E2eeV20: Codable {
 
-        struct Files: Codable {
-            let authenticationTag: String
-            let filename: String
-            let key: String
-            let mimetype: String
-            let nonce: String
-        }
-
-        struct ciphertext: Codable {
-            let counter: Int
-            let deleted: Bool?
-            let keyChecksums: [String]?
-            let files: [String: Files]?
-            let folders: [String: String]?
-        }
-
         struct Metadata: Codable {
             let ciphertext: String
             let nonce: String
             let authenticationTag: String
+
+            struct ciphertext: Codable {
+                let counter: Int
+                let deleted: Bool?
+                let keyChecksums: [String]?
+                let files: [String: Files]?
+                let folders: [String: String]?
+
+                struct Files: Codable {
+                    let authenticationTag: String
+                    let filename: String
+                    let key: String
+                    let mimetype: String
+                    let nonce: String
+                }
+            }
         }
 
         struct Users: Codable {
@@ -58,20 +58,20 @@ extension NCEndToEndMetadata {
         }
 
         struct Filedrop: Codable {
-            let ciphertext: String?
-            let nonce: String?
-            let authenticationTag: String?
-            let users: [UsersFiledrop]?
+            let ciphertext: String
+            let nonce: String
+            let authenticationTag: String
+            let users: [UsersFiledrop]
 
             struct UsersFiledrop: Codable {
-                let userId: String?
-                let encryptedFiledropKey: String?
+                let userId: String
+                let encryptedFiledropKey: String
             }
         }
 
         let metadata: Metadata
         let users: [Users]?
-        let filedrop: [Filedrop]?
+        let filedrop: [String: Filedrop]?
         let version: String
     }
 
@@ -108,9 +108,7 @@ extension NCEndToEndMetadata {
         var metadataKey: String?
         var keyChecksums: [String] = []
         var usersCodable: [E2eeV20.Users] = []
-        var usersFileDropCodable: [E2eeV20.Filedrop.UsersFiledrop] = []
-        var filesCodable: [String: E2eeV20.Files] = [:]
-        var filedropCodable: [E2eeV20.Filedrop] = []
+        var filesCodable: [String: E2eeV20.Metadata.ciphertext.Files] = [:]
         var folders: [String: String] = [:]
         var counter: Int = 1
 
@@ -130,7 +128,7 @@ extension NCEndToEndMetadata {
             guard var key = NCEndToEndEncryption.sharedManager()?.generateKey() as? Data else {
                 return (nil, nil, 0, NKError(errorCode: NCGlobal.shared.errorUnexpectedResponseFromDB, errorDescription: "_e2e_error_"))
             }
-            if let tableUserId = NCManageDatabase.shared.getE2EUsers(account: account, ocIdServerUrl: directoryTop.ocId, userId: userId),
+            if let tableUserId = NCManageDatabase.shared.getE2EUser(account: account, ocIdServerUrl: directoryTop.ocId, userId: userId),
                let metadataKey = tableUserId.metadataKey {
                 key = metadataKey
             } else {
@@ -155,7 +153,7 @@ extension NCEndToEndMetadata {
 
         } else {
 
-            guard let tableUserId = NCManageDatabase.shared.getE2EUsers(account: account, ocIdServerUrl: directoryTop.ocId, userId: userId), let key = tableUserId.metadataKey else {
+            guard let tableUserId = NCManageDatabase.shared.getE2EUser(account: account, ocIdServerUrl: directoryTop.ocId, userId: userId), let key = tableUserId.metadataKey else {
                 return (nil, nil, 0, NKError(errorCode: NCGlobal.shared.errorUnexpectedResponseFromDB, errorDescription: "_e2e_error_"))
             }
 
@@ -169,7 +167,6 @@ extension NCEndToEndMetadata {
             for user in users {
                 if isDirectoryTop {
                     usersCodable.append(E2eeV20.Users(userId: user.userId, certificate: user.certificate, encryptedMetadataKey: user.encryptedMetadataKey))
-                    // usersFileDropCodable.append(E2eeV20.Filedrop.UsersFiledrop(userId: user.userId, encryptedFiledropKey: user.encryptedFiledropKey))
                 }
                 if let hash = NCEndToEndEncryption.sharedManager().createSHA256(user.metadataKey) {
                     keyChecksums.append(hash)
@@ -189,39 +186,17 @@ extension NCEndToEndMetadata {
 
         for e2eEncryption in e2eEncryptions {
             if e2eEncryption.blob == "files" {
-                let file = E2eeV20.Files(authenticationTag: e2eEncryption.authenticationTag, filename: e2eEncryption.fileName, key: e2eEncryption.key, mimetype: e2eEncryption.mimeType, nonce: e2eEncryption.initializationVector)
+                let file = E2eeV20.Metadata.ciphertext.Files(authenticationTag: e2eEncryption.authenticationTag, filename: e2eEncryption.fileName, key: e2eEncryption.key, mimetype: e2eEncryption.mimeType, nonce: e2eEncryption.initializationVector)
                 filesCodable.updateValue(file, forKey: e2eEncryption.fileNameIdentifier)
             } else if e2eEncryption.blob == "folders" {
                 folders[e2eEncryption.fileNameIdentifier] = e2eEncryption.fileName
-            } else if e2eEncryption.blob == "filedrop" {
-                let filedrop = E2eeV20.Files(authenticationTag: e2eEncryption.authenticationTag, filename: e2eEncryption.fileName, key: e2eEncryption.key, mimetype: e2eEncryption.mimeType, nonce: e2eEncryption.initializationVector)
-                var authenticationTag: NSString?
-                var initializationVector: NSString?
-                do {
-                    let json = try JSONEncoder().encode(filedrop)
-                    let jsonZip = try json.gzipped()
-                    let ciphertext = NCEndToEndEncryption.sharedManager().encryptPayloadFile(jsonZip, key: metadataKey, initializationVector: &initializationVector, authenticationTag: &authenticationTag)
-
-                    guard var ciphertext, let initializationVector = initializationVector as? String, let authenticationTag = authenticationTag as? String else {
-                        return (nil, nil, counter, NKError(errorCode: NCGlobal.shared.errorE2EEEncryptPayloadFile, errorDescription: "_e2e_error_"))
-                    }
-
-                    // Add initializationVector [ANDROID]
-                    ciphertext = ciphertext + "|" + initializationVector
-
-                    let filedrop = E2eeV20.Filedrop(ciphertext: ciphertext, nonce: initializationVector, authenticationTag: authenticationTag, users: usersFileDropCodable)
-                    filedropCodable.append(filedrop)
-
-                } catch let error {
-                    return (nil, nil, counter, NKError(errorCode: NCGlobal.shared.errorE2EEJSon, errorDescription: error.localizedDescription))
-                }
             }
         }
 
         do {
             var authenticationTag: NSString?
             var initializationVector: NSString?
-            let json = try JSONEncoder().encode(E2eeV20.ciphertext(counter: counter, deleted: false, keyChecksums: keyChecksums, files: filesCodable, folders: folders))
+            let json = try JSONEncoder().encode(E2eeV20.Metadata.ciphertext(counter: counter, deleted: false, keyChecksums: keyChecksums, files: filesCodable, folders: folders))
             let jsonZip = try json.gzipped()
             let ciphertextMetadata = NCEndToEndEncryption.sharedManager().encryptPayloadFile(jsonZip, key: metadataKey, initializationVector: &initializationVector, authenticationTag: &authenticationTag)
             guard var ciphertextMetadata, let initializationVector = initializationVector as? String, let authenticationTag = authenticationTag as? String else {
@@ -232,7 +207,7 @@ extension NCEndToEndMetadata {
             ciphertextMetadata = ciphertextMetadata + "|" + initializationVector
 
             let metadataCodable = E2eeV20.Metadata(ciphertext: ciphertextMetadata, nonce: initializationVector, authenticationTag: authenticationTag)
-            let e2eeCodable = E2eeV20(metadata: metadataCodable, users: usersCodable, filedrop: filedropCodable, version: NCGlobal.shared.e2eeVersionV20)
+            let e2eeCodable = E2eeV20(metadata: metadataCodable, users: usersCodable, filedrop: nil, version: NCGlobal.shared.e2eeVersionV20)
             let e2eeData = try JSONEncoder().encode(e2eeCodable)
             e2eeData.printJson()
 
@@ -257,6 +232,8 @@ extension NCEndToEndMetadata {
             return NKError(errorCode: NCGlobal.shared.errorE2EEKeyDecodeMetadata, errorDescription: "_e2e_error_")
         }
 
+        let isDirectoryTop = utilityFileSystem.isDirectoryE2EETop(account: account, serverUrl: serverUrl)
+
         func addE2eEncryption(fileNameIdentifier: String, filename: String, authenticationTag: String, key: String, initializationVector: String, metadataKey: String, mimetype: String, blob: String) {
 
             if let metadata = NCManageDatabase.shared.getMetadata(predicate: NSPredicate(format: "account == %@ AND fileName == %@", account, fileNameIdentifier)) {
@@ -296,53 +273,65 @@ extension NCEndToEndMetadata {
             let filesdrop = json.filedrop
             let version = json.version as String? ?? NCGlobal.shared.e2eeVersionV20
 
-            // SAVE IN DB ALL USER
-            //
-            if let users {
-                for user in users {
-                    var metadataKey: Data?
-                    if let encryptedMetadataKey = user.encryptedMetadataKey {
-                        let data = Data(base64Encoded: encryptedMetadataKey)
-                        if let decrypted = NCEndToEndEncryption.sharedManager().decryptAsymmetricData(data, privateKey: NCKeychain().getEndToEndPrivateKey(account: account)) {
-                            metadataKey = decrypted
+            if isDirectoryTop {
+
+                // SAVE IN DB ALL USER
+                //
+                if let users {
+                    for user in users {
+                        var metadataKey: Data?
+                        if let encryptedMetadataKey = user.encryptedMetadataKey {
+                            let data = Data(base64Encoded: encryptedMetadataKey)
+                            if let decrypted = NCEndToEndEncryption.sharedManager().decryptAsymmetricData(data, privateKey: NCKeychain().getEndToEndPrivateKey(account: account)) {
+                                metadataKey = decrypted
+                            }
                         }
+                        NCManageDatabase.shared.addE2EUsers(account: account, serverUrl: serverUrl, ocIdServerUrl: ocIdServerUrl, userId: user.userId, certificate: user.certificate, encryptedMetadataKey: user.encryptedMetadataKey, metadataKey: metadataKey)
                     }
-                    NCManageDatabase.shared.addE2EUsers(account: account, serverUrl: serverUrl, ocIdServerUrl: ocIdServerUrl, userId: user.userId, certificate: user.certificate, encryptedMetadataKey: user.encryptedMetadataKey, metadataKey: metadataKey)
                 }
             }
 
             // GET metadataKey, decryptedMetadataKey
             //
-            guard let objUsers = NCManageDatabase.shared.getE2EUsers(account: account, ocIdServerUrl: directoryTop.ocId, userId: userId),
-                  let metadataKey = objUsers.metadataKey?.base64EncodedString(),
-                  let decryptedMetadataKey = objUsers.metadataKey else {
+            guard let tableUser = NCManageDatabase.shared.getE2EUser(account: account, ocIdServerUrl: directoryTop.ocId, userId: userId),
+                  let metadataKey = tableUser.metadataKey?.base64EncodedString(),
+                  let decryptedMetadataKey = tableUser.metadataKey else {
                 return NKError(errorCode: NCGlobal.shared.errorE2EENoUserFound, errorDescription: "_e2e_error_")
             }
 
             // SIGNATURE CHECK
             //
-            guard let signature,
-                  verifySignature(account: account, signature: signature, userId: objUsers.userId, metadata: metadata, users: users, version: version, certificate: objUsers.certificate) else {
-                return NKError(errorCode: NCGlobal.shared.errorE2EEKeyVerifySignature, errorDescription: "_e2e_error_")
-
+            if let signature {
+                if !verifySignature(account: account, signature: signature, userId: tableUser.userId, metadata: metadata, users: users, version: version, certificate: tableUser.certificate) {
+                    return NKError(errorCode: NCGlobal.shared.errorE2EEKeyVerifySignature, errorDescription: "_e2e_error_")
+                }
             }
 
             // FILEDROP
-            /*
-            if let filesdrop, let filedropKey = tableE2eUsersV2.filedropKey?.base64EncodedString() {
-                for filedrop in filesdrop {
-                    guard let decryptedFiledrop = NCEndToEndEncryption.sharedManager().decryptPayloadFile(filedrop.ciphertext, key: filedropKey, initializationVector: filedrop.nonce, authenticationTag: filedrop.authenticationTag),
-                          decryptedFiledrop.isGzipped else {
-                        return NKError(errorCode: NCGlobal.shared.errorE2EEKeyFiledropCiphertext, errorDescription: "_e2e_error_")
+            //
+            if let filesdrop {
+                for filedop in filesdrop {
+                    let fileNameIdentifier = filedop.key
+                    let ciphertext = filedop.value.ciphertext
+                    let nonce = filedop.value.nonce
+                    let authenticationTag = filedop.value.authenticationTag
+                    for user in filedop.value.users where user.userId == userId {
+                        let data = Data(base64Encoded: user.encryptedFiledropKey)
+                        if let decryptedFiledropKey = NCEndToEndEncryption.sharedManager().decryptAsymmetricData(data, privateKey: NCKeychain().getEndToEndPrivateKey(account: account)) {
+                            let filedropKey = decryptedFiledropKey.base64EncodedString()
+                            guard let decryptedFiledrop = NCEndToEndEncryption.sharedManager().decryptPayloadFile(ciphertext, key: filedropKey, initializationVector: nonce, authenticationTag: authenticationTag),
+                                  decryptedFiledrop.isGzipped else {
+                                return NKError(errorCode: NCGlobal.shared.errorE2EEKeyFiledropCiphertext, errorDescription: "_e2e_error_")
+                            }
+                            let data = try decryptedFiledrop.gunzipped()
+                            if let jsonText = String(data: data, encoding: .utf8) { print(jsonText) }
+                            let file = try JSONDecoder().decode(E2eeV20.Metadata.ciphertext.Files.self, from: data)
+                            print(file)
+                            addE2eEncryption(fileNameIdentifier: fileNameIdentifier, filename: file.filename, authenticationTag: file.authenticationTag, key: file.key, initializationVector: file.nonce, metadataKey: filedropKey, mimetype: file.mimetype, blob: "files")
+                        }
                     }
-                    let data = try decryptedFiledrop.gunzipped()
-                    if let jsonText = String(data: data, encoding: .utf8) { print(jsonText) }
-                    let file = try JSONDecoder().decode(E2eeV20.Files.self, from: data)
-                    print(file)
-                    addE2eEncryption(fileNameIdentifier: file.key, filename: file.filename, authenticationTag: file.authenticationTag, key: file.key, initializationVector: file.nonce, metadataKey: filedropKey, mimetype: file.mimetype, blob: "filedrop")
                 }
             }
-            */
 
             // CIPHERTEXT METADATA
             //
@@ -353,14 +342,15 @@ extension NCEndToEndMetadata {
 
             let data = try decryptedMetadata.gunzipped()
             if let jsonText = String(data: data, encoding: .utf8) { print(jsonText) }
-            let jsonCiphertextMetadata = try JSONDecoder().decode(E2eeV20.ciphertext.self, from: data)
+            let jsonCiphertextMetadata = try JSONDecoder().decode(E2eeV20.Metadata.ciphertext.self, from: data)
 
             // CHECKSUM CHECK
             //
-            guard let keyChecksums = jsonCiphertextMetadata.keyChecksums,
-                  let hash = NCEndToEndEncryption.sharedManager().createSHA256(decryptedMetadataKey),
-                  keyChecksums.contains(hash) else {
-                return NKError(errorCode: NCGlobal.shared.errorE2EEKeyChecksums, errorDescription: NSLocalizedString("_e2e_error_", comment: ""))
+            if let keyChecksums = jsonCiphertextMetadata.keyChecksums {
+                guard let hash = NCEndToEndEncryption.sharedManager().createSHA256(decryptedMetadataKey),
+                      keyChecksums.contains(hash) else {
+                    return NKError(errorCode: NCGlobal.shared.errorE2EEKeyChecksums, errorDescription: NSLocalizedString("_e2e_error_", comment: ""))
+                }
             }
 
             print("\n\nCOUNTER -------------------------------")
@@ -459,24 +449,32 @@ extension NCEndToEndMetadata {
 
     func verifySignature(account: String, signature: String, userId: String, metadata: E2eeV20.Metadata, users: [E2eeV20.Users]?, version: String, certificate: String) -> Bool {
 
-        guard let users else { return false }
+        var signatureCodable: E2eeV20Signature?
+        var certificates: [String] = []
 
-        var usersSignatureCodable: [E2eeV20Signature.Users] = []
-        for user in users {
-            usersSignatureCodable.append(E2eeV20Signature.Users(userId: user.userId, certificate: user.certificate, encryptedMetadataKey: user.encryptedMetadataKey))
+        if let users {
+            var usersSignatureCodable: [E2eeV20Signature.Users] = []
+            for user in users {
+                usersSignatureCodable.append(E2eeV20Signature.Users(userId: user.userId, certificate: user.certificate, encryptedMetadataKey: user.encryptedMetadataKey))
+            }
+            signatureCodable = E2eeV20Signature(metadata: E2eeV20Signature.Metadata(ciphertext: metadata.ciphertext, nonce: metadata.nonce, authenticationTag: metadata.authenticationTag), users: usersSignatureCodable, version: version)
+            certificates = users.map { $0.certificate }
+        } else {
+            signatureCodable = E2eeV20Signature(metadata: E2eeV20Signature.Metadata(ciphertext: metadata.ciphertext, nonce: metadata.nonce, authenticationTag: metadata.authenticationTag), users: nil, version: version)
+            certificates = [certificate]
         }
-        let signatureCodable = E2eeV20Signature(metadata: E2eeV20Signature.Metadata(ciphertext: metadata.ciphertext, nonce: metadata.nonce, authenticationTag: metadata.authenticationTag), users: usersSignatureCodable, version: version)
 
         do {
             let jsonEncoder = JSONEncoder()
             let json = try jsonEncoder.encode(signatureCodable)
             let dataSerialization = try JSONSerialization.jsonObject(with: json, options: [])
-            let decoded = try? JSONSerialization.data(withJSONObject: dataSerialization, options: [.sortedKeys, .withoutEscapingSlashes])
-            let base64 = decoded!.base64EncodedString()
-            if let base64Data = base64.data(using: .utf8),
-               let signatureData = Data(base64Encoded: signature) {
-                let certificates = users.map { $0.certificate }
-                return NCEndToEndEncryption.sharedManager().verifySignatureCMS(signatureData, data: base64Data, certificates: certificates)
+            if var dataSerialization = dataSerialization as? [String: Any?] {
+                dataSerialization = dataSerialization.compactMapValues { $0 }
+                let decodedSignatureCodable = try JSONSerialization.data(withJSONObject: dataSerialization, options: [.sortedKeys, .withoutEscapingSlashes])
+                let base64 = decodedSignatureCodable.base64EncodedString()
+                if let data = base64.data(using: .utf8), let signatureData = Data(base64Encoded: signature) {
+                    return NCEndToEndEncryption.sharedManager().verifySignatureCMS(signatureData, data: data, certificates: certificates)
+                }
             }
 
         } catch {

+ 19 - 9
iOSClient/Networking/E2EE/NCNetworkingE2EE.swift

@@ -26,7 +26,7 @@ class NCNetworkingE2EE: NSObject {
 
     func isInUpload(account: String, serverUrl: String) -> Bool {
 
-        let counter = NCManageDatabase.shared.getMetadatas(predicate: NSPredicate(format: "account == %@ AND serverUrl == %@ AND (status == %d OR status == %d OR status == %d)", account, serverUrl, NCGlobal.shared.metadataStatusWaitUpload, NCGlobal.shared.metadataStatusInUpload, NCGlobal.shared.metadataStatusUploading)).count
+        let counter = NCManageDatabase.shared.getMetadatas(predicate: NSPredicate(format: "account == %@ AND serverUrl == %@ AND (status == %d OR status == %d)", account, serverUrl, NCGlobal.shared.metadataStatusWaitUpload, NCGlobal.shared.metadataStatusUploading)).count
 
         return counter > 0 ? true : false
     }
@@ -38,6 +38,12 @@ class NCNetworkingE2EE: NSObject {
         return UUID
     }
 
+    func getOptions() -> NKRequestOptions {
+
+        let version = NCGlobal.shared.capabilityE2EEApiVersion == "2.0" ? "v2" : "v1"
+        return NKRequestOptions(version: version)
+    }
+
     func uploadMetadata(account: String, serverUrl: String, userId: String, addUserId: String? = nil, removeUserId: String? = nil) async -> NKError {
 
         var addCertificate: String?
@@ -47,7 +53,7 @@ class NCNetworkingE2EE: NSObject {
         }
 
         if let addUserId {
-            let results = await NextcloudKit.shared.getE2EECertificate(user: addUserId)
+            let results = await NextcloudKit.shared.getE2EECertificate(user: addUserId, options: NCNetworkingE2EE().getOptions())
             if results.error == .success, let certificateUser = results.certificateUser {
                 addCertificate = certificateUser
             } else {
@@ -64,7 +70,7 @@ class NCNetworkingE2EE: NSObject {
 
         // METHOD
         //
-        let resultsGetE2EEMetadata = await NextcloudKit.shared.getE2EEMetadata(fileId: fileId, e2eToken: e2eToken)
+        let resultsGetE2EEMetadata = await NextcloudKit.shared.getE2EEMetadata(fileId: fileId, e2eToken: e2eToken, options: NCNetworkingE2EE().getOptions())
         if resultsGetE2EEMetadata.error == .success {
             method = "PUT"
         } else if resultsGetE2EEMetadata.error.errorCode != NCGlobal.shared.errorResourceNotFound {
@@ -102,13 +108,15 @@ class NCNetworkingE2EE: NSObject {
                           fileId: String,
                           e2eToken: String) async -> NKError {
 
-        let resultsGetE2EEMetadata = await NextcloudKit.shared.getE2EEMetadata(fileId: fileId, e2eToken: e2eToken)
+        let resultsGetE2EEMetadata = await NextcloudKit.shared.getE2EEMetadata(fileId: fileId, e2eToken: e2eToken, options: NCNetworkingE2EE().getOptions())
         guard resultsGetE2EEMetadata.error == .success, let e2eMetadata = resultsGetE2EEMetadata.e2eMetadata else {
             return resultsGetE2EEMetadata.error
         }
 
         let resultsDecodeMetadataError = NCEndToEndMetadata().decodeMetadata(e2eMetadata, signature: resultsGetE2EEMetadata.signature, serverUrl: serverUrl, account: account, urlBase: urlBase, userId: userId)
         guard resultsDecodeMetadataError == .success else {
+            // Client Diagnostic
+            NCManageDatabase.shared.addDiagnostic(account: account, issue: NCGlobal.shared.diagnosticIssueE2eeErrors)
             return resultsDecodeMetadataError
         }
 
@@ -128,10 +136,12 @@ class NCNetworkingE2EE: NSObject {
 
         let resultsEncodeMetadata = NCEndToEndMetadata().encodeMetadata(account: account, serverUrl: serverUrl, userId: userId, addUserId: addUserId, addCertificate: addCertificate, removeUserId: removeUserId)
         guard resultsEncodeMetadata.error == .success, let e2eMetadata = resultsEncodeMetadata.metadata else {
+            // Client Diagnostic
+            NCManageDatabase.shared.addDiagnostic(account: account, issue: NCGlobal.shared.diagnosticIssueE2eeErrors)
             return resultsEncodeMetadata.error
         }
 
-        let putE2EEMetadataResults = await NextcloudKit.shared.putE2EEMetadata(fileId: fileId, e2eToken: e2eToken, e2eMetadata: e2eMetadata, signature: resultsEncodeMetadata.signature, method: method)
+        let putE2EEMetadataResults = await NextcloudKit.shared.putE2EEMetadata(fileId: fileId, e2eToken: e2eToken, e2eMetadata: e2eMetadata, signature: resultsEncodeMetadata.signature, method: method, options: NCNetworkingE2EE().getOptions())
         guard putE2EEMetadataResults.error == .success else {
             return putE2EEMetadataResults.error
         }
@@ -148,7 +158,7 @@ class NCNetworkingE2EE: NSObject {
     func lock(account: String, serverUrl: String) async -> (fileId: String?, e2eToken: String?, error: NKError) {
 
         var e2eToken: String?
-        var e2eCounter = "0"
+        var e2eCounter = "1"
 
         guard let directory = NCManageDatabase.shared.getTableDirectory(predicate: NSPredicate(format: "account == %@ AND serverUrl == %@", account, serverUrl)) else {
             return (nil, nil, NKError(errorCode: NCGlobal.shared.errorUnexpectedResponseFromDB, errorDescription: "_e2e_error_"))
@@ -163,7 +173,7 @@ class NCNetworkingE2EE: NSObject {
             e2eCounter = "\(counter)"
         }
 
-        let resultsLockE2EEFolder = await NextcloudKit.shared.lockE2EEFolder(fileId: directory.fileId, e2eToken: e2eToken, e2eCounter: e2eCounter, method: "POST")
+        let resultsLockE2EEFolder = await NextcloudKit.shared.lockE2EEFolder(fileId: directory.fileId, e2eToken: e2eToken, e2eCounter: e2eCounter, method: "POST", options: NCNetworkingE2EE().getOptions())
         if resultsLockE2EEFolder.error == .success, let e2eToken = resultsLockE2EEFolder.e2eToken {
             NCManageDatabase.shared.setE2ETokenLock(account: account, serverUrl: serverUrl, fileId: directory.fileId, e2eToken: e2eToken)
         }
@@ -177,7 +187,7 @@ class NCNetworkingE2EE: NSObject {
             return
         }
 
-        let resultsLockE2EEFolder = await NextcloudKit.shared.lockE2EEFolder(fileId: tableLock.fileId, e2eToken: tableLock.e2eToken, e2eCounter: nil, method: "DELETE")
+        let resultsLockE2EEFolder = await NextcloudKit.shared.lockE2EEFolder(fileId: tableLock.fileId, e2eToken: tableLock.e2eToken, e2eCounter: nil, method: "DELETE", options: NCNetworkingE2EE().getOptions())
         if resultsLockE2EEFolder.error == .success {
             NCManageDatabase.shared.deleteE2ETokenLock(account: account, serverUrl: serverUrl)
         }
@@ -191,7 +201,7 @@ class NCNetworkingE2EE: NSObject {
 
         Task {
             for result in NCManageDatabase.shared.getE2EAllTokenLock(account: account) {
-                let resultsLockE2EEFolder = await NextcloudKit.shared.lockE2EEFolder(fileId: result.fileId, e2eToken: result.e2eToken, e2eCounter: nil, method: "DELETE")
+                let resultsLockE2EEFolder = await NextcloudKit.shared.lockE2EEFolder(fileId: result.fileId, e2eToken: result.e2eToken, e2eCounter: nil, method: "DELETE", options: NCNetworkingE2EE().getOptions())
                 if resultsLockE2EEFolder.error == .success {
                     NCManageDatabase.shared.deleteE2ETokenLock(account: account, serverUrl: result.serverUrl)
                 }

+ 1 - 1
iOSClient/Networking/E2EE/NCNetworkingE2EECreateFolder.swift

@@ -134,7 +134,7 @@ class NCNetworkingE2EECreateFolder: NSObject {
 
         // SET FOLDER AS E2EE
         //
-        let resultsMarkE2EEFolder = await NextcloudKit.shared.markE2EEFolder(fileId: fileId, delete: false)
+        let resultsMarkE2EEFolder = await NextcloudKit.shared.markE2EEFolder(fileId: fileId, delete: false, options: NCNetworkingE2EE().getOptions())
         guard resultsMarkE2EEFolder.error == .success  else {
             await networkingE2EE.unlock(account: account, serverUrl: serverUrl)
             return resultsMarkE2EEFolder.error

+ 1 - 1
iOSClient/Networking/E2EE/NCNetworkingE2EEMarkFolder.swift

@@ -31,7 +31,7 @@ class NCNetworkingE2EEMarkFolder: NSObject {
         let resultsReadFileOrFolder = await NextcloudKit.shared.readFileOrFolder(serverUrlFileName: serverUrlFileName, depth: "0")
         guard resultsReadFileOrFolder.error == .success, let file = resultsReadFileOrFolder.files.first else { return resultsReadFileOrFolder.error }
 
-        let resultsMarkE2EEFolder = await NextcloudKit.shared.markE2EEFolder(fileId: file.fileId, delete: false)
+        let resultsMarkE2EEFolder = await NextcloudKit.shared.markE2EEFolder(fileId: file.fileId, delete: false, options: NCNetworkingE2EE().getOptions())
         guard resultsMarkE2EEFolder.error == .success else { return resultsMarkE2EEFolder.error }
 
         file.e2eEncrypted = true

+ 49 - 7
iOSClient/Networking/E2EE/NCNetworkingE2EEUpload.swift

@@ -125,7 +125,15 @@ class NCNetworkingE2EEUpload: NSObject {
         let resultsLock = await networkingE2EE.lock(account: metadata.account, serverUrl: metadata.serverUrl)
         guard let e2eToken = resultsLock.e2eToken, let fileId = resultsLock.fileId, resultsLock.error == .success else {
             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": NKError(errorCode: NCGlobal.shared.errorE2EELock, errorDescription: NSLocalizedString("_e2e_error_", comment: ""))])
+            NotificationCenter.default.post(
+                name: Notification.Name(rawValue: NCGlobal.shared.notificationCenterUploadedFile),
+                object: nil,
+                userInfo: ["ocId": metadata.ocId,
+                           "serverUrl": metadata.serverUrl,
+                           "account": metadata.account,
+                           "fileName": metadata.fileName,
+                           "ocIdTemp": ocIdTemp,
+                           "error": NKError(errorCode: NCGlobal.shared.errorE2EELock, errorDescription: NSLocalizedString("_e2e_error_", comment: ""))])
             return NKError(errorCode: NCGlobal.shared.errorE2EELock, errorDescription: NSLocalizedString("_e2e_error_", comment: ""))
         }
 
@@ -144,7 +152,15 @@ class NCNetworkingE2EEUpload: NSObject {
         guard sendE2eeError == .success else {
             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])
+            NotificationCenter.default.post(
+                name: Notification.Name(rawValue: NCGlobal.shared.notificationCenterUploadedFile),
+                object: nil,
+                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)
             return sendE2eeError
         }
@@ -179,7 +195,15 @@ class NCNetworkingE2EEUpload: NSObject {
 
             utilityFileSystem.removeFile(atPath: utilityFileSystem.getDirectoryProviderStorageOcId(metadata.ocId))
             NCManageDatabase.shared.deleteMetadata(predicate: NSPredicate(format: "ocId == %@", metadata.ocId))
-            NotificationCenter.default.postOnMainThread(name: NCGlobal.shared.notificationCenterUploadedFile, userInfo: ["ocId": metadata.ocId, "serverUrl": metadata.serverUrl, "account": metadata.account, "fileName": metadata.fileName, "ocIdTemp": ocIdTemp, "error": resultsSendFile.error])
+            NotificationCenter.default.post(
+                name: Notification.Name(rawValue: NCGlobal.shared.notificationCenterUploadedFile),
+                object: nil,
+                userInfo: ["ocId": metadata.ocId,
+                           "serverUrl": metadata.serverUrl,
+                           "account": metadata.account,
+                           "fileName": metadata.fileName,
+                           "ocIdTemp": ocIdTemp,
+                           "error": resultsSendFile.error])
 
         } else if resultsSendFile.error == .success, let ocId = resultsSendFile.ocId {
 
@@ -193,18 +217,36 @@ class NCNetworkingE2EEUpload: NSObject {
 
             metadata.session = ""
             metadata.sessionError = ""
-            metadata.sessionTaskIdentifier = 0
             metadata.status = NCGlobal.shared.metadataStatusNormal
 
             NCManageDatabase.shared.addMetadata(metadata)
             NCManageDatabase.shared.addLocalFile(metadata: metadata)
             utility.createImageFrom(fileNameView: metadata.fileNameView, ocId: metadata.ocId, etag: metadata.etag, classFile: metadata.classFile)
-            NotificationCenter.default.postOnMainThread(name: NCGlobal.shared.notificationCenterUploadedFile, userInfo: ["ocId": metadata.ocId, "serverUrl": metadata.serverUrl, "account": metadata.account, "fileName": metadata.fileName, "ocIdTemp": ocIdTemp, "error": resultsSendFile.error])
+            NotificationCenter.default.post(
+                name: Notification.Name(rawValue: NCGlobal.shared.notificationCenterUploadedFile),
+                object: nil,
+                userInfo: ["ocId": metadata.ocId,
+                           "serverUrl": metadata.serverUrl,
+                           "account": metadata.account,
+                           "fileName": metadata.fileName,
+                           "ocIdTemp": ocIdTemp,
+                           "error": resultsSendFile.error])
 
         } else {
 
-            NCManageDatabase.shared.setMetadataSession(ocId: metadata.ocId, session: nil, sessionError: resultsSendFile.error.errorDescription, sessionSelector: nil, sessionTaskIdentifier: 0, status: NCGlobal.shared.metadataStatusUploadError, errorCode: resultsSendFile.error.errorCode)
-            NotificationCenter.default.postOnMainThread(name: NCGlobal.shared.notificationCenterUploadedFile, userInfo: ["ocId": metadata.ocId, "serverUrl": metadata.serverUrl, "account": metadata.account, "fileName": metadata.fileName, "ocIdTemp": ocIdTemp, "error": resultsSendFile.error])
+            NCManageDatabase.shared.setMetadataSession(ocId: metadata.ocId,
+                                                       sessionError: resultsSendFile.error.errorDescription,
+                                                       status: NCGlobal.shared.metadataStatusUploadError,
+                                                       errorCode: resultsSendFile.error.errorCode)
+            NotificationCenter.default.post(
+                name: Notification.Name(rawValue: NCGlobal.shared.notificationCenterUploadedFile),
+                object: nil,
+                userInfo: ["ocId": metadata.ocId,
+                           "serverUrl": metadata.serverUrl,
+                           "account": metadata.account,
+                           "fileName": metadata.fileName,
+                           "ocIdTemp": ocIdTemp,
+                           "error": resultsSendFile.error])
         }
 
         return (resultsSendFile.error)

+ 7 - 7
iOSClient/Networking/NCAutoUpload.swift

@@ -50,7 +50,7 @@ class NCAutoUpload: NSObject {
                 NCManageDatabase.shared.setAccountAutoUploadProperty("autoUpload", state: false)
                 return completion(0)
             }
-            Task {
+            DispatchQueue.global().async {
                 self.uploadAssetsNewAndFull(viewController: viewController, selector: NCGlobal.shared.selectorUploadAutoUpload, log: "Init Auto Upload") { items in
                     completion(items)
                 }
@@ -67,7 +67,7 @@ class NCAutoUpload: NSObject {
             let error = NKError(errorCode: NCGlobal.shared.errorInternalError, errorDescription: "_create_full_upload_")
             NCContentPresenter().showWarning(error: error, priority: .max)
             NCActivityIndicator.shared.start()
-            Task {
+            DispatchQueue.global().async {
                 self.uploadAssetsNewAndFull(viewController: viewController, selector: NCGlobal.shared.selectorUploadAutoUploadAll, log: log) { _ in
                     NCActivityIndicator.shared.stop()
                 }
@@ -125,14 +125,14 @@ class NCAutoUpload: NSObject {
                     session = NextcloudKit.shared.nkCommonInstance.sessionIdentifierUpload
                 } else {
                     if assetMediaType == PHAssetMediaType.image && account.autoUploadWWAnPhoto == false {
-                        session = NCNetworking.shared.sessionIdentifierBackground
+                        session = NCNetworking.shared.sessionUploadBackground
                     } else if assetMediaType == PHAssetMediaType.video && account.autoUploadWWAnVideo == false {
-                        session = NCNetworking.shared.sessionIdentifierBackground
+                        session = NCNetworking.shared.sessionUploadBackground
                     } else if assetMediaType == PHAssetMediaType.image && account.autoUploadWWAnPhoto {
-                        session = NCNetworking.shared.sessionIdentifierBackgroundWWan
+                        session = NCNetworking.shared.sessionUploadBackgroundWWan
                     } else if assetMediaType == PHAssetMediaType.video && account.autoUploadWWAnVideo {
-                        session = NCNetworking.shared.sessionIdentifierBackgroundWWan
-                    } else { session = NCNetworking.shared.sessionIdentifierBackground }
+                        session = NCNetworking.shared.sessionUploadBackgroundWWan
+                    } else { session = NCNetworking.shared.sessionUploadBackground }
                 }
 
                 if account.autoUploadCreateSubfolder {

Файлын зөрүү хэтэрхий том тул дарагдсан байна
+ 445 - 165
iOSClient/Networking/NCNetworking.swift


+ 2 - 1
iOSClient/Networking/NCNetworkingCheckRemoteUser.swift

@@ -33,10 +33,11 @@ class NCNetworkingCheckRemoteUser {
               !token.isEmpty,
               let appDelegate = UIApplication.shared.delegate as? AppDelegate else { return }
 
+        NCNetworking.shared.cancelAllQueue()
         NCNetworking.shared.cancelDataTask()
         NCNetworking.shared.cancelDownloadTasks()
         NCNetworking.shared.cancelUploadTasks()
-        NCNetworking.shared.cancelUploadBackgroundTask()
+        NCNetworking.shared.cancelUploadBackgroundTask(withNotification: false)
 
         if NCGlobal.shared.capabilityServerVersionMajor >= NCGlobal.shared.nextcloudVersion17 {
 

+ 71 - 68
iOSClient/Networking/NCNetworkingProcessUpload.swift

@@ -110,16 +110,11 @@ class NCNetworkingProcessUpload: NSObject {
 
         queue.async {
 
-            let metadatasUpload = NCManageDatabase.shared.getMetadatas(predicate: NSPredicate(format: "account == %@ AND (status == %d OR status == %d)", self.appDelegate.account, NCGlobal.shared.metadataStatusInUpload, NCGlobal.shared.metadataStatusUploading))
+            let metadatasUpload = NCManageDatabase.shared.getMetadatas(predicate: NSPredicate(format: "account == %@ AND status == %d", self.appDelegate.account, NCGlobal.shared.metadataStatusUploading))
             let isWiFi = NCNetworking.shared.networkReachability == NKCommon.TypeReachability.reachableEthernetOrWiFi
             var counterUpload = metadatasUpload.count
             let sessionSelectors = [NCGlobal.shared.selectorUploadFileNODelete, NCGlobal.shared.selectorUploadFile, NCGlobal.shared.selectorUploadAutoUpload, NCGlobal.shared.selectorUploadAutoUploadAll]
 
-            // Update Badge
-            let counterBadgeDownload = NCManageDatabase.shared.getMetadatas(predicate: NSPredicate(format: "status < 0"))
-            let counterBadgeUpload = NCManageDatabase.shared.getMetadatas(predicate: NSPredicate(format: "status > 0"))
-            NotificationCenter.default.postOnMainThread(name: NCGlobal.shared.notificationCenterUpdateBadgeNumber, userInfo: ["counterDownload": counterBadgeDownload.count, "counterUpload": counterBadgeUpload.count])
-
             // ** TEST ONLY ONE **
             // E2EE
             let uniqueMetadatas = metadatasUpload.unique(map: { $0.serverUrl })
@@ -154,7 +149,7 @@ class NCNetworkingProcessUpload: NSObject {
                         }
 
                         // Session Extension ? skipped
-                        if metadata.session == NCNetworking.shared.sessionIdentifierBackgroundExtension {
+                        if metadata.session == NCNetworking.shared.sessionUploadBackgroundExtension {
                             continue
                         }
 
@@ -170,7 +165,7 @@ class NCNetworkingProcessUpload: NSObject {
                                 let isInDirectoryE2EE = metadata.isDirectoryE2EE
 
                                 // NO WiFi
-                                if !isWiFi && metadata.session == NCNetworking.shared.sessionIdentifierBackgroundWWan {
+                                if !isWiFi && metadata.session == NCNetworking.shared.sessionUploadBackgroundWWan {
                                     continue
                                 }
 
@@ -178,7 +173,7 @@ class NCNetworkingProcessUpload: NSObject {
                                     continue
                                 }
 
-                                if let metadata = NCManageDatabase.shared.setMetadataStatus(ocId: metadata.ocId, status: NCGlobal.shared.metadataStatusInUpload) {
+                                if let metadata = NCManageDatabase.shared.setMetadataStatus(ocId: metadata.ocId, status: NCGlobal.shared.metadataStatusUploading) {
                                     NCNetworking.shared.upload(metadata: metadata, hudView: self.hudView, hud: self.hud)
                                     if isInDirectoryE2EE || metadata.chunk > 0 {
                                         maxConcurrentOperationUpload = 1
@@ -192,6 +187,11 @@ class NCNetworkingProcessUpload: NSObject {
                     }
                 }
 
+                // Update Badge Number
+                NotificationCenter.default.post(
+                    name: Notification.Name(rawValue: NCGlobal.shared.notificationCenterUpdateBadgeNumber),
+                    object: nil)
+
                 // No upload available ? --> Retry Upload in Error
                 if counterUpload == 0 {
                     let metadatas = NCManageDatabase.shared.getMetadatas(predicate: NSPredicate(format: "account == %@ AND status == %d", self.appDelegate.account, NCGlobal.shared.metadataStatusUploadError))
@@ -200,11 +200,17 @@ class NCNetworkingProcessUpload: NSObject {
                         if metadata.sessionError.contains("\(NCGlobal.shared.errorQuota)") {
                             NextcloudKit.shared.getUserProfile { _, userProfile, _, error in
                                 if error == .success, let userProfile, userProfile.quotaFree > 0, userProfile.quotaFree > metadata.size {
-                                    NCManageDatabase.shared.setMetadataSession(ocId: metadata.ocId, session: NCNetworking.shared.sessionIdentifierBackground, sessionError: "", sessionSelector: nil, sessionTaskIdentifier: 0, status: NCGlobal.shared.metadataStatusWaitUpload, errorCode: nil)
+                                    NCManageDatabase.shared.setMetadataSession(ocId: metadata.ocId,
+                                                                               session: NCNetworking.shared.sessionUploadBackground,
+                                                                               sessionError: "",
+                                                                               status: NCGlobal.shared.metadataStatusWaitUpload)
                                 }
                             }
                         } else {
-                            NCManageDatabase.shared.setMetadataSession(ocId: metadata.ocId, session: NCNetworking.shared.sessionIdentifierBackground, sessionError: "", sessionSelector: nil, sessionTaskIdentifier: 0, status: NCGlobal.shared.metadataStatusWaitUpload, errorCode: nil)
+                            NCManageDatabase.shared.setMetadataSession(ocId: metadata.ocId,
+                                                                       session: NCNetworking.shared.sessionUploadBackground,
+                                                                       sessionError: "",
+                                                                       status: NCGlobal.shared.metadataStatusWaitUpload)
                         }
                     }
 
@@ -271,75 +277,72 @@ class NCNetworkingProcessUpload: NSObject {
 
     func verifyUploadZombie() {
 
-        var session: URLSession?
-        let utilityFileSystem = NCUtilityFileSystem()
+        Task {
+            let utilityFileSystem = NCUtilityFileSystem()
+            var notificationCenter: Bool = false
 
-        // remove leaning upload share extension
-        let metadatasUploadShareExtension = NCManageDatabase.shared.getMetadatas(predicate: NSPredicate(format: "session == %@ AND sessionSelector == %@", NextcloudKit.shared.nkCommonInstance.sessionIdentifierUpload, NCGlobal.shared.selectorUploadFileShareExtension))
-        for metadata in metadatasUploadShareExtension {
-            NCManageDatabase.shared.deleteMetadata(predicate: NSPredicate(format: "ocId == %@", metadata.ocId))
-            utilityFileSystem.removeFile(atPath: utilityFileSystem.getDirectoryProviderStorageOcId(metadata.ocId))
-        }
-
-        // verify metadataStatusInUpload (BACKGROUND)
-        let metadatasInUploadBackground = NCManageDatabase.shared.getMetadatas(
-            predicate: NSPredicate(
-                format: "(session == %@ OR session == %@ OR session == %@) AND status == %d AND sessionTaskIdentifier == 0",
-                NCNetworking.shared.sessionIdentifierBackground,
-                NCNetworking.shared.sessionIdentifierBackgroundExtension,
-                NCNetworking.shared.sessionIdentifierBackgroundWWan,
-                NCGlobal.shared.metadataStatusInUpload))
-        for metadata in metadatasInUploadBackground {
-            DispatchQueue.main.asyncAfter(deadline: .now() + 5) {
-                if let metadata = NCManageDatabase.shared.getMetadata(predicate: NSPredicate(format: "ocId == %@ AND status == %d AND sessionTaskIdentifier == 0", metadata.ocId, NCGlobal.shared.metadataStatusInUpload)) {
-                    NCManageDatabase.shared.setMetadataSession(ocId: metadata.ocId, session: NCNetworking.shared.sessionIdentifierBackground, sessionError: "", sessionSelector: nil, sessionTaskIdentifier: 0, status: NCGlobal.shared.metadataStatusWaitUpload, errorCode: nil)
+            // selectorUploadFileShareExtension (FOREGROUND)
+            if let results = NCManageDatabase.shared.getResultsMetadatas(predicate: NSPredicate(format: "session == %@ AND sessionSelector == %@", NextcloudKit.shared.nkCommonInstance.sessionIdentifierUpload, NCGlobal.shared.selectorUploadFileShareExtension)) {
+                for metadata in results {
+                    NCManageDatabase.shared.deleteMetadata(predicate: NSPredicate(format: "ocId == %@", metadata.ocId))
+                    utilityFileSystem.removeFile(atPath: utilityFileSystem.getDirectoryProviderStorageOcId(metadata.ocId))
+                    notificationCenter = true
                 }
             }
-        }
 
-        // metadataStatusUploading (BACKGROUND)
-        let metadatasUploadingBackground = NCManageDatabase.shared.getMetadatas(predicate: NSPredicate(format: "(session == %@ OR session == %@ OR session == %@) AND status == %d", NCNetworking.shared.sessionIdentifierBackground, NCNetworking.shared.sessionIdentifierBackgroundWWan, NCNetworking.shared.sessionIdentifierBackgroundExtension, NCGlobal.shared.metadataStatusUploading))
-        for metadata in metadatasUploadingBackground {
-
-            if metadata.session == NCNetworking.shared.sessionIdentifierBackground {
-                session = NCNetworking.shared.sessionManagerBackground
-            } else if metadata.session == NCNetworking.shared.sessionIdentifierBackgroundWWan {
-                session = NCNetworking.shared.sessionManagerBackgroundWWan
+            // metadataStatusUploading (FOREGROUND)
+            if let results = NCManageDatabase.shared.getResultsMetadatas(predicate: NSPredicate(format: "session == %@ AND status == %d", NextcloudKit.shared.nkCommonInstance.sessionIdentifierUpload, NCGlobal.shared.metadataStatusUploading)) {
+                if results.isEmpty { NCNetworking.shared.transferInForegorund = nil }
+                for metadata in results {
+                    let fileNameLocalPath = utilityFileSystem.getDirectoryProviderStorageOcId(metadata.ocId, fileNameView: metadata.fileNameView)
+                    if NCNetworking.shared.uploadRequest[fileNameLocalPath] == nil {
+                        NCManageDatabase.shared.setMetadataSession(ocId: metadata.ocId,
+                                                                   status: NCGlobal.shared.metadataStatusWaitUpload)
+                        notificationCenter = true
+                    }
+                }
             }
 
-            var taskUpload: URLSessionTask?
-
-            session?.getAllTasks(completionHandler: { tasks in
-                for task in tasks {
-                    if task.taskIdentifier == metadata.sessionTaskIdentifier {
-                        taskUpload = task
-                    }
+            // metadataStatusDownloading (FOREGROUND)
+            if let results = NCManageDatabase.shared.getResultsMetadatas(predicate: NSPredicate(format: "session == %@ AND status == %d", NextcloudKit.shared.nkCommonInstance.sessionIdentifierDownload, NCGlobal.shared.metadataStatusDownloading)) {
+                for metadata in results {
+                    NCManageDatabase.shared.setMetadataSession(ocId: metadata.ocId,
+                                                               session: "",
+                                                               sessionError: "",
+                                                               selector: "",
+                                                               status: NCGlobal.shared.metadataStatusNormal,
+                                                               errorCode: 0)
+                    notificationCenter = true
                 }
+            }
 
-                if taskUpload == nil {
-                    if let metadata = NCManageDatabase.shared.getMetadata(predicate: NSPredicate(format: "ocId == %@ AND status == %d", metadata.ocId, NCGlobal.shared.metadataStatusUploading)) {
-                        NCManageDatabase.shared.setMetadataSession(ocId: metadata.ocId, session: NCNetworking.shared.sessionIdentifierBackground, sessionError: "", sessionSelector: nil, sessionTaskIdentifier: 0, status: NCGlobal.shared.metadataStatusWaitUpload, errorCode: nil)
+            // metadataStatusUploading (BACKGROUND)
+            let results = NCManageDatabase.shared.getMetadatas(predicate: NSPredicate(format: "(session == %@ OR session == %@ OR session == %@) AND status == %d", NCNetworking.shared.sessionUploadBackground, NCNetworking.shared.sessionUploadBackgroundWWan, NCNetworking.shared.sessionUploadBackgroundExtension, NCGlobal.shared.metadataStatusUploading))
+            for metadata in results {
+                var taskUpload: URLSessionTask?
+                var session: URLSession?
+                if metadata.session == NCNetworking.shared.sessionUploadBackground {
+                    session = NCNetworking.shared.sessionManagerUploadBackground
+                } else if metadata.session == NCNetworking.shared.sessionUploadBackgroundWWan {
+                    session = NCNetworking.shared.sessionManagerUploadBackgroundWWan
+                }
+                if let tasks = await session?.allTasks {
+                    for task in tasks {
+                        if task.taskIdentifier == metadata.sessionTaskIdentifier { taskUpload = task }
+                    }
+                    if taskUpload == nil, let metadata = NCManageDatabase.shared.getResultMetadata(predicate: NSPredicate(format: "ocId == %@ AND status == %d", metadata.ocId, NCGlobal.shared.metadataStatusUploading)) {
+                        NCManageDatabase.shared.setMetadataSession(ocId: metadata.ocId,
+                                                                   session: NCNetworking.shared.sessionUploadBackground,
+                                                                   sessionError: "",
+                                                                   status: NCGlobal.shared.metadataStatusWaitUpload)
+                        notificationCenter = true
                     }
                 }
-            })
-        }
-
-        // metadataStatusUploading OR metadataStatusInUpload (FOREGROUND)
-        let metadatasUploading = NCManageDatabase.shared.getMetadatas(predicate: NSPredicate(format: "session == %@ AND (status == %d OR status == %d)", NextcloudKit.shared.nkCommonInstance.sessionIdentifierUpload, NCGlobal.shared.metadataStatusUploading, NCGlobal.shared.metadataStatusInUpload))
-        if metadatasUploading.isEmpty {
-            NCNetworking.shared.transferInForegorund = nil
-        }
-        for metadata in metadatasUploading {
-            let fileNameLocalPath = utilityFileSystem.getDirectoryProviderStorageOcId(metadata.ocId, fileNameView: metadata.fileNameView)
-            if NCNetworking.shared.uploadRequest[fileNameLocalPath] == nil {
-                NCManageDatabase.shared.setMetadataSession(ocId: metadata.ocId, session: nil, sessionError: "", sessionSelector: nil, sessionTaskIdentifier: 0, status: NCGlobal.shared.metadataStatusWaitUpload, errorCode: nil)
             }
-        }
 
-        // download
-        let metadatasDownload = NCManageDatabase.shared.getMetadatas(predicate: NSPredicate(format: "session == %@", NextcloudKit.shared.nkCommonInstance.sessionIdentifierDownload))
-        for metadata in metadatasDownload {
-            NCManageDatabase.shared.setMetadataSession(ocId: metadata.ocId, session: "", sessionError: "", sessionSelector: "", sessionTaskIdentifier: 0, status: NCGlobal.shared.metadataStatusNormal, errorCode: 0)
+            if notificationCenter {
+                NotificationCenter.default.postOnMainThread(name: NCGlobal.shared.notificationCenterReloadDataSource)
+            }
         }
     }
 }

+ 97 - 29
iOSClient/Networking/NCService.swift

@@ -51,8 +51,7 @@ class NCService: NSObject {
                 requestDashboardWidget()
                 NCNetworkingE2EE().unlockAll(account: account)
                 NCNetworkingProcessUpload.shared.verifyUploadZombie()
-                // TODO: sendClientDiagnosticsRemoteOperation(account: account)
-                // sendClientDiagnosticsRemoteOperation(account: account)
+                sendClientDiagnosticsRemoteOperation(account: account)
             }
         }
     }
@@ -133,7 +132,6 @@ class NCService: NSObject {
 
     func synchronize() {
 
-        NextcloudKit.shared.nkCommonInstance.writeLog("[INFO] start synchronize Favorite")
         NextcloudKit.shared.listingFavorites(showHiddenFiles: NCKeychain().showHiddenFiles,
                                              options: NKRequestOptions(queue: NextcloudKit.shared.nkCommonInstance.backgroundQueue)) { account, files, _, error in
 
@@ -141,8 +139,7 @@ class NCService: NSObject {
             NCManageDatabase.shared.convertFilesToMetadatas(files, useMetadataFolder: false) { _, _, metadatas in
                 NCManageDatabase.shared.updateMetadatasFavorite(account: account, metadatas: metadatas)
             }
-            NextcloudKit.shared.nkCommonInstance.writeLog("[INFO] end synchronize Favorite")
-            NextcloudKit.shared.nkCommonInstance.writeLog("[INFO] start synchronize Offline")
+            NextcloudKit.shared.nkCommonInstance.writeLog("[INFO] Synchronize Favorite")
             self.synchronizeOffline(account: account)
         }
     }
@@ -291,9 +288,11 @@ class NCService: NSObject {
     @objc func synchronizeOffline(account: String) {
 
         // Synchronize Directory
-        if let directories = NCManageDatabase.shared.getTablesDirectory(predicate: NSPredicate(format: "account == %@ AND offline == true", account), sorted: "serverUrl", ascending: true) {
-            for directory: tableDirectory in directories {
-                NCNetworking.shared.synchronizationServerUrl(directory.serverUrl, account: account, selector: NCGlobal.shared.selectorSynchronizationOffline)
+        Task {
+            if let directories = NCManageDatabase.shared.getTablesDirectory(predicate: NSPredicate(format: "account == %@ AND offline == true", account), sorted: "serverUrl", ascending: true) {
+                for directory: tableDirectory in directories {
+                    await NCNetworking.shared.synchronization(account: account, serverUrl: directory.serverUrl, selector: NCGlobal.shared.selectorSynchronizationOffline)
+                }
             }
         }
 
@@ -301,47 +300,116 @@ class NCService: NSObject {
         let files = NCManageDatabase.shared.getTableLocalFiles(predicate: NSPredicate(format: "account == %@ AND offline == true", account), sorted: "fileName", ascending: true)
         for file: tableLocalFile in files {
             guard let metadata = NCManageDatabase.shared.getMetadataFromOcId(file.ocId) else { continue }
-            if NCNetworking.shared.synchronizeMetadata(metadata),
+            if metadata.isSynchronizable,
                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")
     }
 
     // MARK: -
 
     func sendClientDiagnosticsRemoteOperation(account: String) {
 
-        struct Problem: Codable {
-            let count: Int
-            let oldest: TimeInterval
-        }
+        guard NCGlobal.shared.capabilitySecurityGuardDiagnostics,
+              NCManageDatabase.shared.existsDiagnostics(account: account) else { return }
+
+        struct Issues: Codable {
 
-        struct Problems: Codable {
-            var problems: [String: Problem] = [:]
+            struct SyncConflicts: Codable {
+                var count: Int?
+                var oldest: TimeInterval?
+            }
+
+            struct VirusDetected: Codable {
+                var count: Int?
+                var oldest: TimeInterval?
+            }
+
+            struct E2EError: Codable {
+                var count: Int?
+                var oldest: TimeInterval?
+            }
+
+            struct Problem: Codable {
+                struct Error: Codable {
+                    var count: Int
+                    var oldest: TimeInterval
+                }
+
+                var forbidden: Error?               // NCGlobal.shared.diagnosticProblemsForbidden
+                var badResponse: Error?             // NCGlobal.shared.diagnosticProblemsBadResponse
+                var uploadServerError: Error?       // NCGlobal.shared.diagnosticProblemsUploadServerError
+            }
+
+            var syncConflicts: SyncConflicts
+            var virusDetected: VirusDetected
+            var e2eeErrors: E2EError
+            var problems: Problem?
+
+            enum CodingKeys: String, CodingKey {
+                case syncConflicts = "sync_conflicts"
+                case virusDetected = "virus_detected"
+                case e2eeErrors = "e2ee_errors"
+                case problems
+            }
         }
 
-        var problems = Problems()
+        var ids: [ObjectId] = []
 
-        guard let metadatas = NCManageDatabase.shared.getMetadatasInError(account: account), !metadatas.isEmpty else { return }
-        for metadata in metadatas {
-            guard let oldest = metadata.errorCodeDate?.timeIntervalSince1970 else { continue }
-            var key = String(metadata.errorCode)
-            if !metadata.sessionError.isEmpty {
-                key = key + " - " + metadata.sessionError
+        var syncConflicts: Issues.SyncConflicts = Issues.SyncConflicts()
+        var virusDetected: Issues.VirusDetected = Issues.VirusDetected()
+        var e2eeErrors: Issues.E2EError = Issues.E2EError()
+
+        var problems: Issues.Problem? = Issues.Problem()
+        var problemForbidden: Issues.Problem.Error?
+        var problemBadResponse: Issues.Problem.Error?
+        var problemUploadServerError: Issues.Problem.Error?
+
+        if let result = NCManageDatabase.shared.getDiagnostics(account: account, issue: NCGlobal.shared.diagnosticIssueSyncConflicts)?.first {
+            syncConflicts = Issues.SyncConflicts(count: result.counter, oldest: result.oldest)
+            ids.append(result.id)
+        }
+        if let result = NCManageDatabase.shared.getDiagnostics(account: account, issue: NCGlobal.shared.diagnosticIssueVirusDetected)?.first {
+            virusDetected = Issues.VirusDetected(count: result.counter, oldest: result.oldest)
+            ids.append(result.id)
+        }
+        if let result = NCManageDatabase.shared.getDiagnostics(account: account, issue: NCGlobal.shared.diagnosticIssueE2eeErrors)?.first {
+            e2eeErrors = Issues.E2EError(count: result.counter, oldest: result.oldest)
+            ids.append(result.id)
+        }
+        if let results = NCManageDatabase.shared.getDiagnostics(account: account, issue: NCGlobal.shared.diagnosticIssueProblems) {
+            for result in results {
+                switch result.error {
+                case NCGlobal.shared.diagnosticProblemsForbidden:
+                    if result.counter >= 1 {
+                        problemForbidden = Issues.Problem.Error(count: result.counter, oldest: result.oldest)
+                        ids.append(result.id)
+                    }
+                case NCGlobal.shared.diagnosticProblemsBadResponse:
+                    if result.counter >= 2 {
+                        problemBadResponse = Issues.Problem.Error(count: result.counter, oldest: result.oldest)
+                        ids.append(result.id)
+                    }
+                case NCGlobal.shared.diagnosticProblemsUploadServerError:
+                    if result.counter >= 1 {
+                        problemUploadServerError = Issues.Problem.Error(count: result.counter, oldest: result.oldest)
+                        ids.append(result.id)
+                    }
+                default:
+                    break
+                }
             }
-            let value = Problem(count: metadata.errorCodeCounter, oldest: oldest)
-            problems.problems[key] = value
+            problems = Issues.Problem(forbidden: problemForbidden, badResponse: problemBadResponse, uploadServerError: problemUploadServerError)
         }
 
         do {
-            @ThreadSafe var metadatas = metadatas
-            let data = try JSONEncoder().encode(problems)
+            let issues = Issues(syncConflicts: syncConflicts, virusDetected: virusDetected, e2eeErrors: e2eeErrors, problems: problems)
+            let data = try JSONEncoder().encode(issues)
             data.printJson()
-            NextcloudKit.shared.sendClientDiagnosticsRemoteOperation(problems: data, options: NKRequestOptions(queue: NextcloudKit.shared.nkCommonInstance.backgroundQueue)) { _, error in
+            NextcloudKit.shared.sendClientDiagnosticsRemoteOperation(data: data, options: NKRequestOptions(queue: NextcloudKit.shared.nkCommonInstance.backgroundQueue)) { _, error in
                 if error == .success {
-                    NCManageDatabase.shared.clearErrorCodeMetadatas(metadatas: metadatas)
+                    NCManageDatabase.shared.deleteDiagnostics(account: account, ids: ids)
                 }
             }
         } catch {

+ 1 - 1
iOSClient/Notification/NCNotification.swift

@@ -67,7 +67,7 @@ class NCNotification: UITableViewController, NCNotificationCellDelegate, NCEmpty
         super.viewWillAppear(animated)
 
         appDelegate.activeViewController = self
-        navigationController?.setFileAppreance()
+        navigationController?.setNavigationBarAppearance()
         getNetwokingNotification()
     }
 

+ 6 - 17
iOSClient/Offline/NCOffline.swift

@@ -44,12 +44,13 @@ class NCOffline: NCCollectionViewCommon {
     override func viewWillAppear(_ animated: Bool) {
         super.viewWillAppear(animated)
 
-        navigationController?.setFileAppreance()
+        reloadDataSource()
     }
 
     // MARK: - DataSource + NC Endpoint
 
-    override func queryDB(isForced: Bool) {
+    override func queryDB() {
+        super.queryDB()
 
         var ocIds: [String] = []
         var metadatas: [tableMetadata] = []
@@ -81,21 +82,9 @@ class NCOffline: NCCollectionViewCommon {
             searchResults: self.searchResults)
     }
 
-    override func reloadDataSource(isForced: Bool = true) {
-        super.reloadDataSource()
+    override func reloadDataSourceNetwork() {
+        super.reloadDataSourceNetwork()
 
-        DispatchQueue.global().async {
-            self.queryDB(isForced: isForced)
-            DispatchQueue.main.async {
-                self.refreshControl.endRefreshing()
-                self.collectionView.reloadData()
-            }
-        }
-    }
-
-    override func reloadDataSourceNetwork(isForced: Bool = false) {
-        super.reloadDataSourceNetwork(isForced: isForced)
-
-        return self.reloadDataSource()
+        reloadDataSource()
     }
 }

+ 4 - 4
iOSClient/PushNotification/NCPushNotification.m

@@ -105,12 +105,12 @@
     NSData *pushPublicKey = [[[NCKeychain alloc] init] getPushNotificationPublicKeyWithAccount:account];
     NSString *pushDevicePublicKey = [[NSString alloc] initWithData:pushPublicKey encoding:NSUTF8StringEncoding];
     NSString *proxyServerPath = [NCBrandOptions shared].pushNotificationServerProxy;
-    NKRequestOptions *options = [[NKRequestOptions alloc] initWithEndpoint:nil customHeader:nil customUserAgent:nil contentType:nil e2eToken:nil timeout:60 queue:dispatch_get_main_queue()];
+    NKRequestOptions *options = [[NKRequestOptions alloc] initWithEndpoint:nil version:nil customHeader:nil customUserAgent:nil contentType:nil e2eToken:nil timeout:60 queue:dispatch_get_main_queue()];
 
     [[NextcloudKit shared] subscribingPushNotificationWithServerUrl:urlBase account:account user:user password:[[[NCKeychain alloc] init] getPasswordWithAccount:account] pushTokenHash:pushTokenHash devicePublicKey:pushDevicePublicKey proxyServerUrl:proxyServerPath options:options completion:^(NSString *account, NSString *deviceIdentifier, NSString *signature, NSString *publicKey, NSData *data, NKError *error) {
         if (error == NKError.success) {
             NSString *userAgent = [NSString stringWithFormat:@"%@  (Strict VoIP)", [[NCBrandOptions shared] getUserAgent]];
-            NKRequestOptions *options = [[NKRequestOptions alloc] initWithEndpoint:nil customHeader:nil customUserAgent:userAgent contentType:nil e2eToken:nil timeout:60 queue:dispatch_get_main_queue()];
+            NKRequestOptions *options = [[NKRequestOptions alloc] initWithEndpoint:nil version:nil customHeader:nil customUserAgent:userAgent contentType:nil e2eToken:nil timeout:60 queue:dispatch_get_main_queue()];
 
             [[NextcloudKit shared] subscribingPushProxyWithProxyServerUrl:proxyServerPath pushToken:self.pushKitToken deviceIdentifier:deviceIdentifier signature:signature publicKey:publicKey options:options completion:^(NKError *error) {
                 if (error == NKError.success) {
@@ -134,13 +134,13 @@
     NSString *deviceIdentifier = [[[NCKeychain alloc] init] getPushNotificationDeviceIdentifierWithAccount:account];
     NSString *signature = [[[NCKeychain alloc] init] getPushNotificationDeviceIdentifierSignatureWithAccount:account];
     NSString *publicKey = [[[NCKeychain alloc] init] getPushNotificationSubscribingPublicKeyWithAccount:account];
-    NKRequestOptions *options = [[NKRequestOptions alloc] initWithEndpoint:nil customHeader:nil customUserAgent:nil contentType:nil e2eToken:nil timeout:60 queue:dispatch_get_main_queue()];
+    NKRequestOptions *options = [[NKRequestOptions alloc] initWithEndpoint:nil version:nil customHeader:nil customUserAgent:nil contentType:nil e2eToken:nil timeout:60 queue:dispatch_get_main_queue()];
 
     [[NextcloudKit shared] unsubscribingPushNotificationWithServerUrl:urlBase account:account user:user password:[[[NCKeychain alloc] init] getPasswordWithAccount:account] options:options completion:^(NSString *account, NKError *error) {
         if (error == NKError.success) {
             NSString *proxyServerPath = [NCBrandOptions shared].pushNotificationServerProxy;
             NSString *userAgent = [NSString stringWithFormat:@"%@  (Strict VoIP)", [[NCBrandOptions shared] getUserAgent]];
-            NKRequestOptions *options = [[NKRequestOptions alloc] initWithEndpoint:nil customHeader:nil customUserAgent:userAgent contentType:nil e2eToken:nil timeout:60 queue:dispatch_get_main_queue()];
+            NKRequestOptions *options = [[NKRequestOptions alloc] initWithEndpoint:nil version:nil customHeader:nil customUserAgent:userAgent contentType:nil e2eToken:nil timeout:60 queue:dispatch_get_main_queue()];
 
             [[NextcloudKit shared] unsubscribingPushProxyWithProxyServerUrl:proxyServerPath deviceIdentifier:deviceIdentifier signature:signature publicKey:publicKey options:options completion:^(NKError *error) {
                 if (error == NKError.success) {

+ 6 - 23
iOSClient/Recent/NCRecent.swift

@@ -44,12 +44,13 @@ class NCRecent: NCCollectionViewCommon {
     override func viewWillAppear(_ animated: Bool) {
         super.viewWillAppear(animated)
 
-        navigationController?.setFileAppreance()
+        reloadDataSourceNetwork()
     }
 
     // MARK: - DataSource + NC Endpoint
 
-    override func queryDB(isForced: Bool) {
+    override func queryDB() {
+        super.queryDB()
 
         let metadatas = NCManageDatabase.shared.getAdvancedMetadatas(predicate: NSPredicate(format: "account == %@", self.appDelegate.account), page: 1, limit: 100, sorted: "date", ascending: false)
         self.dataSource = NCDataSource(metadatas: metadatas,
@@ -61,20 +62,8 @@ class NCRecent: NCCollectionViewCommon {
                                        searchResults: self.searchResults)
     }
 
-    override func reloadDataSource(isForced: Bool = true) {
-        super.reloadDataSource()
-
-        DispatchQueue.global().async {
-            self.queryDB(isForced: isForced)
-            DispatchQueue.main.async {
-                self.refreshControl.endRefreshing()
-                self.collectionView.reloadData()
-            }
-        }
-    }
-
-    override func reloadDataSourceNetwork(isForced: Bool = false) {
-        super.reloadDataSourceNetwork(isForced: isForced)
+    override func reloadDataSourceNetwork() {
+        super.reloadDataSourceNetwork()
 
         let requestBodyRecent =
         """
@@ -144,17 +133,11 @@ class NCRecent: NCCollectionViewCommon {
         let lessDateString = dateFormatter.string(from: Date())
         let requestBody = String(format: requestBodyRecent, "/files/" + appDelegate.userId, lessDateString)
 
-        NextcloudKit.shared.nkCommonInstance.writeLog("[INFO] Reload data source network recent forced \(isForced)")
-
-        isReloadDataSourceNetworkInProgress = true
-        collectionView?.reloadData()
-
         NextcloudKit.shared.searchBodyRequest(serverUrl: appDelegate.urlBase,
                                               requestBody: requestBody,
                                               showHiddenFiles: NCKeychain().showHiddenFiles,
                                               options: NKRequestOptions(queue: NextcloudKit.shared.nkCommonInstance.backgroundQueue)) { account, files, _, error in
 
-            self.isReloadDataSourceNetworkInProgress = false
             if error == .success {
                 NCManageDatabase.shared.convertFilesToMetadatas(files, useMetadataFolder: false) { _, metadatasFolder, metadatas in
                     // Update sub directories
@@ -167,7 +150,7 @@ class NCRecent: NCCollectionViewCommon {
                     self.reloadDataSource()
                 }
             } else {
-                self.reloadDataSource()
+                self.reloadDataSource(withQueryDB: false)
             }
         }
     }

+ 7 - 10
iOSClient/RichWorkspace/NCViewerRichWorkspace.swift

@@ -61,15 +61,13 @@ import MarkdownKit
     override func viewWillAppear(_ animated: Bool) {
         super.viewWillAppear(animated)
 
-        NCNetworking.shared.readFile(serverUrlFileName: serverUrl) { account, metadata, error in
-
-            if error == .success && account == self.appDelegate.account {
-                guard let metadata = metadata else { return }
-                NCManageDatabase.shared.setDirectory(serverUrl: self.serverUrl, richWorkspace: metadata.richWorkspace, account: account)
-                if self.richWorkspaceText != metadata.richWorkspace && metadata.richWorkspace != nil {
-                    self.delegate?.richWorkspaceText = self.richWorkspaceText
-                    self.richWorkspaceText = metadata.richWorkspace!
-                    DispatchQueue.main.async {
+        DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
+            NCNetworking.shared.readFile(serverUrlFileName: self.serverUrl, queue: .main) { account, metadata, error in
+                if error == .success, account == self.appDelegate.account, let metadata {
+                    NCManageDatabase.shared.setDirectory(serverUrl: self.serverUrl, richWorkspace: metadata.richWorkspace, account: account)
+                    if self.richWorkspaceText != metadata.richWorkspace, metadata.richWorkspace != nil {
+                        self.delegate?.richWorkspaceText = self.richWorkspaceText
+                        self.richWorkspaceText = metadata.richWorkspace!
                         self.textView.attributedText = self.markdownParser.parse(metadata.richWorkspace!)
                     }
                 }
@@ -86,7 +84,6 @@ import MarkdownKit
     }
 
     @IBAction func editItemAction(_ sender: Any) {
-
         richWorkspaceCommon.openViewerNextcloudText(serverUrl: serverUrl, viewController: self)
     }
 }

+ 1 - 1
iOSClient/Scan document/NCUploadScanDocument.swift

@@ -82,7 +82,7 @@ class NCUploadScanDocument: ObservableObject {
                                                           url: "",
                                                           contentType: "")
 
-        metadata.session = NCNetworking.shared.sessionIdentifierBackground
+        metadata.session = NCNetworking.shared.sessionUploadBackground
         metadata.sessionSelector = NCGlobal.shared.selectorUploadFile
         metadata.status = NCGlobal.shared.metadataStatusWaitUpload
 

+ 2 - 1
iOSClient/Settings/CCAdvanced.m

@@ -355,10 +355,11 @@
 
 - (void)clearCache:(NSString *)account
 {
+    [[NCNetworking shared] cancelAllQueue];
     [[NCNetworking shared] cancelDataTask];
     [[NCNetworking shared] cancelDownloadTasks];
     [[NCNetworking shared] cancelUploadTasks];
-    [[NCNetworking shared] cancelUploadBackgroundTask];
+    [[NCNetworking shared] cancelUploadBackgroundTaskWithNotification:false];
 
     dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC), dispatch_get_main_queue(), ^(void) {
 

+ 7 - 19
iOSClient/Settings/NCKeychain.swift

@@ -101,7 +101,9 @@ import KeychainAccess
                 }
                 keychainOLD["notPasscodeAtStart"] = nil
             }
-            if let value = try? keychain.get("requestPasscodeAtStart"), let result = Bool(value) {
+            if NCBrandOptions.shared.doNotAskPasscodeAtStartup {
+                return false
+            } else if let value = try? keychain.get("requestPasscodeAtStart"), let result = Bool(value) {
                 return result
             }
             return true
@@ -124,19 +126,6 @@ import KeychainAccess
         }
     }
 
-    var intro: Bool {
-        get {
-            migrate(key: "intro")
-            if let value = try? keychain.get("intro"), let result = Bool(value) {
-                return result
-            }
-            return false
-        }
-        set {
-            keychain["intro"] = String(newValue)
-        }
-    }
-
     @objc var incrementalNumber: String {
         migrate(key: "incrementalnumber")
         var incrementalString = String(format: "%04ld", 0)
@@ -278,16 +267,15 @@ import KeychainAccess
         }
     }
 
-    var mediaWidthImage: Int {
+    var mediaItemForLine: Int {
         get {
-            migrate(key: "mediaWidthImage")
-            if let value = try? keychain.get("mediaWidthImage"), let result = Int(value) {
+            if let value = try? keychain.get("itemForLine"), let result = Int(value) {
                 return result
             }
-            return 80
+            return 3
         }
         set {
-            keychain["mediaWidthImage"] = String(newValue)
+            keychain["itemForLine"] = String(newValue)
         }
     }
 

+ 3 - 0
iOSClient/Settings/NCSettings.m

@@ -94,6 +94,9 @@
     row.cellConfigAtConfigure[@"backgroundColor"] = UIColor.secondarySystemGroupedBackgroundColor;
     [row.cellConfig setObject:[UIFont systemFontOfSize:15.0] forKey:@"textLabel.font"];
     [row.cellConfig setObject:UIColor.labelColor forKey:@"textLabel.textColor"];
+    if ([[NCBrandOptions shared] doNotAskPasscodeAtStartup]) {
+        row.disabled = @YES;
+    }
     [section addFormRow:row];
     // Privacy screen
     row = [XLFormRowDescriptor formRowDescriptorWithTag:@"privacyScreen" rowType:XLFormRowDescriptorTypeBooleanSwitch title:NSLocalizedString(@"_privacy_screen_", nil)];

+ 11 - 21
iOSClient/Shares/NCShares.swift

@@ -44,12 +44,16 @@ class NCShares: NCCollectionViewCommon {
     override func viewWillAppear(_ animated: Bool) {
         super.viewWillAppear(animated)
 
-        navigationController?.setFileAppreance()
+        if dataSource.metadatas.isEmpty {
+            reloadDataSource()
+        }
+        reloadDataSourceNetwork()
     }
 
     // MARK: - DataSource + NC Endpoint
 
-    override func queryDB(isForced: Bool) {
+    override func queryDB() {
+        super.queryDB()
 
         var metadatas: [tableMetadata] = []
 
@@ -92,35 +96,21 @@ class NCShares: NCCollectionViewCommon {
         reload()
     }
 
-    override func reloadDataSource(isForced: Bool = true) {
-        super.reloadDataSource()
-
-        DispatchQueue.global().async {
-            self.queryDB(isForced: isForced)
-        }
-    }
-
-    override func reloadDataSourceNetwork(isForced: Bool = false) {
-        super.reloadDataSourceNetwork(isForced: isForced)
-
-        NextcloudKit.shared.nkCommonInstance.writeLog("[INFO] Reload data source network shares forced \(isForced)")
-
-        isReloadDataSourceNetworkInProgress = true
-        collectionView?.reloadData()
+    override func reloadDataSourceNetwork() {
+        super.reloadDataSourceNetwork()
 
         NextcloudKit.shared.readShares(parameters: NKShareParameter()) { account, shares, _, error in
 
-            self.refreshControl.endRefreshing()
-            self.isReloadDataSourceNetworkInProgress = false
-
             if error == .success {
                 NCManageDatabase.shared.deleteTableShare(account: account)
                 if let shares = shares, !shares.isEmpty {
                     let home = self.utilityFileSystem.getHomeServer(urlBase: self.appDelegate.urlBase, userId: self.appDelegate.userId)
                     NCManageDatabase.shared.addShare(account: self.appDelegate.account, home: home, shares: shares)
                 }
+                self.reloadDataSource()
+            } else {
+                self.reloadDataSource(withQueryDB: false)
             }
-            self.reloadDataSource()
         }
     }
 }

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


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

@@ -198,7 +198,7 @@
 "_source_code_"             = "Get source code";
 "_account_select_"          = "Select the account";
 "_account_select_to_add_"   = "Select the account to add";
-"_host_insert_"             = "Insert the host name, for example:";
+"_host_insert_"             = "Insert the hostname, for example:";
 "_certificate_not_found_"   = "File %@ in documents directory not found.";
 "_copy_failed_"             = "Copy failed";
 "_certificate_installed_"   = "Certificate installed";
@@ -593,6 +593,7 @@
 "_media_viewimage_show_"        = "Show only images";
 "_media_viewvideo_show_"        = "Show only video";
 "_media_show_all_"              = "Show both";
+"_media_view_options_"          = "View options";
 "_media_by_created_date_"       = "Sort by created date";
 "_media_by_upload_date_"        = "Sort by upload date";
 "_media_by_modified_date_"      = "Sort by modified date";
@@ -949,6 +950,10 @@
 "_reset_wrong_passcode_"    = "Reset application";
 "_reset_wrong_passcode_desc_" = "Use \"Reset application\" to remove all accounts and local data after %d failed code entry attempts.";
 "_deviceOwnerAuthentication_" = "The biometric sensor has been temporarily disabled due to multiple failed attempts. Enter the device passcode to re-enable the sensor.";
+"_virus_detect_"            = "Virus detected. Upload cannot be completed!";
+"_zoom_"                    = "Zoom";
+"_zoom_in_"                 = "Zoom in";
+"_zoom_out_"                = "Zoom out";
 
 // Video
 "_select_trace_"            = "Select the trace";

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


Энэ ялгаанд хэт олон файл өөрчлөгдсөн тул зарим файлыг харуулаагүй болно