浏览代码

Version 4.9.2 (#2673)

Version 4.9.2
Marino Faggiana 1 年之前
父节点
当前提交
2a9b9d80ee
共有 100 个文件被更改,包括 3149 次插入1793 次删除
  1. 1 1
      Brand/Database.swift
  2. 7 85
      Brand/NCBrand.swift
  3. 11 12
      File Provider Extension/FileProviderData.swift
  4. 10 9
      File Provider Extension/FileProviderEnumerator.swift
  5. 23 23
      File Provider Extension/FileProviderExtension+Actions.swift
  6. 3 3
      File Provider Extension/FileProviderExtension+Thumbnail.swift
  7. 25 23
      File Provider Extension/FileProviderExtension.swift
  8. 2 2
      File Provider Extension/FileProviderItem.swift
  9. 5 8
      File Provider Extension/FileProviderUtility.swift
  10. 251 56
      Nextcloud.xcodeproj/project.pbxproj
  11. 1 1
      Notification Service Extension/NotificationService.swift
  12. 6 4
      Share/NCShareCell.swift
  13. 16 19
      Share/NCShareExtension+DataSource.swift
  14. 3 2
      Share/NCShareExtension+Files.swift
  15. 5 4
      Share/NCShareExtension+NCDelegate.swift
  16. 19 20
      Share/NCShareExtension.swift
  17. 1 0
      Share/Share-Bridging-Header.h
  18. 13 0
      Tests/NextcloudIntegrationTests/FilesIntegrationTests.swift
  19. 15 0
      Tests/NextcloudSnapshotTests/Extensions/SwiftUIView+Extensions.swift
  20. 13 0
      Tests/NextcloudSnapshotTests/NextcloudSnapshotTests.swift
  21. 13 0
      Tests/NextcloudUITests/BaseUIXCTestCase.swift
  22. 13 0
      Tests/NextcloudUITests/LoginUITests.swift
  23. 15 1
      Tests/NextcloudUnitTests/NextcloudUnitTests.swift
  24. 11 11
      Widget/Dashboard/DashboardData.swift
  25. 16 15
      Widget/Files/FilesData.swift
  26. 4 3
      Widget/Lockscreen/LockscreenData.swift
  27. 1 0
      Widget/Widget-Brinding-header.h
  28. 15 0
      WidgetDashboardIntentHandler/IntentHandler.swift
  29. 4 4
      iOSClient/Account Request/NCAccountRequest.swift
  30. 14 15
      iOSClient/Activity/NCActivity.swift
  31. 1 1
      iOSClient/Activity/NCActivityCommentView.swift
  32. 60 9
      iOSClient/Activity/NCActivityTableViewCell.swift
  33. 227 95
      iOSClient/AppDelegate.swift
  34. 1 1
      iOSClient/BrowserWeb/NCBrowserWeb.swift
  35. 16 1
      iOSClient/Color/NCColorPicker.swift
  36. 9 3
      iOSClient/Data/NCDataSource.swift
  37. 0 157
      iOSClient/Data/NCDatabase.swift
  38. 5 6
      iOSClient/Data/NCManageDatabase+Account.swift
  39. 1 47
      iOSClient/Data/NCManageDatabase+Activity.swift
  40. 4 4
      iOSClient/Data/NCManageDatabase+Avatar.swift
  41. 7 0
      iOSClient/Data/NCManageDatabase+Capabilities.swift
  42. 2 2
      iOSClient/Data/NCManageDatabase+Chunk.swift
  43. 93 0
      iOSClient/Data/NCManageDatabase+Comments.swift
  44. 158 0
      iOSClient/Data/NCManageDatabase+DirectEditing.swift
  45. 93 0
      iOSClient/Data/NCManageDatabase+ExternalSites.swift
  46. 67 0
      iOSClient/Data/NCManageDatabase+GPS.swift
  47. 38 0
      iOSClient/Data/NCManageDatabase+LocalFile.swift
  48. 77 62
      iOSClient/Data/NCManageDatabase+Metadata.swift
  49. 100 0
      iOSClient/Data/NCManageDatabase+PhotoLibrary.swift
  50. 1 1
      iOSClient/Data/NCManageDatabase+Share.swift
  51. 97 0
      iOSClient/Data/NCManageDatabase+Tag.swift
  52. 64 0
      iOSClient/Data/NCManageDatabase+Tip.swift
  53. 151 0
      iOSClient/Data/NCManageDatabase+Trash.swift
  54. 70 0
      iOSClient/Data/NCManageDatabase+UserStatus.swift
  55. 52 548
      iOSClient/Data/NCManageDatabase.swift
  56. 18 2
      iOSClient/Diagnostics/NCCapabilitiesView.swift
  57. 15 0
      iOSClient/Extensions/Optional+Extension.swift
  58. 16 1
      iOSClient/Extensions/PHAsset+Extension.swift
  59. 5 5
      iOSClient/Extensions/UIAlertController+Extension.swift
  60. 31 0
      iOSClient/Extensions/UIImage+Extension.swift
  61. 1 1
      iOSClient/Favorites/NCFavorite.swift
  62. 5 3
      iOSClient/Files/NCFiles.swift
  63. 3 3
      iOSClient/Groupfolders/NCGroupfolders.swift
  64. 12 0
      iOSClient/Images.xcassets/contact.imageset/Contents.json
  65. 7 0
      iOSClient/Images.xcassets/contact.imageset/contact.svg
  66. 4 11
      iOSClient/Login/NCLogin.swift
  67. 8 13
      iOSClient/Login/NCLoginWeb.swift
  68. 3 2
      iOSClient/Login/NCViewCertificateDetails.swift
  69. 202 90
      iOSClient/Main/Collection Common/NCCollectionViewCommon.swift
  70. 2 2
      iOSClient/Main/Collection Common/NCGridCell.swift
  71. 3 3
      iOSClient/Main/Collection Common/NCListCell.swift
  72. 21 21
      iOSClient/Main/Create cloud/NCCreateFormUploadConflict.swift
  73. 18 19
      iOSClient/Main/Create cloud/NCCreateFormUploadDocuments.swift
  74. 9 10
      iOSClient/Main/Create cloud/NCCreateFormUploadVoiceNote.swift
  75. 15 12
      iOSClient/Main/Create cloud/NCUploadAssets.swift
  76. 51 51
      iOSClient/Main/NCActionCenter.swift
  77. 2 6
      iOSClient/Main/NCMainTabBar.swift
  78. 9 14
      iOSClient/Main/NCPickerViewController.swift
  79. 5 4
      iOSClient/Main/Section Header Footer/NCSectionHeaderMenu.swift
  80. 1 1
      iOSClient/Media/Cell/NCGridMediaCell.swift
  81. 140 100
      iOSClient/Media/NCMedia.swift
  82. 10 14
      iOSClient/Menu/AppDelegate+Menu.swift
  83. 22 22
      iOSClient/Menu/NCCollectionViewCommon+Menu.swift
  84. 13 8
      iOSClient/Menu/NCContextMenu.swift
  85. 3 3
      iOSClient/Menu/NCLoginWeb+Menu.swift
  86. 44 26
      iOSClient/Menu/NCMedia+Menu.swift
  87. 20 16
      iOSClient/Menu/NCMenuAction.swift
  88. 124 0
      iOSClient/Menu/NCOperationSaveLivePhoto.swift
  89. 9 9
      iOSClient/Menu/NCShare+Menu.swift
  90. 8 8
      iOSClient/Menu/NCTrash+Menu.swift
  91. 15 15
      iOSClient/Menu/NCViewer+Menu.swift
  92. 6 6
      iOSClient/Menu/UIViewController+Menu.swift
  93. 15 0
      iOSClient/More/Cells/BaseNCMoreCell.swift
  94. 15 0
      iOSClient/More/Cells/CCCellMore.swift
  95. 17 7
      iOSClient/More/Cells/NCMoreAppSuggestionsCell.swift
  96. 15 0
      iOSClient/More/Cells/NCMoreUserCell.swift
  97. 8 22
      iOSClient/More/NCMore.swift
  98. 12 4
      iOSClient/NCGlobal.swift
  99. 256 0
      iOSClient/NCImageCache.swift
  100. 1 1
      iOSClient/Networking/E2EE/NCEndToEndEncryption.m

+ 1 - 1
Brand/Database.swift

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

+ 7 - 85
Brand/NCBrand.swift

@@ -84,6 +84,13 @@ let userAgent: String = {
     // 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
+
+    // Number of failed attempts after reset app
+    @objc public let resetAppPasscodeAttempts: Int = 10
+    public let passcodeSecondsFail: Int = 60
+
     // Info Paging
     enum NCInfoPagingTab: Int, CaseIterable {
         case activity, sharing
@@ -136,45 +143,6 @@ class NCBrandColor: NSObject {
         return instance
     }()
 
-    struct cacheImages {
-        static var file = UIImage()
-
-        static var shared = UIImage()
-        static var canShare = UIImage()
-        static var shareByLink = UIImage()
-
-        static var favorite = UIImage()
-        static var comment = UIImage()
-        static var livePhoto = UIImage()
-        static var offlineFlag = UIImage()
-        static var local = UIImage()
-
-        static var folderEncrypted = UIImage()
-        static var folderSharedWithMe = UIImage()
-        static var folderPublic = UIImage()
-        static var folderGroup = UIImage()
-        static var folderExternal = UIImage()
-        static var folderAutomaticUpload = UIImage()
-        static var folder = UIImage()
-
-        static var checkedYes = UIImage()
-        static var checkedNo = UIImage()
-
-        static var buttonMore = UIImage()
-        static var buttonStop = UIImage()
-        static var buttonMoreLock = UIImage()
-        static var buttonRestore = UIImage()
-        static var buttonTrash = UIImage()
-
-        static var iconContacts = UIImage()
-        static var iconTalk = UIImage()
-        static var iconCalendar = UIImage()
-        static var iconDeck = UIImage()
-        static var iconMail = UIImage()
-        static var iconConfirm = UIImage()
-        static var iconPages = UIImage()
-    }
-
     // Color
     @objc public let customer: UIColor = UIColor(red: 0.0 / 255.0, green: 130.0 / 255.0, blue: 201.0 / 255.0, alpha: 1.0)         // BLU NC : #0082c9
     @objc public var customerText: UIColor = .white
@@ -207,47 +175,6 @@ class NCBrandColor: NSObject {
         userColors = generateColors()
     }
 
-    func createImagesThemingColor() {
-
-        cacheImages.file = UIImage(named: "file")!
-
-        cacheImages.shared = UIImage(named: "share")!.image(color: .systemGray, size: 50)
-        cacheImages.canShare = UIImage(named: "share")!.image(color: .systemGray, size: 50)
-        cacheImages.shareByLink = UIImage(named: "sharebylink")!.image(color: .systemGray, size: 50)
-
-        cacheImages.favorite = NCUtility.shared.loadImage(named: "star.fill", color: yellowFavorite)
-        cacheImages.comment = UIImage(named: "comment")!.image(color: .systemGray, size: 50)
-        cacheImages.livePhoto = NCUtility.shared.loadImage(named: "livephoto", color: .label)
-        cacheImages.offlineFlag = UIImage(named: "offlineFlag")!
-        cacheImages.local = UIImage(named: "local")!
-
-        let folderWidth: CGFloat = UIScreen.main.bounds.width / 3
-        cacheImages.folderEncrypted = UIImage(named: "folderEncrypted")!.image(color: brandElement, size: folderWidth)
-        cacheImages.folderSharedWithMe = UIImage(named: "folder_shared_with_me")!.image(color: brandElement, size: folderWidth)
-        cacheImages.folderPublic = UIImage(named: "folder_public")!.image(color: brandElement, size: folderWidth)
-        cacheImages.folderGroup = UIImage(named: "folder_group")!.image(color: brandElement, size: folderWidth)
-        cacheImages.folderExternal = UIImage(named: "folder_external")!.image(color: brandElement, size: folderWidth)
-        cacheImages.folderAutomaticUpload = UIImage(named: "folderAutomaticUpload")!.image(color: brandElement, size: folderWidth)
-        cacheImages.folder = UIImage(named: "folder")!.image(color: brandElement, size: folderWidth)
-
-        cacheImages.checkedYes = NCUtility.shared.loadImage(named: "checkmark.circle.fill", color: .systemBlue)
-        cacheImages.checkedNo = NCUtility.shared.loadImage(named: "circle", color: .systemGray)
-
-        cacheImages.buttonMore = UIImage(named: "more")!.image(color: .systemGray, size: 50)
-        cacheImages.buttonStop = UIImage(named: "stop")!.image(color: .systemGray, size: 50)
-        cacheImages.buttonMoreLock = UIImage(named: "moreLock")!.image(color: .systemGray, size: 50)
-        cacheImages.buttonRestore = UIImage(named: "restore")!.image(color: .systemGray, size: 50)
-        cacheImages.buttonTrash = UIImage(named: "trash")!.image(color: .systemGray, size: 50)
-
-        cacheImages.iconContacts = UIImage(named: "icon-contacts")!.image(color: brandElement, size: folderWidth)
-        cacheImages.iconTalk = UIImage(named: "icon-talk")!.image(color: brandElement, size: folderWidth)
-        cacheImages.iconCalendar = UIImage(named: "icon-calendar")!.image(color: brandElement, size: folderWidth)
-        cacheImages.iconDeck = UIImage(named: "icon-deck")!.image(color: brandElement, size: folderWidth)
-        cacheImages.iconMail = UIImage(named: "icon-mail")!.image(color: brandElement, size: folderWidth)
-        cacheImages.iconConfirm = UIImage(named: "icon-confirm")!.image(color: brandElement, size: folderWidth)
-        cacheImages.iconPages = UIImage(named: "icon-pages")!.image(color: brandElement, size: folderWidth)
-    }
-
     func settingThemingColor(account: String) {
 
         let darker: CGFloat = 30    // %
@@ -319,11 +246,6 @@ class NCBrandColor: NSObject {
             brand = customer
             brandText = customerText
         }
-
-        createImagesThemingColor()
-#if !EXTENSION
-        NotificationCenter.default.postOnMainThread(name: NCGlobal.shared.notificationCenterChangeTheming)
-#endif
     }
 
     private func stepCalc(steps: Int, color1: CGColor, color2: CGColor) -> [CGFloat] {

+ 11 - 12
File Provider Extension/FileProviderData.swift

@@ -32,6 +32,7 @@ class fileProviderData: NSObject {
 
     var domain: NSFileProviderDomain?
     var fileProviderManager: NSFileProviderManager = NSFileProviderManager.default
+    let utilityFileSystem = NCUtilityFileSystem()
 
     var account = ""
     var user = ""
@@ -72,13 +73,11 @@ class fileProviderData: NSObject {
         }
 
         // LOG
-        if let pathDirectoryGroup = CCUtility.getDirectoryGroup()?.path {
-            NextcloudKit.shared.nkCommonInstance.pathLog = pathDirectoryGroup
-            let levelLog = CCUtility.getLogLevel()
-            NextcloudKit.shared.nkCommonInstance.levelLog = levelLog
-            let version = NSString(format: NCBrandOptions.shared.textCopyrightNextcloudiOS as NSString, NCUtility.shared.getVersionApp()) as String
-            NextcloudKit.shared.nkCommonInstance.writeLog("[INFO] Start File Provider session with level \(levelLog) " + version + " (File Provider Extension)")
-        }
+        NextcloudKit.shared.nkCommonInstance.pathLog = utilityFileSystem.directoryGroup
+        let levelLog = NCKeychain().logLevel
+        NextcloudKit.shared.nkCommonInstance.levelLog = levelLog
+        let version = NSString(format: NCBrandOptions.shared.textCopyrightNextcloudiOS as NSString, NCUtility().getVersionApp()) as String
+        NextcloudKit.shared.nkCommonInstance.writeLog("[INFO] Start File Provider session with level \(levelLog) " + version + " (File Provider Extension)")
 
         // NO DOMAIN -> Set default account
         if domain == nil {
@@ -89,11 +88,11 @@ class fileProviderData: NSObject {
             user = activeAccount.user
             userId = activeAccount.userId
             accountUrlBase = activeAccount.urlBase
-            homeServerUrl = NCUtilityFileSystem.shared.getHomeServer(urlBase: activeAccount.urlBase, userId: activeAccount.userId)
+            homeServerUrl = utilityFileSystem.getHomeServer(urlBase: activeAccount.urlBase, userId: activeAccount.userId)
 
             NCManageDatabase.shared.setCapabilities(account: account)
 
-            NextcloudKit.shared.setup(account: activeAccount.account, user: activeAccount.user, userId: activeAccount.userId, password: CCUtility.getPassword(activeAccount.account), urlBase: activeAccount.urlBase, userAgent: userAgent, nextcloudVersion: NCGlobal.shared.capabilityServerVersionMajor, delegate: NCNetworking.shared)
+            NextcloudKit.shared.setup(account: activeAccount.account, user: activeAccount.user, userId: activeAccount.userId, password: NCKeychain().getPassword(account: activeAccount.account), urlBase: activeAccount.urlBase, userAgent: userAgent, nextcloudVersion: NCGlobal.shared.capabilityServerVersionMajor, delegate: NCNetworking.shared)
             NCNetworking.shared.delegate = providerExtension as? NCNetworkingDelegate
 
             return tableAccount.init(value: activeAccount)
@@ -113,11 +112,11 @@ class fileProviderData: NSObject {
                 user = activeAccount.user
                 userId = activeAccount.userId
                 accountUrlBase = activeAccount.urlBase
-                homeServerUrl = NCUtilityFileSystem.shared.getHomeServer(urlBase: activeAccount.urlBase, userId: activeAccount.userId)
+                homeServerUrl = utilityFileSystem.getHomeServer(urlBase: activeAccount.urlBase, userId: activeAccount.userId)
 
                 NCManageDatabase.shared.setCapabilities(account: account)
 
-                NextcloudKit.shared.setup(account: activeAccount.account, user: activeAccount.user, userId: activeAccount.userId, password: CCUtility.getPassword(activeAccount.account), urlBase: activeAccount.urlBase, userAgent: userAgent, nextcloudVersion: NCGlobal.shared.capabilityServerVersionMajor, delegate: NCNetworking.shared)
+                NextcloudKit.shared.setup(account: activeAccount.account, user: activeAccount.user, userId: activeAccount.userId, password: NCKeychain().getPassword(account: activeAccount.account), urlBase: activeAccount.urlBase, userAgent: userAgent, nextcloudVersion: NCGlobal.shared.capabilityServerVersionMajor, delegate: NCNetworking.shared)
                 NCNetworking.shared.delegate = providerExtension as? NCNetworkingDelegate
 
                 return tableAccount.init(value: activeAccount)
@@ -134,7 +133,7 @@ class fileProviderData: NSObject {
 
         guard let metadata = NCManageDatabase.shared.getMetadataFromOcId(ocId) else { return nil }
 
-        guard let parentItemIdentifier = fileProviderUtility.shared.getParentItemIdentifier(metadata: metadata) else { return nil }
+        guard let parentItemIdentifier = fileProviderUtility().getParentItemIdentifier(metadata: metadata) else { return nil }
 
         let item = FileProviderItem(metadata: metadata, parentItemIdentifier: parentItemIdentifier)
 

+ 10 - 9
File Provider Extension/FileProviderEnumerator.swift

@@ -29,6 +29,7 @@ class FileProviderEnumerator: NSObject, NSFileProviderEnumerator {
 
     var enumeratedItemIdentifier: NSFileProviderItemIdentifier
     var serverUrl: String?
+    let fpUtility = fileProviderUtility()
 
     init(enumeratedItemIdentifier: NSFileProviderItemIdentifier) {
 
@@ -39,7 +40,7 @@ class FileProviderEnumerator: NSObject, NSFileProviderEnumerator {
             serverUrl = fileProviderData.shared.homeServerUrl
         } else {
 
-            let metadata = fileProviderUtility.shared.getTableMetadataFromItemIdentifier(enumeratedItemIdentifier)
+            let metadata = fpUtility.getTableMetadataFromItemIdentifier(enumeratedItemIdentifier)
             if metadata != nil {
                 if let directorySource = NCManageDatabase.shared.getTableDirectory(predicate: NSPredicate(format: "account == %@ AND serverUrl == %@", metadata!.account, metadata!.serverUrl)) {
                     serverUrl = directorySource.serverUrl + "/" + metadata!.fileName
@@ -68,8 +69,8 @@ class FileProviderEnumerator: NSObject, NSFileProviderEnumerator {
             for tag in tags {
 
                 guard let metadata = NCManageDatabase.shared.getMetadataFromOcId(tag.ocId)  else { continue }
-                fileProviderUtility.shared.createocIdentifierOnFileSystem(metadata: metadata)
-                itemIdentifierMetadata[fileProviderUtility.shared.getItemIdentifier(metadata: metadata)] = metadata
+                fpUtility.createocIdentifierOnFileSystem(metadata: metadata)
+                itemIdentifierMetadata[fpUtility.getItemIdentifier(metadata: metadata)] = metadata
             }
 
             // ***** Favorite *****
@@ -77,12 +78,12 @@ class FileProviderEnumerator: NSObject, NSFileProviderEnumerator {
             for (identifier, _) in fileProviderData.shared.listFavoriteIdentifierRank {
 
                 guard let metadata = NCManageDatabase.shared.getMetadataFromOcId(identifier) else { continue }
-                itemIdentifierMetadata[fileProviderUtility.shared.getItemIdentifier(metadata: metadata)] = metadata
+                itemIdentifierMetadata[fpUtility.getItemIdentifier(metadata: metadata)] = metadata
             }
 
             // create items
             for (_, metadata) in itemIdentifierMetadata {
-                let parentItemIdentifier = fileProviderUtility.shared.getParentItemIdentifier(metadata: metadata)
+                let parentItemIdentifier = fpUtility.getParentItemIdentifier(metadata: metadata)
                 if parentItemIdentifier != nil {
                     let item = FileProviderItem(metadata: metadata, parentItemIdentifier: parentItemIdentifier!)
                     items.append(item)
@@ -175,9 +176,9 @@ class FileProviderEnumerator: NSObject, NSFileProviderEnumerator {
 
                 if metadata.e2eEncrypted || (!metadata.session.isEmpty && metadata.session != NCNetworking.shared.sessionIdentifierBackgroundExtension) { continue }
 
-                fileProviderUtility.shared.createocIdentifierOnFileSystem(metadata: metadata)
+                fpUtility.createocIdentifierOnFileSystem(metadata: metadata)
 
-                let parentItemIdentifier = fileProviderUtility.shared.getParentItemIdentifier(metadata: metadata)
+                let parentItemIdentifier = fpUtility.getParentItemIdentifier(metadata: metadata)
                 if parentItemIdentifier != nil {
                     let item = FileProviderItem(metadata: metadata, parentItemIdentifier: parentItemIdentifier!)
                     items.append(item)
@@ -203,11 +204,11 @@ class FileProviderEnumerator: NSObject, NSFileProviderEnumerator {
             directoryEtag = tableDirectory.etag
         }
 
-        NextcloudKit.shared.readFileOrFolder(serverUrlFileName: serverUrl, depth: "0", showHiddenFiles: CCUtility.getShowHiddenFiles()) { account, files, _, error in
+        NextcloudKit.shared.readFileOrFolder(serverUrlFileName: serverUrl, depth: "0", showHiddenFiles: NCKeychain().showHiddenFiles) { account, files, _, error in
 
             if directoryEtag != files.first?.etag {
 
-                NextcloudKit.shared.readFileOrFolder(serverUrlFileName: serverUrl, depth: "1", showHiddenFiles: CCUtility.getShowHiddenFiles()) { account, files, _, error in
+                NextcloudKit.shared.readFileOrFolder(serverUrlFileName: serverUrl, depth: "1", showHiddenFiles: NCKeychain().showHiddenFiles) { account, files, _, error in
 
                     if error == .success {
                         DispatchQueue.global().async {

+ 23 - 23
File Provider Extension/FileProviderExtension+Actions.swift

@@ -29,23 +29,23 @@ extension FileProviderExtension {
 
     override func createDirectory(withName directoryName: String, inParentItemIdentifier parentItemIdentifier: NSFileProviderItemIdentifier, completionHandler: @escaping (NSFileProviderItem?, Error?) -> Void) {
 
-        guard let tableDirectory = fileProviderUtility.shared.getTableDirectoryFromParentItemIdentifier(parentItemIdentifier, account: fileProviderData.shared.account, homeServerUrl: fileProviderData.shared.homeServerUrl) else {
+        guard let tableDirectory = fpUtility.getTableDirectoryFromParentItemIdentifier(parentItemIdentifier, account: fileProviderData.shared.account, homeServerUrl: fileProviderData.shared.homeServerUrl) else {
             completionHandler(nil, NSFileProviderError(.noSuchItem))
             return
         }
 
-        let directoryName = NCUtilityFileSystem.shared.createFileName(directoryName, serverUrl: tableDirectory.serverUrl, account: fileProviderData.shared.account)
+        let directoryName = utilityFileSystem.createFileName(directoryName, serverUrl: tableDirectory.serverUrl, account: fileProviderData.shared.account)
         let serverUrlFileName = tableDirectory.serverUrl + "/" + directoryName
 
         NextcloudKit.shared.createFolder(serverUrlFileName: serverUrlFileName) { _, ocId, _, error in
 
             if error == .success {
 
-                NextcloudKit.shared.readFileOrFolder(serverUrlFileName: serverUrlFileName, depth: "0", showHiddenFiles: CCUtility.getShowHiddenFiles()) { _, files, _, error in
+                NextcloudKit.shared.readFileOrFolder(serverUrlFileName: serverUrlFileName, depth: "0", showHiddenFiles: NCKeychain().showHiddenFiles) { _, files, _, error in
 
                     if error == .success, let file = files.first {
 
-                        let isDirectoryEncrypted = NCUtility.shared.isDirectoryE2EE(file: file)
+                        let isDirectoryEncrypted = self.utilityFileSystem.isDirectoryE2EE(file: file)
                         let metadata = NCManageDatabase.shared.convertFileToMetadata(file, isDirectoryE2EE: isDirectoryEncrypted)
 
                         NCManageDatabase.shared.addDirectory(encrypted: false, favorite: false, ocId: ocId!, fileId: metadata.fileId, etag: metadata.etag, permissions: metadata.permissions, serverUrl: serverUrlFileName, account: metadata.account)
@@ -56,7 +56,7 @@ extension FileProviderExtension {
                             return
                         }
 
-                        guard let parentItemIdentifier = fileProviderUtility.shared.getParentItemIdentifier(metadata: metadataInsert) else {
+                        guard let parentItemIdentifier = self.fpUtility.getParentItemIdentifier(metadata: metadataInsert) else {
                             completionHandler(nil, NSFileProviderError(.noSuchItem))
                             return
                         }
@@ -77,7 +77,7 @@ extension FileProviderExtension {
 
     override func deleteItem(withIdentifier itemIdentifier: NSFileProviderItemIdentifier, completionHandler: @escaping (Error?) -> Void) {
 
-        guard let metadata = fileProviderUtility.shared.getTableMetadataFromItemIdentifier(itemIdentifier) else {
+        guard let metadata = fpUtility.getTableMetadataFromItemIdentifier(itemIdentifier) else {
             completionHandler(NSFileProviderError(.noSuchItem))
             return
         }
@@ -92,16 +92,16 @@ extension FileProviderExtension {
 
             if error == .success { // || error == kOCErrorServerPathNotFound {
 
-                let fileNamePath = CCUtility.getDirectoryProviderStorageOcId(itemIdentifier.rawValue)!
+                let fileNamePath = self.utilityFileSystem.getDirectoryProviderStorageOcId(itemIdentifier.rawValue)
                 do {
-                    try fileProviderUtility.shared.fileManager.removeItem(atPath: fileNamePath)
+                    try self.fpUtility.fileManager.removeItem(atPath: fileNamePath)
                 } catch let error {
                     print("error: \(error)")
                 }
 
                 if isDirectory {
-                    let dirForDelete = CCUtility.stringAppendServerUrl(serverUrl, addFileName: fileName)
-                    NCManageDatabase.shared.deleteDirectoryAndSubDirectory(serverUrl: dirForDelete!, account: account)
+                    let dirForDelete = self.utilityFileSystem.stringAppendServerUrl(serverUrl, addFileName: fileName)
+                    NCManageDatabase.shared.deleteDirectoryAndSubDirectory(serverUrl: dirForDelete, account: account)
                 }
 
                 NCManageDatabase.shared.deleteMetadata(predicate: NSPredicate(format: "ocId == %@", ocId))
@@ -122,7 +122,7 @@ extension FileProviderExtension {
             return
         }
 
-        guard let metadataFrom = fileProviderUtility.shared.getTableMetadataFromItemIdentifier(itemIdentifier) else {
+        guard let metadataFrom = fpUtility.getTableMetadataFromItemIdentifier(itemIdentifier) else {
             completionHandler(nil, NSFileProviderError(.noSuchItem))
             return
         }
@@ -131,7 +131,7 @@ extension FileProviderExtension {
         let serverUrlFrom = metadataFrom.serverUrl
         let fileNameFrom = serverUrlFrom + "/" + itemFrom.filename
 
-        guard let tableDirectoryTo = fileProviderUtility.shared.getTableDirectoryFromParentItemIdentifier(parentItemIdentifier, account: fileProviderData.shared.account, homeServerUrl: fileProviderData.shared.homeServerUrl) else {
+        guard let tableDirectoryTo = fpUtility.getTableDirectoryFromParentItemIdentifier(parentItemIdentifier, account: fileProviderData.shared.account, homeServerUrl: fileProviderData.shared.homeServerUrl) else {
             completionHandler(nil, NSFileProviderError(.noSuchItem))
             return
         }
@@ -165,7 +165,7 @@ extension FileProviderExtension {
 
     override func renameItem(withIdentifier itemIdentifier: NSFileProviderItemIdentifier, toName itemName: String, completionHandler: @escaping (NSFileProviderItem?, Error?) -> Void) {
 
-        guard let metadata = fileProviderUtility.shared.getTableMetadataFromItemIdentifier(itemIdentifier) else {
+        guard let metadata = fpUtility.getTableMetadataFromItemIdentifier(itemIdentifier) else {
             completionHandler(nil, NSFileProviderError(.noSuchItem))
             return
         }
@@ -198,19 +198,19 @@ extension FileProviderExtension {
 
                 } else {
 
-                    let itemIdentifier = fileProviderUtility.shared.getItemIdentifier(metadata: metadata)
+                    let itemIdentifier = self.fpUtility.getItemIdentifier(metadata: metadata)
 
                     // rename file
-                    _ = fileProviderUtility.shared.moveFile(CCUtility.getDirectoryProviderStorageOcId(itemIdentifier.rawValue, fileNameView: fileNameFrom), toPath: CCUtility.getDirectoryProviderStorageOcId(itemIdentifier.rawValue, fileNameView: itemName))
+                    _ = self.fpUtility.moveFile(self.utilityFileSystem.getDirectoryProviderStorageOcId(itemIdentifier.rawValue, fileNameView: fileNameFrom), toPath: self.utilityFileSystem.getDirectoryProviderStorageOcId(itemIdentifier.rawValue, fileNameView: itemName))
 
-                    _ = fileProviderUtility.shared.moveFile(CCUtility.getDirectoryProviderStoragePreviewOcId(itemIdentifier.rawValue, etag: metadata.etag), toPath: CCUtility.getDirectoryProviderStoragePreviewOcId(itemIdentifier.rawValue, etag: metadata.etag))
+                    _ = self.fpUtility.moveFile(self.utilityFileSystem.getDirectoryProviderStoragePreviewOcId(itemIdentifier.rawValue, etag: metadata.etag), toPath: self.utilityFileSystem.getDirectoryProviderStoragePreviewOcId(itemIdentifier.rawValue, etag: metadata.etag))
 
-                    _ = fileProviderUtility.shared.moveFile(CCUtility.getDirectoryProviderStorageIconOcId(itemIdentifier.rawValue, etag: metadata.etag), toPath: CCUtility.getDirectoryProviderStorageIconOcId(itemIdentifier.rawValue, etag: metadata.etag))
+                    _ = self.fpUtility.moveFile(self.utilityFileSystem.getDirectoryProviderStorageIconOcId(itemIdentifier.rawValue, etag: metadata.etag), toPath: self.utilityFileSystem.getDirectoryProviderStorageIconOcId(itemIdentifier.rawValue, etag: metadata.etag))
 
                     NCManageDatabase.shared.setLocalFile(ocId: ocId, fileName: itemName, etag: nil)
                 }
 
-                guard let parentItemIdentifier = fileProviderUtility.shared.getParentItemIdentifier(metadata: metadata) else {
+                guard let parentItemIdentifier = self.fpUtility.getParentItemIdentifier(metadata: metadata) else {
                     completionHandler(nil, NSFileProviderError(.noSuchItem))
                     return
                 }
@@ -226,7 +226,7 @@ extension FileProviderExtension {
 
     override func setFavoriteRank(_ favoriteRank: NSNumber?, forItemIdentifier itemIdentifier: NSFileProviderItemIdentifier, completionHandler: @escaping (NSFileProviderItem?, Error?) -> Void) {
 
-        guard let metadata = fileProviderUtility.shared.getTableMetadataFromItemIdentifier(itemIdentifier) else {
+        guard let metadata = fpUtility.getTableMetadataFromItemIdentifier(itemIdentifier) else {
             completionHandler(nil, NSFileProviderError(.noSuchItem))
             return
         }
@@ -245,7 +245,7 @@ extension FileProviderExtension {
         }
 
         if (favorite == true && metadata.favorite == false) || (favorite == false && metadata.favorite == true) {
-            let fileNamePath = CCUtility.returnFileNamePath(fromFileName: metadata.fileName, serverUrl: metadata.serverUrl, urlBase: metadata.urlBase, userId: metadata.userId, account: metadata.account)!
+            let fileNamePath = utilityFileSystem.getFileNamePath(metadata.fileName, serverUrl: metadata.serverUrl, urlBase: metadata.urlBase, userId: metadata.userId)
 
             NextcloudKit.shared.setFavorite(fileName: fileNamePath, favorite: favorite) { _, error in
 
@@ -283,7 +283,7 @@ extension FileProviderExtension {
 
     override func setTagData(_ tagData: Data?, forItemIdentifier itemIdentifier: NSFileProviderItemIdentifier, completionHandler: @escaping (NSFileProviderItem?, Error?) -> Void) {
 
-        guard let metadataForTag = fileProviderUtility.shared.getTableMetadataFromItemIdentifier(itemIdentifier) else {
+        guard let metadataForTag = fpUtility.getTableMetadataFromItemIdentifier(itemIdentifier) else {
             completionHandler(nil, NSFileProviderError(.noSuchItem))
             return
         }
@@ -299,12 +299,12 @@ extension FileProviderExtension {
 
     override func setLastUsedDate(_ lastUsedDate: Date?, forItemIdentifier itemIdentifier: NSFileProviderItemIdentifier, completionHandler: @escaping (NSFileProviderItem?, Error?) -> Void) {
 
-        guard let metadata = fileProviderUtility.shared.getTableMetadataFromItemIdentifier(itemIdentifier) else {
+        guard let metadata = fpUtility.getTableMetadataFromItemIdentifier(itemIdentifier) else {
             completionHandler(nil, NSFileProviderError(.noSuchItem))
             return
         }
 
-        guard let parentItemIdentifier = fileProviderUtility.shared.getParentItemIdentifier(metadata: metadata) else {
+        guard let parentItemIdentifier = fpUtility.getParentItemIdentifier(metadata: metadata) else {
             completionHandler(nil, NSFileProviderError(.noSuchItem))
             return
         }

+ 3 - 3
File Provider Extension/FileProviderExtension+Thumbnail.swift

@@ -34,7 +34,7 @@ extension FileProviderExtension {
 
         for itemIdentifier in itemIdentifiers {
 
-            guard let metadata = fileProviderUtility.shared.getTableMetadataFromItemIdentifier(itemIdentifier) else {
+            guard let metadata = fpUtility.getTableMetadataFromItemIdentifier(itemIdentifier) else {
                 counterProgress += 1
                 if counterProgress == progress.totalUnitCount { completionHandler(nil) }
                 continue
@@ -42,8 +42,8 @@ extension FileProviderExtension {
 
             if metadata.hasPreview {
 
-                let fileNamePath = CCUtility.returnFileNamePath(fromFileName: metadata.fileName, serverUrl: metadata.serverUrl, urlBase: metadata.urlBase, userId: metadata.userId, account: metadata.account)!
-                let fileNameIconLocalPath = CCUtility.getDirectoryProviderStorageIconOcId(metadata.ocId, etag: metadata.etag)!
+                let fileNamePath = utilityFileSystem.getFileNamePath(metadata.fileName, serverUrl: metadata.serverUrl, urlBase: metadata.urlBase, userId: metadata.userId)
+                let fileNameIconLocalPath = utilityFileSystem.getDirectoryProviderStorageIconOcId(metadata.ocId, etag: metadata.etag)
 
                 if let urlBase = metadata.urlBase.urlEncoded,
                    let fileNamePath = fileNamePath.urlEncoded,

+ 25 - 23
File Provider Extension/FileProviderExtension.swift

@@ -55,12 +55,14 @@ class FileProviderExtension: NSFileProviderExtension, NCNetworkingDelegate {
 
     var outstandingSessionTasks: [URL: URLSessionTask] = [:]
     var outstandingOcIdTemp: [String: String] = [:]
+    var fpUtility = fileProviderUtility()
+    let utilityFileSystem = NCUtilityFileSystem()
 
     override init() {
         super.init()
 
         // Create directory File Provider Storage
-        CCUtility.getDirectoryProviderStorage()
+        _ = utilityFileSystem.directoryProviderStorage
         // Configure URLSession
         _ = NCNetworking.shared.sessionManagerBackgroundExtension
     }
@@ -78,9 +80,9 @@ class FileProviderExtension: NSFileProviderExtension, NCNetworkingDelegate {
         if containerItemIdentifier != NSFileProviderItemIdentifier.workingSet {
             if fileProviderData.shared.setupAccount(domain: domain, providerExtension: self) == nil {
                 throw NSError(domain: NSFileProviderErrorDomain, code: NSFileProviderError.notAuthenticated.rawValue, userInfo: [:])
-            } else if let passcode = CCUtility.getPasscode(), !passcode.isEmpty, CCUtility.isPasscodeAtStartEnabled() {
+            } else if NCKeychain().passcode != nil, NCKeychain().requestPasscodeAtStart {
                 throw NSError(domain: NSFileProviderErrorDomain, code: NSFileProviderError.notAuthenticated.rawValue, userInfo: ["code": NSNumber(value: NCGlobal.shared.errorUnauthorizedFilesPasscode)])
-            } else if CCUtility.getDisableFilesApp() || NCBrandOptions.shared.disable_openin_file {
+            } else if NCKeychain().disableFilesApp || NCBrandOptions.shared.disable_openin_file {
                 throw NSError(domain: NSFileProviderErrorDomain, code: NSFileProviderError.notAuthenticated.rawValue, userInfo: ["code": NSNumber(value: NCGlobal.shared.errorDisableFilesApp)])
             }
         }
@@ -129,10 +131,10 @@ class FileProviderExtension: NSFileProviderExtension, NCNetworkingDelegate {
 
         } else {
 
-            guard let metadata = fileProviderUtility.shared.getTableMetadataFromItemIdentifier(identifier) else {
+            guard let metadata = fpUtility.getTableMetadataFromItemIdentifier(identifier) else {
                 throw NSFileProviderError(.noSuchItem)
             }
-            guard let parentItemIdentifier = fileProviderUtility.shared.getParentItemIdentifier(metadata: metadata) else {
+            guard let parentItemIdentifier = fpUtility.getParentItemIdentifier(metadata: metadata) else {
                 throw NSFileProviderError(.noSuchItem)
             }
             let item = FileProviderItem(metadata: metadata, parentItemIdentifier: parentItemIdentifier)
@@ -194,7 +196,7 @@ class FileProviderExtension: NSFileProviderExtension, NCNetworkingDelegate {
             return completionHandler(nil)
         }
 
-        guard let metadata = fileProviderUtility.shared.getTableMetadataFromItemIdentifier(identifier) else {
+        guard let metadata = fpUtility.getTableMetadataFromItemIdentifier(identifier) else {
             return completionHandler(NSFileProviderError(.noSuchItem))
         }
 
@@ -204,12 +206,12 @@ class FileProviderExtension: NSFileProviderExtension, NCNetworkingDelegate {
         }
 
         let tableLocalFile = NCManageDatabase.shared.getTableLocalFile(predicate: NSPredicate(format: "ocId == %@", metadata.ocId))
-        if tableLocalFile != nil && CCUtility.fileProviderStorageExists(metadata) && tableLocalFile?.etag == metadata.etag {
+        if tableLocalFile != nil && utilityFileSystem.fileProviderStorageExists(metadata) && tableLocalFile?.etag == metadata.etag {
             return completionHandler(nil)
         }
 
         let serverUrlFileName = metadata.serverUrl + "/" + metadata.fileName
-        let fileNameLocalPath = CCUtility.getDirectoryProviderStorageOcId(metadata.ocId, fileNameView: metadata.fileName)!
+        let fileNameLocalPath = utilityFileSystem.getDirectoryProviderStorageOcId(metadata.ocId, fileNameView: metadata.fileName)
 
         // Update status
         NCManageDatabase.shared.setMetadataStatus(ocId: metadata.ocId, status: NCGlobal.shared.metadataStatusDownloading)
@@ -227,7 +229,7 @@ class FileProviderExtension: NSFileProviderExtension, NCNetworkingDelegate {
         }) { _, etag, date, _, _, _, error in
 
             self.outstandingSessionTasks.removeValue(forKey: url)
-            guard var metadata = fileProviderUtility.shared.getTableMetadataFromItemIdentifier(identifier) else {
+            guard var metadata = self.fpUtility.getTableMetadataFromItemIdentifier(identifier) else {
                 completionHandler(NSFileProviderError(.noSuchItem))
                 return
             }
@@ -274,9 +276,9 @@ class FileProviderExtension: NSFileProviderExtension, NCNetworkingDelegate {
         // Temp ocId ?
         if outstandingOcIdTemp[ocId] != nil && outstandingOcIdTemp[ocId] != ocId {
             ocId = outstandingOcIdTemp[ocId]!
-            let atPath = CCUtility.getDirectoryProviderStorageOcId(itemIdentifier.rawValue, fileNameView: fileName)
-            let toPath = CCUtility.getDirectoryProviderStorageOcId(ocId, fileNameView: fileName)
-            CCUtility.copyFile(atPath: atPath, toPath: toPath)
+            let atPath = utilityFileSystem.getDirectoryProviderStorageOcId(itemIdentifier.rawValue, fileNameView: fileName)
+            let toPath = utilityFileSystem.getDirectoryProviderStorageOcId(ocId, fileNameView: fileName)
+            utilityFileSystem.copyFile(atPath: atPath, toPath: toPath)
         }
         guard let metadata = NCManageDatabase.shared.getMetadataFromOcId(ocId) else { return }
 
@@ -296,7 +298,7 @@ class FileProviderExtension: NSFileProviderExtension, NCNetworkingDelegate {
         if !fileHasLocalChanges {
             // remove the existing file to free up space
             do {
-                _ = try fileProviderUtility.shared.fileManager.removeItem(at: url)
+                _ = try fpUtility.fileManager.removeItem(at: url)
             } catch let error {
                 print("error: \(error)")
             }
@@ -323,7 +325,7 @@ class FileProviderExtension: NSFileProviderExtension, NCNetworkingDelegate {
                 var size = 0 as Int64
                 var error: NSError?
 
-                guard let tableDirectory = fileProviderUtility.shared.getTableDirectoryFromParentItemIdentifier(parentItemIdentifier, account: fileProviderData.shared.account, homeServerUrl: fileProviderData.shared.homeServerUrl) else {
+                guard let tableDirectory = self.fpUtility.getTableDirectoryFromParentItemIdentifier(parentItemIdentifier, account: fileProviderData.shared.account, homeServerUrl: fileProviderData.shared.homeServerUrl) else {
                     completionHandler(nil, NSFileProviderError(.noSuchItem))
                     return
                 }
@@ -332,7 +334,7 @@ class FileProviderExtension: NSFileProviderExtension, NCNetworkingDelegate {
 
                 // typefile directory ? (NOT PERMITTED)
                 do {
-                    let attributes = try fileProviderUtility.shared.fileManager.attributesOfItem(atPath: fileURL.path)
+                    let attributes = try self.fpUtility.fileManager.attributesOfItem(atPath: fileURL.path)
                     size = attributes[FileAttributeKey.size] as? Int64 ?? 0
                     let typeFile = attributes[FileAttributeKey.type] as? FileAttributeType
                     if typeFile == FileAttributeType.typeDirectory {
@@ -344,11 +346,11 @@ class FileProviderExtension: NSFileProviderExtension, NCNetworkingDelegate {
                     return
                 }
 
-                let fileName = NCUtilityFileSystem.shared.createFileName(fileURL.lastPathComponent, serverUrl: tableDirectory.serverUrl, account: fileProviderData.shared.account)
+                let fileName = self.utilityFileSystem.createFileName(fileURL.lastPathComponent, serverUrl: tableDirectory.serverUrl, account: fileProviderData.shared.account)
                 let ocIdTemp = NSUUID().uuidString.lowercased()
 
                 NSFileCoordinator().coordinate(readingItemAt: fileURL, options: .withoutChanges, error: &error) { url in
-                    _ = fileProviderUtility.shared.copyFile(url.path, toPath: CCUtility.getDirectoryProviderStorageOcId(ocIdTemp, fileNameView: fileName))
+                    _ = self.fpUtility.copyFile(url.path, toPath: self.utilityFileSystem.getDirectoryProviderStorageOcId(ocIdTemp, fileNameView: fileName))
                 }
 
                 fileURL.stopAccessingSecurityScopedResource()
@@ -361,7 +363,7 @@ class FileProviderExtension: NSFileProviderExtension, NCNetworkingDelegate {
                 NCManageDatabase.shared.addMetadata(metadata)
 
                 let serverUrlFileName = tableDirectory.serverUrl + "/" + fileName
-                let fileNameLocalPath = CCUtility.getDirectoryProviderStorageOcId(ocIdTemp, fileNameView: 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) {
 
@@ -383,7 +385,7 @@ class FileProviderExtension: NSFileProviderExtension, NCNetworkingDelegate {
         guard let metadataTemp = NCManageDatabase.shared.getMetadataFromOcId(ocIdTemp) else { return }
         let metadata = tableMetadata.init(value: metadataTemp)
 
-        let url = URL(fileURLWithPath: CCUtility.getDirectoryProviderStorageOcId(ocIdTemp, fileNameView: fileName))
+        let url = URL(fileURLWithPath: utilityFileSystem.getDirectoryProviderStorageOcId(ocIdTemp, fileNameView: fileName))
         DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
             self.outstandingSessionTasks.removeValue(forKey: url)
         }
@@ -411,14 +413,14 @@ class FileProviderExtension: NSFileProviderExtension, NCNetworkingDelegate {
             NCManageDatabase.shared.addLocalFile(metadata: metadata)
 
             // New file
-            if ocId != ocIdTemp {
+            if let ocId, ocId != ocIdTemp {
 
                 NCManageDatabase.shared.deleteMetadata(predicate: NSPredicate(format: "ocId == %@", ocIdTemp))
 
                 // File system
-                let atPath = CCUtility.getDirectoryProviderStorageOcId(ocIdTemp)
-                let toPath = CCUtility.getDirectoryProviderStorageOcId(ocId)
-                CCUtility.copyFile(atPath: atPath, toPath: toPath)
+                let atPath = utilityFileSystem.getDirectoryProviderStorageOcId(ocIdTemp)
+                let toPath = utilityFileSystem.getDirectoryProviderStorageOcId(ocId)
+                utilityFileSystem.copyFile(atPath: atPath, toPath: toPath)
             }
 
             fileProviderData.shared.signalEnumerator(ocId: metadata.ocId, update: true)

+ 2 - 2
File Provider Extension/FileProviderItem.swift

@@ -31,7 +31,7 @@ class FileProviderItem: NSObject, NSFileProviderItem {
     var parentItemIdentifier: NSFileProviderItemIdentifier
 
     var itemIdentifier: NSFileProviderItemIdentifier {
-        return fileProviderUtility.shared.getItemIdentifier(metadata: metadata)
+        return fileProviderUtility().getItemIdentifier(metadata: metadata)
     }
 
     var filename: String {
@@ -105,7 +105,7 @@ class FileProviderItem: NSObject, NSFileProviderItem {
         if metadata.directory {
             return true
         }
-        if CCUtility.fileProviderStorageExists(metadata) {
+        if NCUtilityFileSystem().fileProviderStorageExists(metadata) {
             return true
         } else {
             return false

+ 5 - 8
File Provider Extension/FileProviderUtility.swift

@@ -24,12 +24,9 @@
 import UIKit
 
 class fileProviderUtility: NSObject {
-    static let shared: fileProviderUtility = {
-        let instance = fileProviderUtility()
-        return instance
-    }()
 
-    var fileManager = FileManager()
+    let fileManager = FileManager()
+    let utilityFileSystem = NCUtilityFileSystem()
 
     func getAccountFromItemIdentifier(_ itemIdentifier: NSFileProviderItemIdentifier) -> String? {
 
@@ -53,15 +50,15 @@ class fileProviderUtility: NSObject {
         let itemIdentifier = getItemIdentifier(metadata: metadata)
 
         if metadata.directory {
-            CCUtility.getDirectoryProviderStorageOcId(itemIdentifier.rawValue)
+            _ = utilityFileSystem.getDirectoryProviderStorageOcId(itemIdentifier.rawValue)
         } else {
-            CCUtility.getDirectoryProviderStorageOcId(itemIdentifier.rawValue, fileNameView: metadata.fileNameView)
+            _ = utilityFileSystem.getDirectoryProviderStorageOcId(itemIdentifier.rawValue, fileNameView: metadata.fileNameView)
         }
     }
 
     func getParentItemIdentifier(metadata: tableMetadata) -> NSFileProviderItemIdentifier? {
 
-        let homeServerUrl = NCUtilityFileSystem.shared.getHomeServer(urlBase: metadata.urlBase, userId: metadata.userId)
+        let homeServerUrl = utilityFileSystem.getHomeServer(urlBase: metadata.urlBase, userId: metadata.userId)
         if let directory = NCManageDatabase.shared.getTableDirectory(predicate: NSPredicate(format: "account == %@ AND serverUrl == %@", metadata.account, metadata.serverUrl)) {
             if directory.serverUrl == homeServerUrl {
                 return NSFileProviderItemIdentifier(NSFileProviderItemIdentifier.rootContainer.rawValue)

文件差异内容过多而无法显示
+ 251 - 56
Nextcloud.xcodeproj/project.pbxproj


+ 1 - 1
Notification Service Extension/NotificationService.swift

@@ -38,7 +38,7 @@ class NotificationService: UNNotificationServiceExtension {
                 if let message = bestAttemptContent.userInfo["subject"] as? String {
                     let tableAccounts = NCManageDatabase.shared.getAllAccount()
                     for tableAccount in tableAccounts {
-                        guard let privateKey = CCUtility.getPushNotificationPrivateKey(tableAccount.account),
+                        guard let privateKey = NCKeychain().getPushNotificationPrivateKey(account: tableAccount.account),
                               let decryptedMessage = NCPushNotificationEncryption.shared().decryptPushNotification(message, withDevicePrivateKey: privateKey),
                               let data = decryptedMessage.data(using: .utf8) else {
                             continue

+ 6 - 4
Share/NCShareCell.swift

@@ -37,6 +37,8 @@ class NCShareCell: UITableViewCell {
     @IBOutlet weak var sizeCell: UILabel!
     weak var delegate: (NCShareCellDelegate & UIViewController)?
     var fileName = ""
+    let utilityFileSystem = NCUtilityFileSystem()
+    let utility = NCUtility()
 
     func setup(fileName: String) {
         self.fileName = fileName
@@ -52,16 +54,16 @@ class NCShareCell: UITableViewCell {
             if !resultInternalType.iconName.isEmpty {
                 imageCell?.image = UIImage(named: resultInternalType.iconName)
             } else {
-                imageCell?.image = NCBrandColor.cacheImages.file
+                imageCell?.image = NCImageCache.images.file
             }
         }
 
         fileNameCell?.text = fileName
 
-        let fileSize = NCUtilityFileSystem.shared.getFileSize(filePath: (NSTemporaryDirectory() + fileName))
-        sizeCell?.text = CCUtility.transformedSize(fileSize)
+        let fileSize = utilityFileSystem.getFileSize(filePath: (NSTemporaryDirectory() + fileName))
+        sizeCell?.text = utilityFileSystem.transformedSize(fileSize)
 
-        moreButton?.setImage(NCUtility.shared.loadImage(named: "more").image(color: .label, size: 15), for: .normal)
+        moreButton?.setImage(utility.loadImage(named: "more").image(color: .label, size: 15), for: .normal)
     }
 
     @IBAction func buttonTapped(_ sender: Any) {

+ 16 - 19
Share/NCShareExtension+DataSource.swift

@@ -29,12 +29,9 @@ import NextcloudKit
 extension NCShareExtension: UICollectionViewDelegate {
 
     func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
-        guard let metadata = dataSource.cellForItemAt(indexPath: indexPath),
-              let serverUrl = CCUtility.stringAppendServerUrl(metadata.serverUrl, addFileName: metadata.fileName) else {
-                  return showAlert(description: "_invalid_url_")
-              }
-
-        if metadata.e2eEncrypted && !CCUtility.isEnd(toEndEnabled: activeAccount.account) {
+        guard let metadata = dataSource.cellForItemAt(indexPath: indexPath) else { return showAlert(description: "_invalid_url_") }
+        let serverUrl = utilityFileSystem.stringAppendServerUrl(metadata.serverUrl, addFileName: metadata.fileName)
+        if metadata.e2eEncrypted && !NCKeychain().isEndToEndEnabled(account: activeAccount.account) {
             showAlert(title: "_info_", description: "_e2e_goto_settings_for_enable_")
         }
 
@@ -87,7 +84,7 @@ extension NCShareExtension: UICollectionViewDataSource {
 
         // image Favorite
         if metadata.favorite {
-            cell.imageFavorite.image = NCBrandColor.cacheImages.favorite
+            cell.imageFavorite.image = NCImageCache.images.favorite
         }
 
         cell.imageSelect.isHidden = true
@@ -98,7 +95,7 @@ extension NCShareExtension: UICollectionViewDataSource {
 
         // Live Photo
         if metadata.livePhoto {
-            cell.imageStatus.image = NCBrandColor.cacheImages.livePhoto
+            cell.imageStatus.image = NCImageCache.images.livePhoto
         }
 
         // Add TAGS
@@ -119,31 +116,31 @@ extension NCShareExtension: UICollectionViewDataSource {
         }
 
         if metadata.e2eEncrypted {
-            cell.imageItem.image = NCBrandColor.cacheImages.folderEncrypted
+            cell.imageItem.image = NCImageCache.images.folderEncrypted
         } else if isShare {
-            cell.imageItem.image = NCBrandColor.cacheImages.folderSharedWithMe
+            cell.imageItem.image = NCImageCache.images.folderSharedWithMe
         } else if !metadata.shareType.isEmpty {
             metadata.shareType.contains(3) ?
-            (cell.imageItem.image = NCBrandColor.cacheImages.folderPublic) :
-            (cell.imageItem.image = NCBrandColor.cacheImages.folderSharedWithMe)
+            (cell.imageItem.image = NCImageCache.images.folderPublic) :
+            (cell.imageItem.image = NCImageCache.images.folderSharedWithMe)
         } else if metadata.mountType == "group" {
-            cell.imageItem.image = NCBrandColor.cacheImages.folderGroup
+            cell.imageItem.image = NCImageCache.images.folderGroup
         } else if isMounted {
-            cell.imageItem.image = NCBrandColor.cacheImages.folderExternal
+            cell.imageItem.image = NCImageCache.images.folderExternal
         } else if metadata.fileName == autoUploadFileName && metadata.serverUrl == autoUploadDirectory {
-            cell.imageItem.image = NCBrandColor.cacheImages.folderAutomaticUpload
+            cell.imageItem.image = NCImageCache.images.folderAutomaticUpload
         } else {
-            cell.imageItem.image = NCBrandColor.cacheImages.folder
+            cell.imageItem.image = NCImageCache.images.folder
         }
 
-        cell.labelInfo.text = CCUtility.dateDiff(metadata.date as Date)
+        cell.labelInfo.text = utility.dateDiff(metadata.date as Date)
 
-        let lockServerUrl = CCUtility.stringAppendServerUrl(metadata.serverUrl, addFileName: metadata.fileName)!
+        let lockServerUrl = utilityFileSystem.stringAppendServerUrl(metadata.serverUrl, addFileName: metadata.fileName)
         let tableDirectory = NCManageDatabase.shared.getTableDirectory(predicate: NSPredicate(format: "account == %@ AND serverUrl == %@", activeAccount.account, lockServerUrl))
 
         // Local image: offline
         if tableDirectory != nil && tableDirectory!.offline {
-            cell.imageLocal.image = NCBrandColor.cacheImages.offlineFlag
+            cell.imageLocal.image = NCImageCache.images.offlineFlag
         }
     }
 }

+ 3 - 2
Share/NCShareExtension+Files.swift

@@ -99,7 +99,7 @@ class NCFilesExtensionHandler {
 
     @discardableResult
     init(items: [NSExtensionItem], completion: @escaping ([String]) -> Void) {
-        CCUtility.emptyTemporaryDirectory()
+        NCUtilityFileSystem().emptyTemporaryDirectory()
         var counter = 0
 
         self.itemsProvider = items.compactMap({ $0.attachments }).flatMap { $0.filter({
@@ -118,7 +118,8 @@ class NCFilesExtensionHandler {
                 if let url = item as? URL, url.isFileURL, !url.lastPathComponent.isEmpty {
                     originalName = url.lastPathComponent
 
-                    if fileNames.contains(originalName), let incrementalNumber = CCUtility.getIncrementalNumber() {
+                    if fileNames.contains(originalName) {
+                        let incrementalNumber = NCKeychain().incrementalNumber
                         originalName = "\(url.deletingPathExtension().lastPathComponent) \(incrementalNumber).\(url.pathExtension)"
                     }
                 }

+ 5 - 4
Share/NCShareExtension+NCDelegate.swift

@@ -82,13 +82,14 @@ extension NCShareExtension: NCEmptyDataSetDelegate, NCAccountRequestDelegate {
         // COLORS
         NCBrandColor.shared.settingThemingColor(account: activeAccount.account)
         NCBrandColor.shared.createUserColors()
+        NCImageCache.shared.createImagesBrandCache()
 
         // NETWORKING
         NextcloudKit.shared.setup(
             account: activeAccount.account,
             user: activeAccount.user,
             userId: activeAccount.userId,
-            password: CCUtility.getPassword(activeAccount.account),
+            password: NCKeychain().getPassword(account: activeAccount.account),
             urlBase: activeAccount.urlBase,
             userAgent: userAgent,
             nextcloudVersion: 0,
@@ -98,7 +99,7 @@ extension NCShareExtension: NCEmptyDataSetDelegate, NCAccountRequestDelegate {
         autoUploadFileName = NCManageDatabase.shared.getAccountAutoUploadFileName()
         autoUploadDirectory = NCManageDatabase.shared.getAccountAutoUploadDirectory(urlBase: activeAccount.urlBase, userId: activeAccount.userId, account: activeAccount.account)
 
-        serverUrl = NCUtilityFileSystem.shared.getHomeServer(urlBase: activeAccount.urlBase, userId: activeAccount.userId)
+        serverUrl = utilityFileSystem.getHomeServer(urlBase: activeAccount.urlBase, userId: activeAccount.userId)
 
         layoutForView = NCManageDatabase.shared.getLayoutForView(account: activeAccount.account, key: keyLayout, serverUrl: serverUrl)
 
@@ -131,7 +132,7 @@ extension NCShareExtension: NCShareCellDelegate, NCRenameFileDelegate, NCListCel
         if let previewImage = UIImage.downsample(imageAt: URL(fileURLWithPath: NSTemporaryDirectory() + fileName), to: CGSize(width: 140, height: 140)) {
             vcRename.imagePreview = previewImage
         } else {
-            vcRename.imagePreview = UIImage(named: resultInternalType.iconName) ?? NCBrandColor.cacheImages.file
+            vcRename.imagePreview = UIImage(named: resultInternalType.iconName) ?? NCImageCache.images.file
         }
 
         let popup = NCPopupViewController(contentController: vcRename, popupWidth: vcRename.width, popupHeight: vcRename.height)
@@ -143,7 +144,7 @@ extension NCShareExtension: NCShareCellDelegate, NCRenameFileDelegate, NCListCel
         guard fileName != fileNameNew else { return }
         guard let fileIx = self.filesName.firstIndex(of: fileName),
               !self.filesName.contains(fileNameNew),
-              NCUtilityFileSystem.shared.moveFile(atPath: (NSTemporaryDirectory() + fileName), toPath: (NSTemporaryDirectory() + fileNameNew)) else {
+              utilityFileSystem.moveFile(atPath: (NSTemporaryDirectory() + fileName), toPath: (NSTemporaryDirectory() + fileNameNew)) else {
                   return showAlert(title: "_single_file_conflict_title_", description: "'\(fileName)' -> '\(fileNameNew)'")
               }
 

+ 19 - 20
Share/NCShareExtension.swift

@@ -71,6 +71,8 @@ class NCShareExtension: UIViewController {
     var uploadMetadata: [tableMetadata] = []
     var uploadStarted = false
     let hud = JGProgressHUD()
+    let utilityFileSystem = NCUtilityFileSystem()
+    let utility = NCUtility()
 
     // MARK: - View Life Cycle
 
@@ -97,28 +99,26 @@ class NCShareExtension: UIViewController {
         commandViewHeightConstraint.constant = heightCommandView
 
         createFolderView.layer.cornerRadius = 10
-        createFolderImage.image = NCUtility.shared.loadImage(named: "folder.badge.plus", color: .label)
+        createFolderImage.image = utility.loadImage(named: "folder.badge.plus", color: .label)
         createFolderLabel.text = NSLocalizedString("_create_folder_", comment: "")
         let createFolderGesture = UITapGestureRecognizer(target: self, action: #selector(actionCreateFolder))
         createFolderView.addGestureRecognizer(createFolderGesture)
 
         uploadView.layer.cornerRadius = 10
 
-        // uploadImage.image = NCUtility.shared.loadImage(named: "square.and.arrow.up", color: .label)
+        // uploadImage.image = utility).loadImage(named: "square.and.arrow.up", color: .label)
         uploadLabel.text = NSLocalizedString("_upload_", comment: "")
         uploadLabel.textColor = .systemBlue
         let uploadGesture = UITapGestureRecognizer(target: self, action: #selector(actionUpload))
         uploadView.addGestureRecognizer(uploadGesture)
 
         // LOG
-        let levelLog = CCUtility.getLogLevel()
-        let isSimulatorOrTestFlight = NCUtility.shared.isSimulatorOrTestFlight()
-        let versionNextcloudiOS = String(format: NCBrandOptions.shared.textCopyrightNextcloudiOS, NCUtility.shared.getVersionApp())
+        let levelLog = NCKeychain().logLevel
+        let isSimulatorOrTestFlight = utility.isSimulatorOrTestFlight()
+        let versionNextcloudiOS = String(format: NCBrandOptions.shared.textCopyrightNextcloudiOS, utility.getVersionApp())
 
         NextcloudKit.shared.nkCommonInstance.levelLog = levelLog
-        if let pathDirectoryGroup = CCUtility.getDirectoryGroup()?.path {
-            NextcloudKit.shared.nkCommonInstance.pathLog = pathDirectoryGroup
-        }
+        NextcloudKit.shared.nkCommonInstance.pathLog = utilityFileSystem.directoryGroup
         if isSimulatorOrTestFlight {
             NextcloudKit.shared.nkCommonInstance.writeLog("[INFO] Start Share session with level \(levelLog) " + versionNextcloudiOS + " (Simulator / TestFlight)")
         } else {
@@ -128,10 +128,10 @@ class NCShareExtension: UIViewController {
         // Colors
         if let activeAccount = NCManageDatabase.shared.getActiveAccount() {
             NCBrandColor.shared.settingThemingColor(account: activeAccount.account)
-        } else {
-            NCBrandColor.shared.createImagesThemingColor()
         }
         NCBrandColor.shared.createUserColors()
+        NCImageCache.shared.createImagesCache()
+        NCImageCache.shared.createImagesBrandCache()
 
         hud.indicatorView = JGProgressHUDRingIndicatorView()
         if let indicatorView = hud.indicatorView as? JGProgressHUDRingIndicatorView {
@@ -211,18 +211,18 @@ class NCShareExtension: UIViewController {
                 self.reloadDatasource(withLoadFolder: true)
 
                 var navigationTitle = (self.serverUrl as NSString).lastPathComponent
-                if NCUtilityFileSystem.shared.getHomeServer(urlBase: self.activeAccount.urlBase, userId: self.activeAccount.userId) == self.serverUrl {
+                if self.utilityFileSystem.getHomeServer(urlBase: self.activeAccount.urlBase, userId: self.activeAccount.userId) == self.serverUrl {
                     navigationTitle = NCBrandOptions.shared.brand
                 }
                 self.setNavigationBar(navigationTitle: navigationTitle)
             }
         }
 
-        let image = NCUtility.shared.loadUserImage(for: activeAccount.user, displayName: activeAccount.displayName, userBaseUrl: activeAccount)
+        let image = utility.loadUserImage(for: activeAccount.user, displayName: activeAccount.displayName, userBaseUrl: activeAccount)
         let profileButton = UIButton(type: .custom)
         profileButton.setImage(image, for: .normal)
 
-        if serverUrl == NCUtilityFileSystem.shared.getHomeServer(urlBase: activeAccount.urlBase, userId: activeAccount.userId) {
+        if serverUrl == utilityFileSystem.getHomeServer(urlBase: activeAccount.urlBase, userId: activeAccount.userId) {
 
             var title = "  "
             if let userAlias = activeAccount?.alias, !userAlias.isEmpty {
@@ -243,7 +243,7 @@ class NCShareExtension: UIViewController {
             }
         }
         var navItems = [UIBarButtonItem(customView: profileButton)]
-        if serverUrl != NCUtilityFileSystem.shared.getHomeServer(urlBase: activeAccount.urlBase, userId: activeAccount.userId) {
+        if serverUrl != utilityFileSystem.getHomeServer(urlBase: activeAccount.urlBase, userId: activeAccount.userId) {
             let space = UIBarButtonItem(barButtonSystemItem: .fixedSpace, target: nil, action: nil)
             space.width = 20
             navItems.append(contentsOf: [UIBarButtonItem(customView: backButton), space])
@@ -297,8 +297,8 @@ extension NCShareExtension {
         var conflicts: [tableMetadata] = []
         for fileName in filesName {
             let ocId = NSUUID().uuidString
-            let toPath = CCUtility.getDirectoryProviderStorageOcId(ocId, fileNameView: fileName)!
-            guard NCUtilityFileSystem.shared.copyFile(atPath: (NSTemporaryDirectory() + fileName), toPath: toPath) else { continue }
+            let toPath = utilityFileSystem.getDirectoryProviderStorageOcId(ocId, fileNameView: fileName)
+            guard utilityFileSystem.copyFile(atPath: (NSTemporaryDirectory() + fileName), toPath: toPath) else { continue }
             let metadata = NCManageDatabase.shared.createMetadata(
                 account: activeAccount.account, user: activeAccount.user, userId: activeAccount.userId,
                 fileName: fileName, fileNameView: fileName,
@@ -307,7 +307,7 @@ extension NCShareExtension {
                 contentType: "")
             metadata.session = NextcloudKit.shared.nkCommonInstance.sessionIdentifierUpload
             metadata.sessionSelector = NCGlobal.shared.selectorUploadFileShareExtension
-            metadata.size = NCUtilityFileSystem.shared.getFileSize(filePath: toPath)
+            metadata.size = utilityFileSystem.getFileSize(filePath: toPath)
             metadata.status = NCGlobal.shared.metadataStatusWaitUpload
             if NCManageDatabase.shared.getMetadataConflict(account: activeAccount.account, serverUrl: serverUrl, fileNameView: fileName) != nil {
                 conflicts.append(metadata)
@@ -352,15 +352,14 @@ extension NCShareExtension {
         hud.textLabel.text = NSLocalizedString("_upload_file_", comment: "") + " \(counterUploaded + 1) " + NSLocalizedString("_of_", comment: "") + " \(filesName.count)"
         hud.show(in: self.view)
 
-        NCNetworking.shared.upload(metadata: metadata, uploadE2EEDelegate: self) {
+        NCNetworking.shared.upload(metadata: metadata, uploadE2EEDelegate: self, hudView: self.view) {
             self.hud.progress = 0
         } progressHandler: { _, _, fractionCompleted in
             self.hud.progress = Float(fractionCompleted)
         } completion: { error in
             if error != .success {
-                let path = CCUtility.getDirectoryProviderStorageOcId(metadata.ocId)!
                 NCManageDatabase.shared.deleteMetadata(predicate: NSPredicate(format: "ocId == %@", metadata.ocId))
-                NCUtilityFileSystem.shared.deleteFile(filePath: path)
+                self.utilityFileSystem.removeFile(atPath: self.utilityFileSystem.getDirectoryProviderStorageOcId(metadata.ocId))
                 self.uploadErrors.append(metadata)
             }
             self.counterUploaded += 1

+ 1 - 0
Share/Share-Bridging-Header.h

@@ -4,3 +4,4 @@
 
 #import "NCEndToEndEncryption.h"
 #import "CCUtility.h"
+#import "UIImage+animatedGIF.h"

+ 13 - 0
Tests/NextcloudIntegrationTests/FilesIntegrationTests.swift

@@ -5,6 +5,19 @@
 //  Created by Milen Pivchev on 5/19/23.
 //  Copyright © 2023 Marino Faggiana. All rights reserved.
 //
+//  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 XCTest
 import NextcloudKit

+ 15 - 0
Tests/NextcloudSnapshotTests/Extensions/SwiftUIView+Extensions.swift

@@ -5,6 +5,21 @@
 //  Created by Milen on 06.06.23.
 //  Copyright © 2023 Marino Faggiana. All rights reserved.
 //
+//  Author Marino Faggiana <marino.faggiana@nextcloud.com>
+//
+//  This program is free software: you can redistribute it and/or modify
+//  it under the terms of the GNU General Public License as published by
+//  the Free Software Foundation, either version 3 of the License, or
+//  (at your option) any later version.
+//
+//  This program is distributed in the hope that it will be useful,
+//  but WITHOUT ANY WARRANTY; without even the implied warranty of
+//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+//  GNU General Public License for more details.
+//
+//  You should have received a copy of the GNU General Public License
+//  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+//
 
 import Foundation
 import SwiftUI

+ 13 - 0
Tests/NextcloudSnapshotTests/NextcloudSnapshotTests.swift

@@ -5,6 +5,19 @@
 //  Created by Milen on 06.06.23.
 //  Copyright © 2023 Marino Faggiana. All rights reserved.
 //
+//  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 XCTest
 import SnapshotTesting

+ 13 - 0
Tests/NextcloudUITests/BaseUIXCTestCase.swift

@@ -5,6 +5,19 @@
 //  Created by Milen on 20.06.23.
 //  Copyright © 2023 Marino Faggiana. All rights reserved.
 //
+//  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 XCTest
 

+ 13 - 0
Tests/NextcloudUITests/LoginUITests.swift

@@ -5,6 +5,19 @@
 //  Created by Milen Pivchev on 5/19/23.
 //  Copyright © 2023 Marino Faggiana. All rights reserved.
 //
+//  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 XCTest
 

+ 15 - 1
Tests/NextcloudUnitTests/NextcloudUnitTests.swift

@@ -2,9 +2,23 @@
 //  NextcloudTests.swift
 //  NextcloudTests
 //
-//  Created by Henrik Storch on 01.12.21.
 //  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 XCTest
 

+ 11 - 11
Widget/Dashboard/DashboardData.swift

@@ -90,7 +90,7 @@ func convertDataToImage(data: Data?, size: CGSize, fileNameToWrite: String?) ->
     }
     if let fileName = fileNameToWrite, let image = imageData {
         do {
-            let fileNamePath: String = CCUtility.getDirectoryUserData() + "/" + fileName + ".png"
+            let fileNamePath: String = NCUtilityFileSystem().directoryUserData + "/" + fileName + ".png"
             try image.pngData()?.write(to: URL(fileURLWithPath: fileNamePath), options: .atomic)
         } catch { }
     }
@@ -99,6 +99,8 @@ func convertDataToImage(data: Data?, size: CGSize, fileNameToWrite: String?) ->
 
 func getDashboardDataEntry(configuration: DashboardIntent?, isPreview: Bool, displaySize: CGSize, completion: @escaping (_ entry: DashboardDataEntry) -> Void) {
 
+    let utilityFileSystem = NCUtilityFileSystem()
+    let utility = NCUtility()
     let dashboardItems = getDashboardItems(displaySize: displaySize, withButton: false)
     let datasPlaceholder = Array(dashboardDatasTest[0...dashboardItems - 1])
     var account: tableAccount?
@@ -130,7 +132,7 @@ func getDashboardDataEntry(configuration: DashboardIntent?, isPreview: Bool, dis
     }
 
     // NETWORKING
-    let password = CCUtility.getPassword(account.account)!
+    let password = NCKeychain().getPassword(account: account.account)
     NextcloudKit.shared.setup(
         account: account.account,
         user: account.user,
@@ -142,14 +144,12 @@ func getDashboardDataEntry(configuration: DashboardIntent?, isPreview: Bool, dis
         delegate: NCNetworking.shared)
 
     // LOG
-    let levelLog = CCUtility.getLogLevel()
-    let isSimulatorOrTestFlight = NCUtility.shared.isSimulatorOrTestFlight()
-    let versionNextcloudiOS = String(format: NCBrandOptions.shared.textCopyrightNextcloudiOS, NCUtility.shared.getVersionApp())
+    let levelLog = NCKeychain().logLevel
+    let isSimulatorOrTestFlight = utility.isSimulatorOrTestFlight()
+    let versionNextcloudiOS = String(format: NCBrandOptions.shared.textCopyrightNextcloudiOS, utility.getVersionApp())
 
     NextcloudKit.shared.nkCommonInstance.levelLog = levelLog
-    if let pathDirectoryGroup = CCUtility.getDirectoryGroup()?.path {
-        NextcloudKit.shared.nkCommonInstance.pathLog = pathDirectoryGroup
-    }
+    NextcloudKit.shared.nkCommonInstance.pathLog = utilityFileSystem.directoryGroup
     if isSimulatorOrTestFlight {
         NextcloudKit.shared.nkCommonInstance.writeLog("[INFO] Start \(NCBrandOptions.shared.brand) dashboard widget session with level \(levelLog) " + versionNextcloudiOS + " (Simulator / TestFlight)")
     } else {
@@ -162,7 +162,7 @@ func getDashboardDataEntry(configuration: DashboardIntent?, isPreview: Bool, dis
 
     var imagetmp = UIImage(named: "widget")!
     if let fileName = tableDashboard?.iconClass {
-        let fileNamePath: String = CCUtility.getDirectoryUserData() + "/" + fileName + ".png"
+        let fileNamePath: String = utilityFileSystem.directoryUserData + "/" + fileName + ".png"
         if let image = UIImage(contentsOfFile: fileNamePath) {
             imagetmp = image.withTintColor(.label, renderingMode: .alwaysOriginal)
         }
@@ -206,7 +206,7 @@ func getDashboardDataEntry(configuration: DashboardIntent?, isPreview: Bool, dis
                                     if (pathComponents.last as? NSString)?.pathExtension.lowercased() == "svg" {
                                         imageTemplate = true
                                     }
-                                    if let item = CCUtility.value(forKey: "fileId", fromQueryItems: queryItems) {
+                                    if let item = queryItems?.filter({ $0.name == "fileId" }).first?.value {
                                         iconFileName = item
                                     } else if pathComponents.contains("avatar") {
                                         iconFileName = pathComponents[pathComponents.count - 2]
@@ -224,7 +224,7 @@ func getDashboardDataEntry(configuration: DashboardIntent?, isPreview: Bool, dis
                                     imageColor = UIColor(hex: colorString)
                                     icon = UIImage(systemName: "circle.fill")!
                                 } else if let fileName = iconFileName {
-                                    let fileNamePath: String = CCUtility.getDirectoryUserData() + "/" + fileName + ".png"
+                                    let fileNamePath: String = utilityFileSystem.directoryUserData + "/" + fileName + ".png"
                                     if FileManager().fileExists(atPath: fileNamePath), let image = UIImage(contentsOfFile: fileNamePath) {
                                         icon = image
                                     } else {

+ 16 - 15
Widget/Files/FilesData.swift

@@ -86,6 +86,8 @@ func getFilesItems(displaySize: CGSize) -> Int {
 
 func getFilesDataEntry(configuration: AccountIntent?, isPreview: Bool, displaySize: CGSize, completion: @escaping (_ entry: FilesDataEntry) -> Void) {
 
+    let utilityFileSystem = NCUtilityFileSystem()
+    let utility = NCUtility()
     let filesItems = getFilesItems(displaySize: displaySize)
     let datasPlaceholder = Array(filesDatasTest[0...filesItems - 1])
     var account: tableAccount?
@@ -124,7 +126,7 @@ func getFilesDataEntry(configuration: AccountIntent?, isPreview: Bool, displaySi
     }
 
     // NETWORKING
-    let password = CCUtility.getPassword(account.account)!
+    let password = NCKeychain().getPassword(account: account.account)
     NextcloudKit.shared.setup(
         account: account.account,
         user: account.user,
@@ -192,14 +194,13 @@ func getFilesDataEntry(configuration: AccountIntent?, isPreview: Bool, displaySi
     let requestBody = String(format: requestBodyRecent, "/files/" + account.userId, lessDateString)
 
     // LOG
-    let levelLog = CCUtility.getLogLevel()
-    let isSimulatorOrTestFlight = NCUtility.shared.isSimulatorOrTestFlight()
-    let versionNextcloudiOS = String(format: NCBrandOptions.shared.textCopyrightNextcloudiOS, NCUtility.shared.getVersionApp())
+    let levelLog = NCKeychain().logLevel
+    let isSimulatorOrTestFlight = utility.isSimulatorOrTestFlight()
+    let versionNextcloudiOS = String(format: NCBrandOptions.shared.textCopyrightNextcloudiOS, utility.getVersionApp())
 
     NextcloudKit.shared.nkCommonInstance.levelLog = levelLog
-    if let pathDirectoryGroup = CCUtility.getDirectoryGroup()?.path {
-        NextcloudKit.shared.nkCommonInstance.pathLog = pathDirectoryGroup
-    }
+    NextcloudKit.shared.nkCommonInstance.pathLog = utilityFileSystem.directoryGroup
+
     if isSimulatorOrTestFlight {
         NextcloudKit.shared.nkCommonInstance.writeLog("[INFO] Start \(NCBrandOptions.shared.brand) widget session with level \(levelLog) " + versionNextcloudiOS + " (Simulator / TestFlight)")
     } else {
@@ -207,7 +208,7 @@ func getFilesDataEntry(configuration: AccountIntent?, isPreview: Bool, displaySi
     }
 
     let options = NKRequestOptions(timeout: 90, queue: NextcloudKit.shared.nkCommonInstance.backgroundQueue)
-    NextcloudKit.shared.searchBodyRequest(serverUrl: account.urlBase, requestBody: requestBody, showHiddenFiles: CCUtility.getShowHiddenFiles(), options: options) { _, files, data, error in
+    NextcloudKit.shared.searchBodyRequest(serverUrl: account.urlBase, requestBody: requestBody, showHiddenFiles: NCKeychain().showHiddenFiles, options: options) { _, files, data, error in
         Task {
             var datas: [FilesData] = []
             var imageRecent = UIImage(named: "file")!
@@ -219,10 +220,10 @@ func getFilesDataEntry(configuration: AccountIntent?, isPreview: Bool, displaySi
                 guard !isLive(file: file, files: files) else { continue }
 
                 // SUBTITLE
-                let subTitle = CCUtility.dateDiff(file.date as Date) + " · " + CCUtility.transformedSize(file.size)
+                let subTitle = utility.dateDiff(file.date as Date) + " · " + utilityFileSystem.transformedSize(file.size)
 
                 // URL: nextcloud://open-file?path=Talk/IMG_0000123.jpg&user=marinofaggiana&link=https://cloud.nextcloud.com/f/123
-                guard var path = NCUtilityFileSystem.shared.getPath(path: file.path, user: file.user, fileName: file.fileName).urlEncoded else { continue }
+                guard var path = utilityFileSystem.getPath(path: file.path, user: file.user, fileName: file.fileName).urlEncoded else { continue }
                 if path.first == "/" { path = String(path.dropFirst())}
                 guard let user = file.user.urlEncoded else { continue }
                 let link = file.urlBase + "/f/" + file.fileId
@@ -233,19 +234,19 @@ func getFilesDataEntry(configuration: AccountIntent?, isPreview: Bool, displaySi
                 if !file.iconName.isEmpty {
                     imageRecent = UIImage(named: file.iconName)!
                 }
-                if let image = NCUtility.shared.createFilePreviewImage(ocId: file.ocId, etag: file.etag, fileNameView: file.fileName, classFile: file.classFile, status: 0, createPreviewMedia: false) {
+                if let image = utility.createFilePreviewImage(ocId: file.ocId, etag: file.etag, fileNameView: file.fileName, classFile: file.classFile, status: 0, createPreviewMedia: false) {
                     imageRecent = image
                 } else if file.hasPreview {
-                    let fileNamePathOrFileId = CCUtility.returnFileNamePath(fromFileName: file.fileName, serverUrl: file.serverUrl, urlBase: file.urlBase, userId: file.userId, account: account.account)!
-                    let fileNamePreviewLocalPath = CCUtility.getDirectoryProviderStoragePreviewOcId(file.ocId, etag: file.etag)!
-                    let fileNameIconLocalPath = CCUtility.getDirectoryProviderStorageIconOcId(file.ocId, etag: file.etag)!
+                    let fileNamePathOrFileId = utilityFileSystem.getFileNamePath(file.fileName, serverUrl: file.serverUrl, urlBase: file.urlBase, userId: file.userId)
+                    let fileNamePreviewLocalPath = utilityFileSystem.getDirectoryProviderStoragePreviewOcId(file.ocId, etag: file.etag)
+                    let fileNameIconLocalPath = utilityFileSystem.getDirectoryProviderStorageIconOcId(file.ocId, etag: file.etag)
                     let (_, _, imageIcon, _, _, _) = await NextcloudKit.shared.downloadPreview(fileNamePathOrFileId: fileNamePathOrFileId, fileNamePreviewLocalPath: fileNamePreviewLocalPath, widthPreview: NCGlobal.shared.sizePreview, heightPreview: NCGlobal.shared.sizePreview, fileNameIconLocalPath: fileNameIconLocalPath, sizeIcon: NCGlobal.shared.sizeIcon, options: options)
                     if let image = imageIcon {
                         imageRecent = image
                     }
                 }
 
-                let isDirectoryE2EE = NCUtility.shared.isDirectoryE2EE(file: file)
+                let isDirectoryE2EE = utilityFileSystem.isDirectoryE2EE(file: file)
                 let metadata = NCManageDatabase.shared.convertFileToMetadata(file, isDirectoryE2EE: isDirectoryE2EE)
 
                 // DATA

+ 4 - 3
Widget/Lockscreen/LockscreenData.swift

@@ -37,6 +37,7 @@ struct LockscreenData: TimelineEntry {
 
 func getLockscreenDataEntry(configuration: AccountIntent?, isPreview: Bool, family: WidgetFamily, completion: @escaping (_ entry: LockscreenData) -> Void) {
 
+    let utilityFileSystem = NCUtilityFileSystem()
     var account: tableAccount?
     var quotaRelative: Float = 0
 
@@ -63,7 +64,7 @@ func getLockscreenDataEntry(configuration: AccountIntent?, isPreview: Bool, fami
     }
 
     // NETWORKING
-    let password = CCUtility.getPassword(account.account)!
+    let password = NCKeychain().getPassword(account: account.account)
     NextcloudKit.shared.setup(
         account: account.account,
         user: account.user,
@@ -82,7 +83,7 @@ func getLockscreenDataEntry(configuration: AccountIntent?, isPreview: Bool, fami
                     if userProfile.quotaRelative > 0 {
                         quotaRelative = Float(userProfile.quotaRelative) / 100
                     }
-                    let quotaUsed: String = CCUtility.transformedSize(userProfile.quotaUsed)
+                    let quotaUsed: String = utilityFileSystem.transformedSize(userProfile.quotaUsed)
                     var quotaTotal: String = ""
 
                     switch userProfile.quotaTotal {
@@ -93,7 +94,7 @@ func getLockscreenDataEntry(configuration: AccountIntent?, isPreview: Bool, fami
                     case -3:
                         quotaTotal = ""
                     default:
-                        quotaTotal = CCUtility.transformedSize(userProfile.quotaTotal)
+                        quotaTotal = utilityFileSystem.transformedSize(userProfile.quotaTotal)
                     }
                     completion(LockscreenData(date: Date(), isPlaceholder: false, activity: "", link: URL(string: "https://")!, quotaRelative: quotaRelative, quotaUsed: quotaUsed, quotaTotal: quotaTotal, error: false))
                 } else {

+ 1 - 0
Widget/Widget-Brinding-header.h

@@ -3,3 +3,4 @@
 //
 
 #import "CCUtility.h"
+#import "UIImage+animatedGIF.h"

+ 15 - 0
WidgetDashboardIntentHandler/IntentHandler.swift

@@ -5,6 +5,21 @@
 //  Created by Marino Faggiana on 08/10/22.
 //  Copyright © 2022 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 Intents
 import RealmSwift

+ 4 - 4
iOSClient/Account Request/NCAccountRequest.swift

@@ -48,7 +48,7 @@ class NCAccountRequest: UIViewController {
     public var enableAddAccount: Bool = false
     public var dismissDidEnterBackground: Bool = false
     public weak var delegate: NCAccountRequestDelegate?
-
+    let utility = NCUtility()
     private var timer: Timer?
     private var time: Float = 0
     private let secondsAutoDismiss: Float = 3
@@ -201,7 +201,7 @@ extension NCAccountRequest: UITableViewDataSource {
 
         if indexPath.row == accounts.count {
 
-            avatarImage?.image = NCUtility.shared.loadImage(named: "plus").image(color: .systemBlue, size: 15)
+            avatarImage?.image = utility.loadImage(named: "plus").image(color: .systemBlue, size: 15)
             avatarImage?.contentMode = .center
             userLabel?.text = NSLocalizedString("_add_account_", comment: "")
             userLabel?.textColor = .systemBlue
@@ -211,7 +211,7 @@ extension NCAccountRequest: UITableViewDataSource {
 
             let account = accounts[indexPath.row]
 
-            avatarImage?.image = NCUtility.shared.loadUserImage(
+            avatarImage?.image = utility.loadUserImage(
                 for: account.user,
                    displayName: account.displayName,
                    userBaseUrl: account)
@@ -224,7 +224,7 @@ extension NCAccountRequest: UITableViewDataSource {
             }
 
             if account.active {
-                activeImage?.image = NCUtility.shared.loadImage(named: "checkmark").image(color: .systemBlue, size: 30)
+                activeImage?.image = utility.loadImage(named: "checkmark").image(color: .systemBlue, size: 30)
             } else {
                 activeImage?.image = nil
             }

+ 14 - 15
iOSClient/Activity/NCActivity.swift

@@ -39,10 +39,9 @@ class NCActivity: UIViewController, NCSharePagingContent {
     var metadata: tableMetadata?
     var showComments: Bool = false
 
-    // swiftlint:disable force_cast
-    private let appDelegate = UIApplication.shared.delegate as! AppDelegate
-    // swiftlint:enable force_cast
-
+    let appDelegate = (UIApplication.shared.delegate as? AppDelegate)!
+    let utilityFileSystem = NCUtilityFileSystem()
+    let utility = NCUtility()
     var allItems: [DateCompareable] = []
     var sectionDates: [Date] = []
 
@@ -90,7 +89,7 @@ class NCActivity: UIViewController, NCSharePagingContent {
                     self.commentView?.newCommentField.text?.removeAll()
                     self.loadComments()
                 } else {
-                    NCContentPresenter.shared.showError(error: error)
+                    NCContentPresenter().showError(error: error)
                 }
             }
         }
@@ -160,7 +159,7 @@ extension NCActivity: UITableViewDelegate {
         let label = UILabel()
         label.font = UIFont.boldSystemFont(ofSize: 13)
         label.textColor = .label
-        label.text = CCUtility.getTitleSectionDate(sectionDates[section])
+        label.text = utility.getTitleFromDate(sectionDates[section])
         label.textAlignment = .center
         label.layer.cornerRadius = 11
         label.layer.masksToBounds = true
@@ -212,12 +211,12 @@ extension NCActivity: UITableViewDataSource {
 
         // Image
         let fileName = appDelegate.userBaseUrl + "-" + comment.actorId + ".png"
-        NCOperationQueue.shared.downloadAvatar(user: comment.actorId, dispalyName: comment.actorDisplayName, fileName: fileName, cell: cell, view: tableView, cellImageView: cell.fileAvatarImageView)
+        NCNetworking.shared.downloadAvatar(user: comment.actorId, dispalyName: comment.actorDisplayName, fileName: fileName, cell: cell, view: tableView, cellImageView: cell.fileAvatarImageView)
         // Username
         cell.labelUser.text = comment.actorDisplayName
         cell.labelUser.textColor = .label
         // Date
-        cell.labelDate.text = CCUtility.dateDiff(comment.creationDateTime as Date)
+        cell.labelDate.text = utility.dateDiff(comment.creationDateTime as Date)
         cell.labelDate.textColor = .systemGray4
         // Message
         cell.labelMessage.text = comment.message
@@ -252,7 +251,7 @@ extension NCActivity: UITableViewDataSource {
         if !activity.icon.isEmpty {
 
             let fileNameIcon = (activity.icon as NSString).lastPathComponent
-            let fileNameLocalPath = CCUtility.getDirectoryUserData() + "/" + fileNameIcon
+            let fileNameLocalPath = utilityFileSystem.directoryUserData + "/" + fileNameIcon
 
             if FileManager.default.fileExists(atPath: fileNameLocalPath) {
                 if let image = UIImage(contentsOfFile: fileNameLocalPath) {
@@ -279,7 +278,7 @@ extension NCActivity: UITableViewDataSource {
 
             let fileName = appDelegate.userBaseUrl + "-" + activity.user + ".png"
 
-            NCOperationQueue.shared.downloadAvatar(user: activity.user, dispalyName: nil, fileName: fileName, cell: cell, view: tableView, cellImageView: cell.fileAvatarImageView)
+            NCNetworking.shared.downloadAvatar(user: activity.user, dispalyName: nil, fileName: fileName, cell: cell, view: tableView, cellImageView: cell.fileAvatarImageView)
         }
 
         // subject
@@ -312,7 +311,7 @@ extension NCActivity: UITableViewDataSource {
                 $0.color = UIColor.lightGray
             }
 
-            subject += "\n" + "<date>" + CCUtility.dateDiff(activity.date as Date) + "</date>"
+            subject += "\n" + "<date>" + utility.dateDiff(activity.date as Date) + "</date>"
             cell.subject.attributedText = subject.set(style: StyleGroup(base: normal, ["bold": bold, "date": date]))
         }
 
@@ -404,7 +403,7 @@ extension NCActivity {
             if error == .success, let comments = comments {
                 NCManageDatabase.shared.addComments(comments, account: metadata.account, objectId: metadata.fileId)
             } else if error.errorCode != NCGlobal.shared.errorResourceNotFound {
-                NCContentPresenter.shared.showError(error: error)
+                NCContentPresenter().showError(error: error)
             }
 
             if let disptachGroup = disptachGroup {
@@ -515,7 +514,7 @@ extension NCActivity: NCShareCommentsCellDelegate {
                             if error == .success {
                                 self.loadComments()
                             } else {
-                                NCContentPresenter.shared.showError(error: error)
+                                NCContentPresenter().showError(error: error)
                             }
                         }
                     }))
@@ -528,7 +527,7 @@ extension NCActivity: NCShareCommentsCellDelegate {
         actions.append(
             NCMenuAction(
                 title: NSLocalizedString("_delete_comment_", comment: ""),
-                icon: NCUtility.shared.loadImage(named: "trash"),
+                icon: utility.loadImage(named: "trash"),
                 action: { _ in
                     guard let metadata = self.metadata, let tableComments = tableComments else { return }
 
@@ -536,7 +535,7 @@ extension NCActivity: NCShareCommentsCellDelegate {
                         if error == .success {
                             self.loadComments()
                         } else {
-                            NCContentPresenter.shared.showError(error: error)
+                            NCContentPresenter().showError(error: error)
                         }
                     }
                 }

+ 1 - 1
iOSClient/Activity/NCActivityCommentView.swift

@@ -36,7 +36,7 @@ class NCActivityCommentView: UIView, UITextFieldDelegate {
         newCommentField.delegate = self
 
         let fileName = urlBase.userBaseUrl + "-" + urlBase.user + ".png"
-        let fileNameLocalPath = String(CCUtility.getDirectoryUserData()) + "/" + fileName
+        let fileNameLocalPath = NCUtilityFileSystem().directoryUserData + "/" + fileName
         if let image = UIImage(contentsOfFile: fileNameLocalPath) {
             imageItem.image = image
         } else {

+ 60 - 9
iOSClient/Activity/NCActivityTableViewCell.swift

@@ -25,6 +25,7 @@ import Foundation
 import NextcloudKit
 import FloatingPanel
 import JGProgressHUD
+import Queuer
 
 class NCActivityCollectionViewCell: UICollectionViewCell {
 
@@ -40,10 +41,6 @@ class NCActivityCollectionViewCell: UICollectionViewCell {
 
 class NCActivityTableViewCell: UITableViewCell, NCCellProtocol {
 
-    // swiftlint:disable force_cast
-    private let appDelegate = UIApplication.shared.delegate as! AppDelegate
-    // swiftlint:enable force_cast
-
     @IBOutlet weak var collectionView: UICollectionView!
     @IBOutlet weak var icon: UIImageView!
     @IBOutlet weak var avatar: UIImageView!
@@ -51,6 +48,7 @@ class NCActivityTableViewCell: UITableViewCell, NCCellProtocol {
     @IBOutlet weak var subjectTrailingConstraint: NSLayoutConstraint!
     @IBOutlet weak var collectionViewHeightConstraint: NSLayoutConstraint!
 
+    private let appDelegate = (UIApplication.shared.delegate as? AppDelegate)!
     private var user: String = ""
     private var index = IndexPath()
 
@@ -58,6 +56,7 @@ class NCActivityTableViewCell: UITableViewCell, NCCellProtocol {
     var activityPreviews: [tableActivityPreview] = []
     var didSelectItemEnable: Bool = true
     var viewController = UIViewController()
+    let utility = NCUtility()
 
     var indexPath: IndexPath {
         get { return index }
@@ -116,7 +115,7 @@ extension NCActivityTableViewCell: UICollectionViewDelegate {
                         (responder as? UIViewController)!.navigationController?.pushViewController(viewController, animated: true)
                     } else {
                         let error = NKError(errorCode: NCGlobal.shared.errorInternalError, errorDescription: "_trash_file_not_found_")
-                        NCContentPresenter.shared.showError(error: error)
+                        NCContentPresenter().showError(error: error)
                     }
                 }
             }
@@ -163,7 +162,7 @@ extension NCActivityTableViewCell: UICollectionViewDataSource {
 
             let source = activityPreview.source
 
-            NCUtility.shared.convertSVGtoPNGWriteToUserData(svgUrlString: source, width: 100, rewrite: false, account: appDelegate.account, id: idActivity) { imageNamePath, id in
+            utility.convertSVGtoPNGWriteToUserData(svgUrlString: source, width: 100, rewrite: false, account: appDelegate.account, id: idActivity) { imageNamePath, id in
                 if let imageNamePath = imageNamePath, id == self.idActivity, let image = UIImage(contentsOfFile: imageNamePath) {
                     cell.imageView.image = image
                 } else {
@@ -177,7 +176,7 @@ extension NCActivityTableViewCell: UICollectionViewDataSource {
 
                 let source = activityPreview.source
 
-                NCUtility.shared.convertSVGtoPNGWriteToUserData(svgUrlString: source, width: 100, rewrite: false, account: appDelegate.account, id: idActivity) { imageNamePath, id in
+                utility.convertSVGtoPNGWriteToUserData(svgUrlString: source, width: 100, rewrite: false, account: appDelegate.account, id: idActivity) { imageNamePath, id in
                     if let imageNamePath = imageNamePath, id == self.idActivity, let image = UIImage(contentsOfFile: imageNamePath) {
                         cell.imageView.image = image
                     } else {
@@ -189,12 +188,18 @@ extension NCActivityTableViewCell: UICollectionViewDataSource {
 
                 if let activitySubjectRich = NCManageDatabase.shared.getActivitySubjectRich(account: activityPreview.account, idActivity: idActivity, id: fileId) {
 
-                    let fileNamePath = CCUtility.getDirectoryUserData() + "/" + activitySubjectRich.name
+                    let fileNamePath = NCUtilityFileSystem().directoryUserData + "/" + activitySubjectRich.name
 
                     if FileManager.default.fileExists(atPath: fileNamePath), let image = UIImage(contentsOfFile: fileNamePath) {
                         cell.imageView.image = image
                     } else {
-                        NCOperationQueue.shared.downloadThumbnailActivity(fileNamePathOrFileId: activityPreview.source, fileNamePreviewLocalPath: fileNamePath, fileId: fileId, cell: cell, collectionView: collectionView)
+                        cell.imageView?.image = UIImage(named: "file_photo")
+                        cell.fileId = fileId
+                        if !FileManager.default.fileExists(atPath: fileNamePath) {
+                            if appDelegate.downloadThumbnailActivityQueue.operations.filter({ ($0 as? NCOperationDownloadThumbnailActivity)?.fileId == fileId }).isEmpty {
+                                appDelegate.downloadThumbnailActivityQueue.addOperation(NCOperationDownloadThumbnailActivity(fileNamePathOrFileId: activityPreview.source, fileNamePreviewLocalPath: fileNamePath, fileId: fileId, cell: cell, collectionView: collectionView))
+                            }
+                        }
                     }
                 }
             }
@@ -219,3 +224,49 @@ extension NCActivityTableViewCell: UICollectionViewDelegateFlowLayout {
         return UIEdgeInsets(top: 0, left: 0, bottom: 10, right: 0)
     }
 }
+
+class NCOperationDownloadThumbnailActivity: ConcurrentOperation {
+
+    var cell: NCActivityCollectionViewCell?
+    var collectionView: UICollectionView?
+    var fileNamePathOrFileId: String
+    var fileNamePreviewLocalPath: String
+    var fileId: String
+
+    init(fileNamePathOrFileId: String, fileNamePreviewLocalPath: String, fileId: String, cell: NCActivityCollectionViewCell?, collectionView: UICollectionView?) {
+        self.fileNamePathOrFileId = fileNamePathOrFileId
+        self.fileNamePreviewLocalPath = fileNamePreviewLocalPath
+        self.fileId = fileId
+        self.cell = cell
+        self.collectionView = collectionView
+    }
+
+    override func start() {
+
+        guard !isCancelled else { return self.finish() }
+
+        NextcloudKit.shared.downloadPreview(fileNamePathOrFileId: fileNamePathOrFileId,
+                                            fileNamePreviewLocalPath: fileNamePreviewLocalPath,
+                                            widthPreview: 0,
+                                            heightPreview: 0,
+                                            etag: nil,
+                                            useInternalEndpoint: false,
+                                            options: NKRequestOptions(queue: NextcloudKit.shared.nkCommonInstance.backgroundQueue)) { _, imagePreview, _, _, _, error in
+
+            if error == .success, let imagePreview = imagePreview {
+                DispatchQueue.main.async {
+                    if self.fileId == self.cell?.fileId, let imageView = self.cell?.imageView {
+                        UIView.transition(with: imageView,
+                                          duration: 0.75,
+                                          options: .transitionCrossDissolve,
+                                          animations: { imageView.image = imagePreview },
+                                          completion: nil)
+                    } else {
+                        self.collectionView?.reloadData()
+                    }
+                }
+            }
+            self.finish()
+        }
+    }
+}

+ 227 - 95
iOSClient/AppDelegate.swift

@@ -28,6 +28,7 @@ import TOPasscodeViewController
 import LocalAuthentication
 import Firebase
 import WidgetKit
+import Queuer
 
 @UIApplicationMain
 class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterDelegate, TOPasscodeViewControllerDelegate, NCAccountRequestDelegate, NCViewCertificateDetailsDelegate, NCUserBaseUrl {
@@ -52,32 +53,41 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
     var disableSharesView: Bool = false
     var documentPickerViewController: NCDocumentPickerViewController?
     var timerErrorNetworking: Timer?
+    private var privacyProtectionWindow: UIWindow?
+
+    let downloadQueue = Queuer(name: "downloadQueue", maxConcurrentOperationCount: NCGlobal.shared.maxConcurrentOperationCountDownload, qualityOfService: .default)
+    let downloadThumbnailQueue = Queuer(name: "downloadThumbnailQueue", maxConcurrentOperationCount: 10, qualityOfService: .default)
+    let downloadThumbnailActivityQueue = Queuer(name: "downloadThumbnailActivityQueue", maxConcurrentOperationCount: 10, qualityOfService: .default)
+    let downloadAvatarQueue = Queuer(name: "downloadAvatarQueue", maxConcurrentOperationCount: 10, qualityOfService: .default)
+    let unifiedSearchQueue = Queuer(name: "unifiedSearchQueue", maxConcurrentOperationCount: 1, qualityOfService: .default)
+    let saveLivePhotoQueue = Queuer(name: "saveLivePhotoQueue", maxConcurrentOperationCount: 1, qualityOfService: .default)
 
     var isAppRefresh: Bool = false
     var isAppProcessing: Bool = false
 
-    private var privacyProtectionWindow: UIWindow?
-
     var isUiTestingEnabled: Bool {
         return ProcessInfo.processInfo.arguments.contains("UI_TESTING")
-     }
+    }
 
     func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
         if isUiTestingEnabled {
             deleteAllAccounts()
         }
+        let utilityFileSystem = NCUtilityFileSystem()
+        let utility = NCUtility()
 
         NCSettingsBundleHelper.checkAndExecuteSettings(delay: 0)
 
-        let versionNextcloudiOS = String(format: NCBrandOptions.shared.textCopyrightNextcloudiOS, NCUtility.shared.getVersionApp())
+        let versionNextcloudiOS = String(format: NCBrandOptions.shared.textCopyrightNextcloudiOS, utility.getVersionApp())
 
         UserDefaults.standard.register(defaults: ["UserAgent": userAgent])
-        if !CCUtility.getDisableCrashservice() && !NCBrandOptions.shared.disable_crash_service {
+        if !NCKeychain().disableCrashservice, !NCBrandOptions.shared.disable_crash_service {
             FirebaseApp.configure()
         }
 
-        CCUtility.createDirectoryStandard()
-        CCUtility.emptyTemporaryDirectory()
+        utilityFileSystem.createDirectoryStandard()
+        utilityFileSystem.emptyTemporaryDirectory()
+        utilityFileSystem.clearCacheDirectory("com.limit-point.LivePhoto")
 
         // Activated singleton
         _ = NCActionCenter.shared
@@ -89,18 +99,16 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
         startTimerErrorNetworking()
 
         var levelLog = 0
-        if let pathDirectoryGroup = CCUtility.getDirectoryGroup()?.path {
-            NextcloudKit.shared.nkCommonInstance.pathLog = pathDirectoryGroup
-        }
+        NextcloudKit.shared.nkCommonInstance.pathLog = utilityFileSystem.directoryGroup
 
         if NCBrandOptions.shared.disable_log {
 
-            NCUtilityFileSystem.shared.deleteFile(filePath: NextcloudKit.shared.nkCommonInstance.filenamePathLog)
-            NCUtilityFileSystem.shared.deleteFile(filePath: NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true).first! + "/" + NextcloudKit.shared.nkCommonInstance.filenameLog)
+            utilityFileSystem.removeFile(atPath: NextcloudKit.shared.nkCommonInstance.filenamePathLog)
+            utilityFileSystem.removeFile(atPath: NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true).first! + "/" + NextcloudKit.shared.nkCommonInstance.filenameLog)
 
         } else {
 
-            levelLog = CCUtility.getLogLevel()
+            levelLog = NCKeychain().logLevel
             NextcloudKit.shared.nkCommonInstance.levelLog = levelLog
             NextcloudKit.shared.nkCommonInstance.copyLogToDocumentDirectory = true
             NextcloudKit.shared.nkCommonInstance.writeLog("[INFO] Start session with level \(levelLog) " + versionNextcloudiOS)
@@ -108,7 +116,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
 
         if let account = NCManageDatabase.shared.getActiveAccount() {
             NextcloudKit.shared.nkCommonInstance.writeLog("Account active \(account.account)")
-            if CCUtility.getPassword(account.account).isEmpty {
+            if NCKeychain().getPassword(account: account.account).isEmpty {
                 NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] PASSWORD NOT FOUND for \(account.account)")
             }
         }
@@ -119,7 +127,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
             urlBase = activeAccount.urlBase
             user = activeAccount.user
             userId = activeAccount.userId
-            password = CCUtility.getPassword(account)
+            password = NCKeychain().getPassword(account: account)
 
             NextcloudKit.shared.setup(account: account, user: user, userId: userId, password: password, urlBase: urlBase)
             NCManageDatabase.shared.setCapabilities(account: account)
@@ -128,22 +136,21 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
 
         } else {
 
-            CCUtility.deleteAllChainStore()
+            NCKeychain().removeAll()
             if let bundleID = Bundle.main.bundleIdentifier {
                 UserDefaults.standard.removePersistentDomain(forName: bundleID)
             }
-
-            NCBrandColor.shared.createImagesThemingColor()
         }
 
         NCBrandColor.shared.createUserColors()
+        NCImageCache.shared.createImagesCache()
 
         // Push Notification & display notification
         application.registerForRemoteNotifications()
         UNUserNotificationCenter.current().delegate = self
         UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .badge, .sound]) { _, _ in }
 
-        if !NCUtility.shared.isSimulatorOrTestFlight() {
+        if !utility.isSimulatorOrTestFlight() {
             let review = NCStoreReview()
             review.incrementAppRuns()
             review.showStoreReview()
@@ -159,12 +166,12 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
 
         // Intro
         if NCBrandOptions.shared.disable_intro {
-            CCUtility.setIntro(true)
+            NCKeychain().intro = true
             if account.isEmpty {
                 openLogin(viewController: nil, selector: NCGlobal.shared.introLogin, openLoginWeb: false)
             }
         } else {
-            if !CCUtility.getIntro() {
+            if !NCKeychain().intro {
                 if let viewController = UIStoryboard(name: "NCIntro", bundle: nil).instantiateInitialViewController() {
                     let navigationController = NCLoginNavigationController(rootViewController: viewController)
                     window?.rootViewController = navigationController
@@ -187,6 +194,8 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
 
         NextcloudKit.shared.nkCommonInstance.writeLog("[INFO] Application did become active")
 
+        DispatchQueue.global().async { NCImageCache.shared.createMediaCache(account: self.account) }
+
         NCSettingsBundleHelper.setVersionAndBuildNumber()
         NCSettingsBundleHelper.checkAndExecuteSettings(delay: 0.5)
 
@@ -194,11 +203,11 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
         NCNetworkingProcessUpload.shared.observeTableMetadata()
         NCNetworkingProcessUpload.shared.startTimer()
 
-        if !NCAskAuthorization.shared.isRequesting {
+        if !NCAskAuthorization().isRequesting {
             hidePrivacyProtectionWindow()
         }
 
-        NCService.shared.startRequestServicesServer()
+        NCService().startRequestServicesServer()
 
         NCAutoUpload.shared.initAutoUpload(viewController: nil) { items in
             NextcloudKit.shared.nkCommonInstance.writeLog("[INFO] Initialize Auto upload with \(items) uploads")
@@ -218,7 +227,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
         NCNetworkingProcessUpload.shared.invalidateObserveTableMetadata()
         NCNetworkingProcessUpload.shared.stopTimer()
 
-        if CCUtility.getPrivacyScreenEnabled() {
+        if NCKeychain().privacyScreenEnabled {
             showPrivacyProtectionWindow()
         }
 
@@ -226,10 +235,9 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
         WidgetCenter.shared.reloadAllTimelines()
 
         // Clear older files
-        let days = CCUtility.getCleanUpDay()
-        if let directory = CCUtility.getDirectoryProviderStorage() {
-            NCUtilityFileSystem.shared.cleanUp(directory: directory, days: TimeInterval(days))
-        }
+        let days = NCKeychain().cleanUpDay
+        let utilityFileSystem = NCUtilityFileSystem()
+        utilityFileSystem.cleanUp(directory: utilityFileSystem.directoryProviderStorage, days: TimeInterval(days))
 
         NotificationCenter.default.postOnMainThread(name: NCGlobal.shared.notificationCenterApplicationWillResignActive)
     }
@@ -253,13 +261,27 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
         NextcloudKit.shared.nkCommonInstance.writeLog("[INFO] Application did enter in background")
 
         guard !account.isEmpty else { return }
+        let activeAccount = NCManageDatabase.shared.getActiveAccount()
+
+        if let autoUpload = activeAccount?.autoUpload, autoUpload {
+            NextcloudKit.shared.nkCommonInstance.writeLog("- Auto upload: true")
+            if UIApplication.shared.backgroundRefreshStatus == .available {
+                NextcloudKit.shared.nkCommonInstance.writeLog("- Auto upload in background: true")
+            } else {
+                NextcloudKit.shared.nkCommonInstance.writeLog("- Auto upload in background: false")
+            }
+        } else {
+            NextcloudKit.shared.nkCommonInstance.writeLog("- Auto upload: false")
+        }
 
         if let error = updateShareAccounts() {
             NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] Create share accounts \(error.localizedDescription)")
         }
 
-        NCNetworking.shared.cancelSessions(inBackground: false)
-
+        scheduleAppRefresh()
+        scheduleAppProcessing()
+        NCNetworking.shared.cancelDataTask()
+        NCNetworking.shared.cancelDownloadTasks()
         presentPasscode { }
 
         NotificationCenter.default.postOnMainThread(name: NCGlobal.shared.notificationCenterApplicationDidEnterBackground)
@@ -268,8 +290,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
     // L'applicazione terminerà
     func applicationWillTerminate(_ application: UIApplication) {
 
-        NCNetworking.shared.cancelSessions(inBackground: false)
-
         if UIApplication.shared.backgroundRefreshStatus == .available {
 
             let content = UNMutableNotificationContent()
@@ -295,6 +315,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
         request.earliestBeginDate = Date(timeIntervalSinceNow: 60) // Refresh after 60 seconds.
         do {
             try BGTaskScheduler.shared.submit(request)
+            NextcloudKit.shared.nkCommonInstance.writeLog("- Refresh task: ok")
         } catch {
             NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] Refresh task failed to submit request: \(error)")
         }
@@ -312,6 +333,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
         request.requiresExternalPower = false
         do {
             try BGTaskScheduler.shared.submit(request)
+            NextcloudKit.shared.nkCommonInstance.writeLog("- Processing task: ok")
         } catch {
             NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] Background Processing task failed to submit request: \(error)")
         }
@@ -320,12 +342,12 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
     func handleAppRefresh(_ task: BGTask) {
         scheduleAppRefresh()
 
-        guard !account.isEmpty, !isAppProcessing else {
-            return task.setTaskCompleted(success: true)
+        if isAppProcessing {
+            NextcloudKit.shared.nkCommonInstance.writeLog("[INFO] Processing task already in progress, abort.")
+            task.setTaskCompleted(success: true)
+            return
         }
-
         isAppRefresh = true
-        NextcloudKit.shared.setup(delegate: NCNetworking.shared)
 
         NCAutoUpload.shared.initAutoUpload(viewController: nil) { items in
             NextcloudKit.shared.nkCommonInstance.writeLog("[INFO] Refresh task auto upload with \(items) uploads")
@@ -340,12 +362,12 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
     func handleProcessingTask(_ task: BGTask) {
         scheduleAppProcessing()
 
-        guard !account.isEmpty, !isAppRefresh else {
-            return task.setTaskCompleted(success: true)
+        if isAppRefresh {
+            NextcloudKit.shared.nkCommonInstance.writeLog("[INFO] Refresh task already in progress, abort.")
+            task.setTaskCompleted(success: true)
+            return
         }
-
         isAppProcessing = true
-        NextcloudKit.shared.setup(delegate: NCNetworking.shared)
 
         NCAutoUpload.shared.initAutoUpload(viewController: nil) { items in
             NextcloudKit.shared.nkCommonInstance.writeLog("[INFO] Processing task auto upload with \(items) uploads")
@@ -518,7 +540,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
     }
 
     @objc private func checkErrorNetworking() {
-        guard !account.isEmpty, CCUtility.getPassword(account)!.isEmpty else { return }
+        guard !account.isEmpty, NCKeychain().getPassword(account: account).isEmpty else { return }
         openLogin(viewController: window?.rootViewController, selector: NCGlobal.shared.introLogin, openLoginWeb: true)
     }
 
@@ -530,7 +552,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
               host == currentHost
         else { return }
 
-        let certificateHostSavedPath = CCUtility.getDirectoryCerificates()! + "/" + host + ".der"
+        let certificateHostSavedPath = NCUtilityFileSystem().directoryCertificates + "/" + host + ".der"
         var title = NSLocalizedString("_ssl_certificate_changed_", comment: "")
 
         if !FileManager.default.fileExists(atPath: certificateHostSavedPath) {
@@ -565,15 +587,17 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
 
     @objc func changeAccount(_ account: String, userProfile: NKUserProfile?) {
 
-        guard let tableAccount = NCManageDatabase.shared.setAccountActive(account) else { return }
+        NCNetworking.shared.cancelDataTask()
+        NCNetworking.shared.cancelDownloadTasks()
+        NCNetworking.shared.cancelUploadTasks()
 
-        NCNetworking.shared.cancelSessions(inBackground: false)
+        guard let tableAccount = NCManageDatabase.shared.setAccountActive(account) else { return }
 
         self.account = tableAccount.account
         self.urlBase = tableAccount.urlBase
         self.user = tableAccount.user
         self.userId = tableAccount.userId
-        self.password = CCUtility.getPassword(tableAccount.account)
+        self.password = NCKeychain().getPassword(account: tableAccount.account)
 
         NextcloudKit.shared.setup(account: account, user: user, userId: userId, password: password, urlBase: urlBase)
         NCManageDatabase.shared.setCapabilities(account: account)
@@ -582,18 +606,16 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
             NCManageDatabase.shared.setAccountUserProfile(account: account, userProfile: userProfile)
         }
 
-        if NCGlobal.shared.capabilityServerVersionMajor > 0 {
-            NextcloudKit.shared.setup(nextcloudVersion: NCGlobal.shared.capabilityServerVersionMajor)
-        }
-
         NCPushNotification.shared().pushNotification()
 
-        NCService.shared.startRequestServicesServer()
+        NCService().startRequestServicesServer()
 
         NCAutoUpload.shared.initAutoUpload(viewController: nil) { items in
             NextcloudKit.shared.nkCommonInstance.writeLog("[INFO] Initialize Auto upload with \(items) uploads")
         }
 
+        DispatchQueue.global().async { NCImageCache.shared.createMediaCache(account: self.account) }
+
         NotificationCenter.default.postOnMainThread(name: NCGlobal.shared.notificationCenterChangeUser)
     }
 
@@ -604,14 +626,15 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
         }
 
         let results = NCManageDatabase.shared.getTableLocalFiles(predicate: NSPredicate(format: "account == %@", account), sorted: "ocId", ascending: false)
+        let utilityFileSystem = NCUtilityFileSystem()
         for result in results {
-            CCUtility.removeFile(atPath: CCUtility.getDirectoryProviderStorageOcId(result.ocId))
+            utilityFileSystem.removeFile(atPath: utilityFileSystem.getDirectoryProviderStorageOcId(result.ocId))
         }
         NCManageDatabase.shared.clearDatabase(account: account, removeAccount: true)
 
-        CCUtility.clearAllKeysEnd(toEnd: account)
-        CCUtility.clearAllKeysPushNotification(account)
-        CCUtility.setPassword(account, password: nil)
+        NCKeychain().clearAllKeysEndToEnd(account: account)
+        NCKeychain().clearAllKeysPushNotification(account: account)
+        NCKeychain().setPassword(account: account, password: nil)
 
         self.account = ""
         self.urlBase = ""
@@ -647,7 +670,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
             let name = account.alias.isEmpty ? account.displayName : account.alias
             let userBaseUrl = account.user + "-" + (URL(string: account.urlBase)?.host ?? "")
             let avatarFileName = userBaseUrl + "-\(account.user).png"
-            let pathAvatarFileName = String(CCUtility.getDirectoryUserData()) + "/" + avatarFileName
+            let pathAvatarFileName = NCUtilityFileSystem().directoryUserData + "/" + avatarFileName
             let image = UIImage(contentsOfFile: pathAvatarFileName)
             accounts.append(NKShareAccounts.DataAccounts(withUrl: account.urlBase, user: account.user, name: name, image: image))
         }
@@ -663,7 +686,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
     func requestAccount() {
 
         if isPasscodePresented() { return }
-        if !CCUtility.getAccountRequest() { return }
+        if !NCKeychain().accountRequest { return }
 
         let accounts = NCManageDatabase.shared.getAllAccount()
 
@@ -694,15 +717,23 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
 
     // MARK: - Passcode
 
+    var isPasscodeReset: Bool {
+        let passcodeCounterFailReset = NCKeychain().passcodeCounterFailReset
+        return NCKeychain().resetAppCounterFail && passcodeCounterFailReset >= NCBrandOptions.shared.resetAppPasscodeAttempts
+    }
+
+    var isPasscodeFail: Bool {
+        let passcodeCounterFail = NCKeychain().passcodeCounterFail
+        return passcodeCounterFail > 0 && passcodeCounterFail.isMultiple(of: 3)
+    }
+
     func presentPasscode(completion: @escaping () -> Void) {
 
-        let laContext = LAContext()
         var error: NSError?
-
         defer { self.requestAccount() }
 
         let presentedViewController = window?.rootViewController?.presentedViewController
-        guard !account.isEmpty, CCUtility.isPasscodeAtStartEnabled(), !(presentedViewController is NCLoginNavigationController) else { return }
+        guard !account.isEmpty, NCKeychain().passcode != nil, NCKeychain().requestPasscodeAtStart, !(presentedViewController is NCLoginNavigationController) else { return }
 
         // Make sure we have a privacy window (in case it's not enabled)
         showPrivacyProtectionWindow()
@@ -710,11 +741,11 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
         let passcodeViewController = TOPasscodeViewController(passcodeType: .sixDigits, allowCancel: false)
         passcodeViewController.delegate = self
         passcodeViewController.keypadButtonShowLettering = false
-        if CCUtility.getEnableTouchFaceID() && laContext.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: &error) {
+        if NCKeychain().touchFaceID, LAContext().canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: &error) {
             if error == nil {
-                if laContext.biometryType == .faceID {
+                if LAContext().biometryType == .faceID {
                     passcodeViewController.biometryType = .faceID
-                } else if laContext.biometryType == .touchID {
+                } else if LAContext().biometryType == .touchID {
                     passcodeViewController.biometryType = .touchID
                 }
                 passcodeViewController.allowBiometricValidation = true
@@ -724,6 +755,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
 
         // show passcode on top of privacy window
         privacyProtectionWindow?.rootViewController?.present(passcodeViewController, animated: true, completion: {
+            self.openAlert(passcodeViewController: passcodeViewController)
             completion()
         })
     }
@@ -734,20 +766,49 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
 
     func enableTouchFaceID() {
         guard !account.isEmpty,
-              CCUtility.getEnableTouchFaceID(),
-              CCUtility.isPasscodeAtStartEnabled(),
+              NCKeychain().touchFaceID,
+              NCKeychain().passcode != nil,
+              NCKeychain().requestPasscodeAtStart,
+              !isPasscodeFail,
+              !isPasscodeReset,
               let passcodeViewController = privacyProtectionWindow?.rootViewController?.presentedViewController as? TOPasscodeViewController
         else { return }
 
         DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
-            LAContext().evaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, localizedReason: NCBrandOptions.shared.brand) { success, _ in
+
+            LAContext().evaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, localizedReason: NCBrandOptions.shared.brand) { success, evaluateError in
                 if success {
                     DispatchQueue.main.async {
                         passcodeViewController.dismiss(animated: true) {
+                            NCKeychain().passcodeCounterFail = 0
+                            NCKeychain().passcodeCounterFailReset = 0
                             self.hidePrivacyProtectionWindow()
                             self.requestAccount()
                         }
                     }
+                } else {
+                    if let error = evaluateError {
+                        switch error._code {
+                        case LAError.userFallback.rawValue, LAError.authenticationFailed.rawValue:
+                            NCKeychain().passcodeCounterFail = 3
+                            NCKeychain().passcodeCounterFailReset += 1
+                            self.openAlert(passcodeViewController: passcodeViewController)
+                        case LAError.biometryLockout.rawValue:
+                            LAContext().evaluatePolicy(LAPolicy.deviceOwnerAuthentication, localizedReason: NSLocalizedString("_deviceOwnerAuthentication_", comment: ""), reply: { success, _ in
+                                if success {
+                                    DispatchQueue.main.async {
+                                        NCKeychain().passcodeCounterFail = 0
+                                        self.enableTouchFaceID()
+                                    }
+                                }
+                            })
+                        case LAError.userCancel.rawValue:
+                            NCKeychain().passcodeCounterFail += 1
+                            NCKeychain().passcodeCounterFailReset += 1
+                        default:
+                            break
+                        }
+                    }
                 }
             }
         }
@@ -756,6 +817,8 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
     func didInputCorrectPasscode(in passcodeViewController: TOPasscodeViewController) {
         DispatchQueue.main.async {
             passcodeViewController.dismiss(animated: true) {
+                NCKeychain().passcodeCounterFail = 0
+                NCKeychain().passcodeCounterFailReset = 0
                 self.hidePrivacyProtectionWindow()
                 self.requestAccount()
             }
@@ -763,13 +826,81 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
     }
 
     func passcodeViewController(_ passcodeViewController: TOPasscodeViewController, isCorrectCode code: String) -> Bool {
-        return code == CCUtility.getPasscode()
+        if code == NCKeychain().passcode {
+            return true
+        } else {
+            NCKeychain().passcodeCounterFail += 1
+            NCKeychain().passcodeCounterFailReset += 1
+            openAlert(passcodeViewController: passcodeViewController)
+            return false
+        }
     }
 
     func didPerformBiometricValidationRequest(in passcodeViewController: TOPasscodeViewController) {
         enableTouchFaceID()
     }
 
+    func openAlert(passcodeViewController: TOPasscodeViewController) {
+
+        DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
+
+            if self.isPasscodeReset {
+
+                passcodeViewController.setContentHidden(true, animated: true)
+
+                let alertController = UIAlertController(title: NSLocalizedString("_reset_wrong_passcode_", comment: ""), message: nil, preferredStyle: .alert)
+                passcodeViewController.present(alertController, animated: true, completion: { })
+
+                DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
+                    self.resetApplication()
+                }
+
+            } else if self.isPasscodeFail {
+
+                passcodeViewController.setContentHidden(true, animated: true)
+
+                let alertController = UIAlertController(title: NSLocalizedString("_passcode_counter_fail_", comment: ""), message: nil, preferredStyle: .alert)
+                passcodeViewController.present(alertController, animated: true, completion: { })
+
+                var seconds = NCBrandOptions.shared.passcodeSecondsFail
+                _ = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { timer in
+                    alertController.message = "\(seconds) " + NSLocalizedString("_seconds_", comment: "")
+                    seconds -= 1
+                    if seconds < 0 {
+                        timer.invalidate()
+                        alertController.dismiss(animated: true)
+                        passcodeViewController.setContentHidden(false, animated: true)
+                        NCKeychain().passcodeCounterFail = 0
+                        self.enableTouchFaceID()
+                    }
+                }
+            }
+        }
+    }
+
+    // MARK: - Reset Application
+
+    @objc func resetApplication() {
+
+        let utilityFileSystem = NCUtilityFileSystem()
+
+        NCNetworking.shared.cancelDataTask()
+        NCNetworking.shared.cancelDownloadTasks()
+        NCNetworking.shared.cancelUploadTasks()
+        NCNetworking.shared.cancelUploadBackgroundTask()
+
+        URLCache.shared.memoryCapacity = 0
+        URLCache.shared.diskCapacity = 0
+
+        utilityFileSystem.removeGroupDirectoryProviderStorage()
+        utilityFileSystem.removeGroupApplicationSupport()
+        utilityFileSystem.removeDocumentsDirectory()
+        utilityFileSystem.removeTemporaryDirectory()
+
+        NCKeychain().removeAll()
+        exit(0)
+    }
+
     // MARK: - Privacy Protection
 
     private func showPrivacyProtectionWindow() {
@@ -799,6 +930,17 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
         }
     }
 
+    // MARK: - Queue
+
+    @objc func cancelAllQueue() {
+        downloadQueue.cancelAll()
+        downloadThumbnailQueue.cancelAll()
+        downloadThumbnailActivityQueue.cancelAll()
+        downloadAvatarQueue.cancelAll()
+        unifiedSearchQueue.cancelAll()
+        saveLivePhotoQueue.cancelAll()
+    }
+
     // MARK: - Universal Links
 
     func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool {
@@ -817,8 +959,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
         var serverUrl: String = ""
 
         /*
-         Example:
-         nextcloud://open-action?action=create-voice-memo&&user=marinofaggiana&url=https://cloud.nextcloud.com
+         Example: nextcloud://open-action?action=create-voice-memo&&user=marinofaggiana&url=https://cloud.nextcloud.com
          */
 
         if !account.isEmpty && scheme == NCGlobal.shared.appScheme && action == "open-action" {
@@ -826,23 +967,23 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
             if let urlComponents = URLComponents(url: url, resolvingAgainstBaseURL: false) {
 
                 let queryItems = urlComponents.queryItems
-                guard let actionScheme = CCUtility.value(forKey: "action", fromQueryItems: queryItems), let rootViewController = window?.rootViewController else { return false }
-                guard let userScheme = CCUtility.value(forKey: "user", fromQueryItems: queryItems) else { return false }
-                guard let urlScheme = CCUtility.value(forKey: "url", fromQueryItems: queryItems) else { return false }
+                guard let actionScheme = queryItems?.filter({ $0.name == "action" }).first?.value,
+                      let userScheme = queryItems?.filter({ $0.name == "user" }).first?.value,
+                      let urlScheme = queryItems?.filter({ $0.name == "url" }).first?.value,
+                      let rootViewController = window?.rootViewController else { return false }
                 if getMatchedAccount(userId: userScheme, url: urlScheme) == nil {
                     let message = NSLocalizedString("_the_account_", comment: "") + " " + userScheme + NSLocalizedString("_of_", comment: "") + " " + urlScheme + " " + NSLocalizedString("_does_not_exist_", comment: "")
                     let alertController = UIAlertController(title: NSLocalizedString("_info_", comment: ""), message: message, preferredStyle: .alert)
                     alertController.addAction(UIAlertAction(title: NSLocalizedString("_ok_", comment: ""), style: .default, handler: { _ in }))
 
                     window?.rootViewController?.present(alertController, animated: true, completion: { })
-
                     return false
                 }
 
                 switch actionScheme {
                 case NCGlobal.shared.actionUploadAsset:
 
-                    NCAskAuthorization.shared.askAuthorizationPhotoLibrary(viewController: rootViewController) { hasPermission in
+                    NCAskAuthorization().askAuthorizationPhotoLibrary(viewController: rootViewController) { hasPermission in
                         if hasPermission {
                             NCPhotosPickerViewController(viewController: rootViewController, maxSelectedAssets: 0, singleSelectedMode: false)
                         }
@@ -871,9 +1012,9 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
 
                 case NCGlobal.shared.actionVoiceMemo:
 
-                    NCAskAuthorization.shared.askAuthorizationAudioRecord(viewController: rootViewController) { hasPermission in
+                    NCAskAuthorization().askAuthorizationAudioRecord(viewController: rootViewController) { hasPermission in
                         if hasPermission {
-                            let fileName = CCUtility.createFileNameDate(NSLocalizedString("_voice_memo_filename_", comment: ""), extension: "m4a")!
+                            let fileName = NCUtilityFileSystem().createFileNameDate(NSLocalizedString("_voice_memo_filename_", comment: ""), ext: "m4a")
                             if let viewController = UIStoryboard(name: "NCAudioRecorderViewController", bundle: nil).instantiateInitialViewController() as? NCAudioRecorderViewController {
 
                                 viewController.delegate = self
@@ -894,8 +1035,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
         }
 
         /*
-         Example:
-         nextcloud://open-file?path=Talk/IMG_0000123.jpg&user=marinofaggiana&link=https://cloud.nextcloud.com/f/123
+         Example: nextcloud://open-file?path=Talk/IMG_0000123.jpg&user=marinofaggiana&link=https://cloud.nextcloud.com/f/123
          */
 
         else if !account.isEmpty && scheme == NCGlobal.shared.appScheme && action == "open-file" {
@@ -903,19 +1043,18 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
             if let urlComponents = URLComponents(url: url, resolvingAgainstBaseURL: false) {
 
                 let queryItems = urlComponents.queryItems
-                guard let userScheme = CCUtility.value(forKey: "user", fromQueryItems: queryItems) else { return false }
-                guard let pathScheme = CCUtility.value(forKey: "path", fromQueryItems: queryItems) else { return false }
-                guard let linkScheme = CCUtility.value(forKey: "link", fromQueryItems: queryItems) else { return false }
+                guard let userScheme = queryItems?.filter({ $0.name == "user" }).first?.value,
+                      let pathScheme = queryItems?.filter({ $0.name == "path" }).first?.value,
+                      let linkScheme = queryItems?.filter({ $0.name == "link" }).first?.value else { return false}
+
                 guard let matchedAccount = getMatchedAccount(userId: userScheme, url: linkScheme) else {
                     guard let domain = URL(string: linkScheme)?.host else { return true }
                     fileName = (pathScheme as NSString).lastPathComponent
                     let message = String(format: NSLocalizedString("_account_not_available_", comment: ""), userScheme, domain, fileName)
-
                     let alertController = UIAlertController(title: NSLocalizedString("_info_", comment: ""), message: message, preferredStyle: .alert)
                     alertController.addAction(UIAlertAction(title: NSLocalizedString("_ok_", comment: ""), style: .default, handler: { _ in }))
 
                     window?.rootViewController?.present(alertController, animated: true, completion: { })
-
                     return false
                 }
 
@@ -935,22 +1074,18 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
             return true
 
         /*
-         Example:
-         nextcloud://open-and-switch-account?user=marinofaggiana&url=https://cloud.nextcloud.com
+         Example: nextcloud://open-and-switch-account?user=marinofaggiana&url=https://cloud.nextcloud.com
          */
 
         } else if !account.isEmpty && scheme == NCGlobal.shared.appScheme && action == "open-and-switch-account" {
             guard let urlComponents = URLComponents(url: url, resolvingAgainstBaseURL: false) else { return false }
             let queryItems = urlComponents.queryItems
-
-            guard let userScheme = CCUtility.value(forKey: "user", fromQueryItems: queryItems) else { return false }
-            guard let urlScheme = CCUtility.value(forKey: "url", fromQueryItems: queryItems) else { return false }
-
+            guard let userScheme = queryItems?.filter({ $0.name == "user" }).first?.value,
+                  let urlScheme = queryItems?.filter({ $0.name == "url" }).first?.value else { return false}
             // If the account doesn't exist, return false which will open the app without switching
             if getMatchedAccount(userId: userScheme, url: urlScheme) == nil {
                 return false
             }
-
             // Otherwise open the app and switch accounts
             return true
         } else {
@@ -992,17 +1127,14 @@ extension AppDelegate: NCAudioRecorderViewControllerDelegate {
 
     func didFinishRecording(_ viewController: NCAudioRecorderViewController, fileName: String) {
 
-        guard
-            let navigationController = UIStoryboard(name: "NCCreateFormUploadVoiceNote", bundle: nil).instantiateInitialViewController() as? UINavigationController,
-                let viewController = navigationController.topViewController as? NCCreateFormUploadVoiceNote
-        else { return }
+        guard let navigationController = UIStoryboard(name: "NCCreateFormUploadVoiceNote", bundle: nil).instantiateInitialViewController() as? UINavigationController,
+              let viewController = navigationController.topViewController as? NCCreateFormUploadVoiceNote else { return }
         navigationController.modalPresentationStyle = .formSheet
         viewController.setup(serverUrl: activeServerUrl, fileNamePath: NSTemporaryDirectory() + fileName, fileName: fileName)
         window?.rootViewController?.present(navigationController, animated: true)
     }
 
-    func didFinishWithoutRecording(_ viewController: NCAudioRecorderViewController, fileName: String) {
-    }
+    func didFinishWithoutRecording(_ viewController: NCAudioRecorderViewController, fileName: String) { }
 }
 
 extension AppDelegate: NCCreateFormUploadConflictDelegate {

+ 1 - 1
iOSClient/BrowserWeb/NCBrowserWeb.swift

@@ -55,7 +55,7 @@ class NCBrowserWeb: UIViewController {
             buttonExit.isHidden = true
         } else {
             self.view.bringSubviewToFront(buttonExit)
-            let image = NCUtility.shared.loadImage(named: "xmark", color: .systemBlue)
+            let image = NCUtility().loadImage(named: "xmark", color: .systemBlue)
             buttonExit.setImage(image, for: .normal)
         }
 

+ 16 - 1
iOSClient/Color/NCColorPicker.swift

@@ -5,6 +5,21 @@
 //  Created by Marino Faggiana on 24/07/22.
 //  Copyright © 2022 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 UIKit
@@ -50,7 +65,7 @@ class NCColorPicker: UIViewController {
             }
         }
 
-        closeButton.setImage(NCUtility.shared.loadImage(named: "xmark", color: .label), for: .normal)
+        closeButton.setImage(NCUtility().loadImage(named: "xmark", color: .label), for: .normal)
         titleLabel.text = NSLocalizedString("_select_color_", comment: "")
 
         orangeButton.backgroundColor = .orange

+ 9 - 3
iOSClient/Data/NCDataSource.swift

@@ -109,8 +109,11 @@ class NCDataSource: NSObject {
 
         // get all Section
         for metadata in self.metadatas {
-            // skipped livePhoto
-            if filterLivePhoto && metadata.livePhoto && (metadata.fileNameView as NSString).pathExtension.lowercased() == "mov" {
+            // skipped livePhoto VIDEO part
+            if filterLivePhoto && metadata.livePhoto && metadata.classFile == NKCommon.TypeClassFile.video.rawValue {
+                if let metadataImage = self.metadatas.filter({ $0.fileNoExtension == metadata.fileNoExtension && $0.classFile == NKCommon.TypeClassFile.image.rawValue }).first {
+                    NCLivePhoto().setLivePhoto(metadata1: metadataImage, metadata2: metadata)
+                }
                 continue
             }
             let section = NSLocalizedString(self.getSectionValue(metadata: metadata), comment: "")
@@ -429,7 +432,10 @@ class NCMetadataForSection: NSObject {
             }
 
             // skipped livePhoto
-            if filterLivePhoto && metadata.livePhoto && (metadata.fileNameView as NSString).pathExtension.lowercased() == "mov" {
+            if filterLivePhoto && metadata.livePhoto && metadata.classFile == NKCommon.TypeClassFile.video.rawValue {
+                if let metadataImage = self.metadatas.filter({ $0.fileNoExtension == metadata.fileNoExtension && $0.classFile == NKCommon.TypeClassFile.image.rawValue }).first {
+                    NCLivePhoto().setLivePhoto(metadata1: metadataImage, metadata2: metadata)
+                }
                 continue
             }
 

+ 0 - 157
iOSClient/Data/NCDatabase.swift

@@ -1,157 +0,0 @@
-//
-//  NCDatabase.swift
-//  Nextcloud
-//
-//  Created by Marino Faggiana on 06/05/17.
-//  Copyright © 2017 Marino Faggiana. All rights reserved.
-//
-//  Author Marino Faggiana <marino.faggiana@nextcloud.com>
-//  Author Henrik Storch <henrik.storch@nextcloud.com>
-//
-//  This program is free software: you can redistribute it and/or modify
-//  it under the terms of the GNU General Public License as published by
-//  the Free Software Foundation, either version 3 of the License, or
-//  (at your option) any later version.
-//
-//  This program is distributed in the hope that it will be useful,
-//  but WITHOUT ANY WARRANTY; without even the implied warranty of
-//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-//  GNU General Public License for more details.
-//
-//  You should have received a copy of the GNU General Public License
-//  along with this program.  If not, see <http://www.gnu.org/licenses/>.
-//
-
-import UIKit
-import RealmSwift
-import NextcloudKit
-
-protocol DateCompareable {
-    var dateKey: Date { get }
-}
-
-class tableComments: Object, DateCompareable {
-    var dateKey: Date { creationDateTime as Date }
-
-    @objc dynamic var account = ""
-    @objc dynamic var actorDisplayName = ""
-    @objc dynamic var actorId = ""
-    @objc dynamic var actorType = ""
-    @objc dynamic var creationDateTime = NSDate()
-    @objc dynamic var isUnread: Bool = false
-    @objc dynamic var message = ""
-    @objc dynamic var messageId = ""
-    @objc dynamic var objectId = ""
-    @objc dynamic var objectType = ""
-    @objc dynamic var path = ""
-    @objc dynamic var verb = ""
-
-    override static func primaryKey() -> String {
-        return "messageId"
-    }
-}
-
-class tableDirectEditingCreators: Object {
-
-    @objc dynamic var account = ""
-    @objc dynamic var editor = ""
-    @objc dynamic var ext = ""
-    @objc dynamic var identifier = ""
-    @objc dynamic var mimetype = ""
-    @objc dynamic var name = ""
-    @objc dynamic var templates: Int = 0
-}
-
-class tableDirectEditingEditors: Object {
-
-    @objc dynamic var account = ""
-    @objc dynamic var editor = ""
-    let mimetypes = List<String>()
-    @objc dynamic var name = ""
-    let optionalMimetypes = List<String>()
-    @objc dynamic var secure: Int = 0
-}
-
-class tableExternalSites: Object {
-
-    @objc dynamic var account = ""
-    @objc dynamic var icon = ""
-    @objc dynamic var idExternalSite: Int = 0
-    @objc dynamic var lang = ""
-    @objc dynamic var name = ""
-    @objc dynamic var type = ""
-    @objc dynamic var url = ""
-}
-
-typealias tableGPS = tableGPSV2
-class tableGPSV2: Object {
-    @objc dynamic var latitude: Double = 0
-    @objc dynamic var longitude: Double = 0
-    @objc dynamic var location = ""
-}
-
-class tablePhotoLibrary: Object {
-
-    @objc dynamic var account = ""
-    @objc dynamic var assetLocalIdentifier = ""
-    @objc dynamic var creationDate: NSDate?
-    @objc dynamic var idAsset = ""
-    @objc dynamic var modificationDate: NSDate?
-    @objc dynamic var mediaType: Int = 0
-
-    override static func primaryKey() -> String {
-        return "idAsset"
-    }
-}
-
-class tableTag: Object {
-
-    @objc dynamic var account = ""
-    @objc dynamic var ocId = ""
-    @objc dynamic var tagIOS: Data?
-
-    override static func primaryKey() -> String {
-        return "ocId"
-    }
-}
-
-class tableTip: Object {
-
-    @Persisted(primaryKey: true) var tipName = ""
-}
-
-class tableTrash: Object {
-
-    @objc dynamic var account = ""
-    @objc dynamic var classFile = ""
-    @objc dynamic var contentType = ""
-    @objc dynamic var date = NSDate()
-    @objc dynamic var directory: Bool = false
-    @objc dynamic var fileId = ""
-    @objc dynamic var fileName = ""
-    @objc dynamic var filePath = ""
-    @objc dynamic var hasPreview: Bool = false
-    @objc dynamic var iconName = ""
-    @objc dynamic var size: Int64 = 0
-    @objc dynamic var trashbinFileName = ""
-    @objc dynamic var trashbinOriginalLocation = ""
-    @objc dynamic var trashbinDeletionTime = NSDate()
-
-    override static func primaryKey() -> String {
-        return "fileId"
-    }
-}
-
-class tableUserStatus: Object {
-
-    @objc dynamic var account = ""
-    @objc dynamic var clearAt: NSDate?
-    @objc dynamic var clearAtTime: String?
-    @objc dynamic var clearAtType: String?
-    @objc dynamic var icon: String?
-    @objc dynamic var id: String?
-    @objc dynamic var message: String?
-    @objc dynamic var predefined: Bool = false
-    @objc dynamic var status: String?
-    @objc dynamic var userId: String?
-}

+ 5 - 6
iOSClient/Data/NCManageDatabase+Account.swift

@@ -2,7 +2,7 @@
 //  NCManageDatabase+Account.swift
 //  Nextcloud
 //
-//  Created by Henrik Storch on 30.11.21.
+//  Created by Marino Faggiana on 13/11/23.
 //  Copyright © 2021 Marino Faggiana. All rights reserved.
 //
 //  Author Marino Faggiana <marino.faggiana@nextcloud.com>
@@ -106,7 +106,7 @@ extension NCManageDatabase {
                     addObject.autoUploadWWAnVideo = true
                 }
 
-                CCUtility.setPassword(account, password: password)
+                NCKeychain().setPassword(account: account, password: password)
 
                 addObject.urlBase = urlBase
                 addObject.user = user
@@ -243,11 +243,11 @@ extension NCManageDatabase {
             realm.refresh()
             guard let result = realm.objects(tableAccount.self).filter("active == true").first else { return "" }
             if result.autoUploadDirectory.isEmpty {
-                return NCUtilityFileSystem.shared.getHomeServer(urlBase: urlBase, userId: userId)
+                return utilityFileSystem.getHomeServer(urlBase: urlBase, userId: userId)
             } else {
                 // FIX change webdav -> /dav/files/
                 if result.autoUploadDirectory.contains("/webdav") {
-                    return NCUtilityFileSystem.shared.getHomeServer(urlBase: urlBase, userId: userId)
+                    return utilityFileSystem.getHomeServer(urlBase: urlBase, userId: userId)
                 } else {
                     return result.autoUploadDirectory
                 }
@@ -263,8 +263,7 @@ extension NCManageDatabase {
 
         let cameraFileName = self.getAccountAutoUploadFileName()
         let cameraDirectory = self.getAccountAutoUploadDirectory(urlBase: urlBase, userId: userId, account: account)
-
-        let folderPhotos = CCUtility.stringAppendServerUrl(cameraDirectory, addFileName: cameraFileName)!
+        let folderPhotos = utilityFileSystem.stringAppendServerUrl(cameraDirectory, addFileName: cameraFileName)
 
         return folderPhotos
     }

+ 1 - 47
iOSClient/Data/NCManageDatabase+Activity.swift

@@ -2,7 +2,7 @@
 //  NCManageDatabase+Activity.swift
 //  Nextcloud
 //
-//  Created by Henrik Storch on 30.11.21.
+//  Created by Marino Faggiana on 13/11/23.
 //  Copyright © 2021 Marino Faggiana. All rights reserved.
 //
 //  Author Marino Faggiana <marino.faggiana@nextcloud.com>
@@ -296,50 +296,4 @@ extension NCManageDatabase {
 
         return nil
     }
-
-    // MARK: -
-    // MARK: Table Comments
-
-    func addComments(_ comments: [NKComments], account: String, objectId: String) {
-
-        do {
-            let realm = try Realm()
-            try realm.write {
-                let results = realm.objects(tableComments.self).filter("account == %@ AND objectId == %@", account, objectId)
-                realm.delete(results)
-                for comment in comments {
-                    let object = tableComments()
-                    object.account = account
-                    object.actorDisplayName = comment.actorDisplayName
-                    object.actorId = comment.actorId
-                    object.actorType = comment.actorType
-                    object.creationDateTime = comment.creationDateTime as NSDate
-                    object.isUnread = comment.isUnread
-                    object.message = comment.message
-                    object.messageId = comment.messageId
-                    object.objectId = comment.objectId
-                    object.objectType = comment.objectType
-                    object.path = comment.path
-                    object.verb = comment.verb
-                    realm.add(object, update: .all)
-                }
-            }
-        } catch let error {
-            NextcloudKit.shared.nkCommonInstance.writeLog("Could not write to database: \(error)")
-        }
-    }
-
-    func getComments(account: String, objectId: String) -> [tableComments] {
-
-        do {
-            let realm = try Realm()
-            realm.refresh()
-            let results = realm.objects(tableComments.self).filter("account == %@ AND objectId == %@", account, objectId).sorted(byKeyPath: "creationDateTime", ascending: false)
-            return Array(results.map(tableComments.init))
-        } catch let error as NSError {
-            NextcloudKit.shared.nkCommonInstance.writeLog("Could not access database: \(error)")
-        }
-
-        return []
-    }
 }

+ 4 - 4
iOSClient/Data/NCManageDatabase+Avatar.swift

@@ -89,7 +89,7 @@ extension NCManageDatabase {
     @discardableResult
     func setAvatarLoaded(fileName: String) -> UIImage? {
 
-        let fileNameLocalPath = String(CCUtility.getDirectoryUserData()) + "/" + fileName
+        let fileNameLocalPath = utilityFileSystem.directoryUserData + "/" + fileName
         var image: UIImage?
 
         do {
@@ -113,14 +113,14 @@ extension NCManageDatabase {
 
     func getImageAvatarLoaded(fileName: String) -> UIImage? {
 
-        let fileNameLocalPath = String(CCUtility.getDirectoryUserData()) + "/" + fileName
+        let fileNameLocalPath = utilityFileSystem.directoryUserData + "/" + fileName
 
         do {
             let realm = try Realm()
             realm.refresh()
             let result = realm.objects(tableAvatar.self).filter("fileName == %@", fileName).first
             if result == nil {
-                NCUtilityFileSystem.shared.deleteFile(filePath: fileNameLocalPath)
+                utilityFileSystem.removeFile(atPath: fileNameLocalPath)
                 return nil
             } else if result?.loaded == false {
                 return nil
@@ -130,7 +130,7 @@ extension NCManageDatabase {
             NextcloudKit.shared.nkCommonInstance.writeLog("Could not access database: \(error)")
         }
 
-        NCUtilityFileSystem.shared.deleteFile(filePath: fileNameLocalPath)
+        utilityFileSystem.removeFile(atPath: fileNameLocalPath)
         return nil
     }
 }

+ 7 - 0
iOSClient/Data/NCManageDatabase+Capabilities.swift

@@ -202,6 +202,12 @@ extension NCManageDatabase {
 
                         struct RichDocuments: Codable {
                             let mimetypes: [String]?
+                            let directediting: Bool?
+
+                            enum CodingKeys: String, CodingKey {
+                                case mimetypes
+                                case directediting = "direct_editing"
+                            }
                         }
 
                         struct Activity: Codable {
@@ -309,6 +315,7 @@ extension NCManageDatabase {
             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 {
                 for mimetype in mimetypes {

+ 2 - 2
iOSClient/Data/NCManageDatabase+Chunk.swift

@@ -100,7 +100,7 @@ extension NCManageDatabase {
                 let result = realm.objects(tableChunk.self).filter(NSPredicate(format: "account == %@ AND ocId == %@ AND fileName == %d", account, ocId, Int(fileChunk.fileName) ?? 0))
                 realm.delete(result)
                 let filePath = directory + "/\(fileChunk.fileName)"
-                NCUtilityFileSystem.shared.deleteFile(filePath: filePath)
+                utilityFileSystem.removeFile(atPath: filePath)
             }
         } catch let error {
             NextcloudKit.shared.nkCommonInstance.writeLog("Could not write to database: \(error)")
@@ -115,7 +115,7 @@ extension NCManageDatabase {
                 let results = realm.objects(tableChunk.self).filter(NSPredicate(format: "account == %@ AND ocId == %@", account, ocId))
                 for result in results {
                     let filePath = directory + "/\(result.fileName)"
-                    NCUtilityFileSystem.shared.deleteFile(filePath: filePath)
+                    utilityFileSystem.removeFile(atPath: filePath)
                 }
                 realm.delete(results)
             }

+ 93 - 0
iOSClient/Data/NCManageDatabase+Comments.swift

@@ -0,0 +1,93 @@
+//
+//  NCManageDatabase+Comments.swift
+//  Nextcloud
+//
+//  Created by Marino Faggiana on 13/11/23.
+//  Copyright © 2023 Marino Faggiana. All rights reserved.
+//
+//  Author Marino Faggiana <marino.faggiana@nextcloud.com>
+//
+//  This program is free software: you can redistribute it and/or modify
+//  it under the terms of the GNU General Public License as published by
+//  the Free Software Foundation, either version 3 of the License, or
+//  (at your option) any later version.
+//
+//  This program is distributed in the hope that it will be useful,
+//  but WITHOUT ANY WARRANTY; without even the implied warranty of
+//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+//  GNU General Public License for more details.
+//
+//  You should have received a copy of the GNU General Public License
+//  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+//
+
+import Foundation
+import RealmSwift
+import NextcloudKit
+
+class tableComments: Object, DateCompareable {
+    var dateKey: Date { creationDateTime as Date }
+
+    @objc dynamic var account = ""
+    @objc dynamic var actorDisplayName = ""
+    @objc dynamic var actorId = ""
+    @objc dynamic var actorType = ""
+    @objc dynamic var creationDateTime = NSDate()
+    @objc dynamic var isUnread: Bool = false
+    @objc dynamic var message = ""
+    @objc dynamic var messageId = ""
+    @objc dynamic var objectId = ""
+    @objc dynamic var objectType = ""
+    @objc dynamic var path = ""
+    @objc dynamic var verb = ""
+
+    override static func primaryKey() -> String {
+        return "messageId"
+    }
+}
+
+extension NCManageDatabase {
+
+    func addComments(_ comments: [NKComments], account: String, objectId: String) {
+
+        do {
+            let realm = try Realm()
+            try realm.write {
+                let results = realm.objects(tableComments.self).filter("account == %@ AND objectId == %@", account, objectId)
+                realm.delete(results)
+                for comment in comments {
+                    let object = tableComments()
+                    object.account = account
+                    object.actorDisplayName = comment.actorDisplayName
+                    object.actorId = comment.actorId
+                    object.actorType = comment.actorType
+                    object.creationDateTime = comment.creationDateTime as NSDate
+                    object.isUnread = comment.isUnread
+                    object.message = comment.message
+                    object.messageId = comment.messageId
+                    object.objectId = comment.objectId
+                    object.objectType = comment.objectType
+                    object.path = comment.path
+                    object.verb = comment.verb
+                    realm.add(object, update: .all)
+                }
+            }
+        } catch let error {
+            NextcloudKit.shared.nkCommonInstance.writeLog("Could not write to database: \(error)")
+        }
+    }
+
+    func getComments(account: String, objectId: String) -> [tableComments] {
+
+        do {
+            let realm = try Realm()
+            realm.refresh()
+            let results = realm.objects(tableComments.self).filter("account == %@ AND objectId == %@", account, objectId).sorted(byKeyPath: "creationDateTime", ascending: false)
+            return Array(results.map(tableComments.init))
+        } catch let error as NSError {
+            NextcloudKit.shared.nkCommonInstance.writeLog("Could not access database: \(error)")
+        }
+
+        return []
+    }
+}

+ 158 - 0
iOSClient/Data/NCManageDatabase+DirectEditing.swift

@@ -0,0 +1,158 @@
+//
+//  NCManageDatabase+DirectEditing.swift
+//  Nextcloud
+//
+//  Created by Marino Faggiana on 13/11/23.
+//  Copyright © 2023 Marino Faggiana. All rights reserved.
+//
+//  Author Marino Faggiana <marino.faggiana@nextcloud.com>
+//
+//  This program is free software: you can redistribute it and/or modify
+//  it under the terms of the GNU General Public License as published by
+//  the Free Software Foundation, either version 3 of the License, or
+//  (at your option) any later version.
+//
+//  This program is distributed in the hope that it will be useful,
+//  but WITHOUT ANY WARRANTY; without even the implied warranty of
+//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+//  GNU General Public License for more details.
+//
+//  You should have received a copy of the GNU General Public License
+//  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+//
+
+import Foundation
+import RealmSwift
+import NextcloudKit
+
+class tableDirectEditingCreators: Object {
+
+    @objc dynamic var account = ""
+    @objc dynamic var editor = ""
+    @objc dynamic var ext = ""
+    @objc dynamic var identifier = ""
+    @objc dynamic var mimetype = ""
+    @objc dynamic var name = ""
+    @objc dynamic var templates: Int = 0
+}
+
+class tableDirectEditingEditors: Object {
+
+    @objc dynamic var account = ""
+    @objc dynamic var editor = ""
+    let mimetypes = List<String>()
+    @objc dynamic var name = ""
+    let optionalMimetypes = List<String>()
+    @objc dynamic var secure: Int = 0
+}
+
+extension NCManageDatabase {
+
+    func addDirectEditing(account: String, editors: [NKEditorDetailsEditors], creators: [NKEditorDetailsCreators]) {
+
+        do {
+            let realm = try Realm()
+            try realm.write {
+
+                let resultsCreators = realm.objects(tableDirectEditingCreators.self).filter("account == %@", account)
+                realm.delete(resultsCreators)
+
+                let resultsEditors = realm.objects(tableDirectEditingEditors.self).filter("account == %@", account)
+                realm.delete(resultsEditors)
+
+                for creator in creators {
+
+                    let addObject = tableDirectEditingCreators()
+
+                    addObject.account = account
+                    addObject.editor = creator.editor
+                    addObject.ext = creator.ext
+                    addObject.identifier = creator.identifier
+                    addObject.mimetype = creator.mimetype
+                    addObject.name = creator.name
+                    addObject.templates = creator.templates
+
+                    realm.add(addObject)
+                }
+
+                for editor in editors {
+
+                    let addObject = tableDirectEditingEditors()
+
+                    addObject.account = account
+                    for mimeType in editor.mimetypes {
+                        addObject.mimetypes.append(mimeType)
+                    }
+                    addObject.name = editor.name
+                    if editor.name.lowercased() == NCGlobal.shared.editorOnlyoffice {
+                        addObject.editor = NCGlobal.shared.editorOnlyoffice
+                    } else {
+                        addObject.editor = NCGlobal.shared.editorText
+                    }
+                    for mimeType in editor.optionalMimetypes {
+                        addObject.optionalMimetypes.append(mimeType)
+                    }
+                    addObject.secure = editor.secure
+
+                    realm.add(addObject)
+                }
+            }
+        } catch let error {
+            NextcloudKit.shared.nkCommonInstance.writeLog("Could not write to database: \(error)")
+        }
+    }
+
+    func getDirectEditingCreators(account: String) -> [tableDirectEditingCreators]? {
+
+        do {
+            let realm = try Realm()
+            realm.refresh()
+            let results = realm.objects(tableDirectEditingCreators.self).filter("account == %@", account)
+            if results.isEmpty {
+                return nil
+            } else {
+                return Array(results.map { tableDirectEditingCreators.init(value: $0) })
+            }
+        } catch let error as NSError {
+            NextcloudKit.shared.nkCommonInstance.writeLog("Could not write to database: \(error)")
+        }
+
+        return nil
+    }
+
+    func getDirectEditingCreators(predicate: NSPredicate) -> [tableDirectEditingCreators]? {
+
+        do {
+            let realm = try Realm()
+            realm.refresh()
+            let results = realm.objects(tableDirectEditingCreators.self).filter(predicate)
+            if results.isEmpty {
+                return nil
+            } else {
+                return Array(results.map { tableDirectEditingCreators.init(value: $0) })
+            }
+        } catch let error as NSError {
+            NextcloudKit.shared.nkCommonInstance.writeLog("Could not write to database: \(error)")
+        }
+
+        return nil
+    }
+
+    func getDirectEditingEditors(account: String) -> [tableDirectEditingEditors]? {
+
+        do {
+            let realm = try Realm()
+            realm.refresh()
+            let results = realm.objects(tableDirectEditingEditors.self).filter("account == %@", account)
+            if results.isEmpty {
+                return nil
+            } else {
+                return Array(results.map { tableDirectEditingEditors.init(value: $0) })
+            }
+        } catch let error as NSError {
+            NextcloudKit.shared.nkCommonInstance.writeLog("Could not write to database: \(error)")
+        }
+
+        return nil
+    }
+}

+ 93 - 0
iOSClient/Data/NCManageDatabase+ExternalSites.swift

@@ -0,0 +1,93 @@
+//
+//  NCManageDatabase+ExternalSites.swift
+//  Nextcloud
+//
+//  Created by Marino Faggiana on 13/11/23.
+//  Copyright © 2023 Marino Faggiana. All rights reserved.
+//
+//  Author Marino Faggiana <marino.faggiana@nextcloud.com>
+//
+//  This program is free software: you can redistribute it and/or modify
+//  it under the terms of the GNU General Public License as published by
+//  the Free Software Foundation, either version 3 of the License, or
+//  (at your option) any later version.
+//
+//  This program is distributed in the hope that it will be useful,
+//  but WITHOUT ANY WARRANTY; without even the implied warranty of
+//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+//  GNU General Public License for more details.
+//
+//  You should have received a copy of the GNU General Public License
+//  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+//
+
+import Foundation
+import RealmSwift
+import NextcloudKit
+
+class tableExternalSites: Object {
+
+    @objc dynamic var account = ""
+    @objc dynamic var icon = ""
+    @objc dynamic var idExternalSite: Int = 0
+    @objc dynamic var lang = ""
+    @objc dynamic var name = ""
+    @objc dynamic var type = ""
+    @objc dynamic var url = ""
+}
+
+extension NCManageDatabase {
+
+    func addExternalSites(_ externalSite: NKExternalSite, account: String) {
+
+        do {
+            let realm = try Realm()
+            try realm.write {
+                let addObject = tableExternalSites()
+
+                addObject.account = account
+                addObject.idExternalSite = externalSite.idExternalSite
+                addObject.icon = externalSite.icon
+                addObject.lang = externalSite.lang
+                addObject.name = externalSite.name
+                addObject.url = externalSite.url
+                addObject.type = externalSite.type
+
+                realm.add(addObject)
+            }
+        } catch let error {
+            NextcloudKit.shared.nkCommonInstance.writeLog("Could not write to database: \(error)")
+        }
+    }
+
+    func deleteExternalSites(account: String) {
+
+        do {
+            let realm = try Realm()
+            try realm.write {
+                let results = realm.objects(tableExternalSites.self).filter("account == %@", account)
+                realm.delete(results)
+            }
+        } catch let error {
+            NextcloudKit.shared.nkCommonInstance.writeLog("Could not write to database: \(error)")
+        }
+    }
+
+    func getAllExternalSites(account: String) -> [tableExternalSites]? {
+
+        do {
+            let realm = try Realm()
+            realm.refresh()
+            let results = realm.objects(tableExternalSites.self).filter("account == %@", account).sorted(byKeyPath: "idExternalSite", ascending: true)
+            if results.isEmpty {
+                return nil
+            } else {
+                return Array(results.map { tableExternalSites.init(value: $0) })
+            }
+        } catch let error as NSError {
+            NextcloudKit.shared.nkCommonInstance.writeLog("Could not write to database: \(error)")
+        }
+
+        return nil
+    }
+}

+ 67 - 0
iOSClient/Data/NCManageDatabase+GPS.swift

@@ -0,0 +1,67 @@
+//
+//  NCManageDatabase+GPS.swift
+//  Nextcloud
+//
+//  Created by Marino Faggiana on 13/11/23.
+//  Copyright © 2023 Marino Faggiana. All rights reserved.
+//
+//  Author Marino Faggiana <marino.faggiana@nextcloud.com>
+//
+//  This program is free software: you can redistribute it and/or modify
+//  it under the terms of the GNU General Public License as published by
+//  the Free Software Foundation, either version 3 of the License, or
+//  (at your option) any later version.
+//
+//  This program is distributed in the hope that it will be useful,
+//  but WITHOUT ANY WARRANTY; without even the implied warranty of
+//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+//  GNU General Public License for more details.
+//
+//  You should have received a copy of the GNU General Public License
+//  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+//
+
+import Foundation
+import RealmSwift
+import NextcloudKit
+
+typealias tableGPS = tableGPSV2
+class tableGPSV2: Object {
+    @objc dynamic var latitude: Double = 0
+    @objc dynamic var longitude: Double = 0
+    @objc dynamic var location = ""
+}
+
+extension NCManageDatabase {
+
+    @objc func addGeocoderLocation(_ location: String, latitude: Double, longitude: Double) {
+
+        do {
+            let realm = try Realm()
+            realm.refresh()
+            guard realm.objects(tableGPS.self).filter("latitude == %@ AND longitude == %@", latitude, longitude).first == nil else { return }
+            try realm.write {
+                let addObject = tableGPS()
+                addObject.latitude = latitude
+                addObject.location = location
+                addObject.longitude = longitude
+                realm.add(addObject)
+            }
+        } catch let error as NSError {
+            NextcloudKit.shared.nkCommonInstance.writeLog("Could not write to database: \(error)")
+        }
+    }
+
+    @objc func getLocationFromLatAndLong(latitude: Double, longitude: Double) -> String? {
+        do {
+            let realm = try Realm()
+            realm.refresh()
+            let result = realm.objects(tableGPS.self).filter("latitude == %@ AND longitude == %@", latitude, longitude).first
+            return result?.location
+        } catch let error as NSError {
+            NextcloudKit.shared.nkCommonInstance.writeLog("Could not write to database: \(error)")
+        }
+
+        return nil
+    }
+}

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

@@ -37,6 +37,7 @@ class tableLocalFile: Object {
     @objc dynamic var fileName = ""
     @objc dynamic var ocId = ""
     @objc dynamic var offline: Bool = false
+    @objc dynamic var lastOpeningDate = NSDate()
 
     override static func primaryKey() -> String {
         return "ocId"
@@ -206,4 +207,41 @@ extension NCManageDatabase {
 
         return []
     }
+
+    func getResultsTableLocalFile(predicate: NSPredicate, sorted: String, ascending: Bool) -> Results<tableLocalFile>? {
+
+        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)")
+        }
+
+        return nil
+    }
+
+    func setLastOpeningDate(metadata: tableMetadata) {
+
+        do {
+            let realm = try Realm()
+            try realm.write {
+                if let result = realm.objects(tableLocalFile.self).filter("ocId == %@", metadata.ocId).first {
+                    result.lastOpeningDate = NSDate()
+                } else {
+                    let addObject = tableLocalFile()
+                    addObject.account = metadata.account
+                    addObject.etag = metadata.etag
+                    addObject.exifDate = NSDate()
+                    addObject.exifLatitude = "-1"
+                    addObject.exifLongitude = "-1"
+                    addObject.ocId = metadata.ocId
+                    addObject.fileName = metadata.fileName
+                    realm.add(addObject, update: .all)
+                }
+            }
+        } catch let error {
+            NextcloudKit.shared.nkCommonInstance.writeLog("Could not write to database: \(error)")
+        }
+    }
 }

+ 77 - 62
iOSClient/Data/NCManageDatabase+Metadata.swift

@@ -57,10 +57,12 @@ class tableMetadata: Object, NCUserBaseUrl {
     @objc dynamic var fileName = ""
     @objc dynamic var fileNameView = ""
     @objc dynamic var hasPreview: Bool = false
+    @objc dynamic var hidden: Bool = false
     @objc dynamic var iconName = ""
     @objc dynamic var iconUrl = ""
     @objc dynamic var isExtractFile: Bool = false
     @objc dynamic var livePhoto: Bool = false
+    @objc dynamic var livePhotoFile = ""
     @objc dynamic var mountType = ""
     @objc dynamic var name = ""                                             // for unifiedSearch is the provider.id
     @objc dynamic var note = ""
@@ -104,6 +106,9 @@ class tableMetadata: Object, NCUserBaseUrl {
     @objc dynamic var longitude: Double = 0
     @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"
@@ -199,19 +204,20 @@ extension tableMetadata {
     }
 
     var isDirectoySettableE2EE: Bool {
-        return directory && size == 0 && !e2eEncrypted && CCUtility.isEnd(toEndEnabled: account)
+        return directory && size == 0 && !e2eEncrypted && NCKeychain().isEndToEndEnabled(account: account)
     }
 
     var isDirectoryUnsettableE2EE: Bool {
-        return !isDirectoryE2EE && directory && size == 0 && e2eEncrypted && CCUtility.isEnd(toEndEnabled: account)
+        return !isDirectoryE2EE && directory && size == 0 && e2eEncrypted && NCKeychain().isEndToEndEnabled(account: account)
     }
 
     var canOpenExternalEditor: Bool {
         if isDocumentViewableOnly {
             return false
         }
-        let editors = NCUtility.shared.isDirectEditing(account: account, contentType: contentType)
-        let isRichDocument = NCUtility.shared.isRichDocument(self)
+        let utility = NCUtility()
+        let editors = utility.isDirectEditing(account: account, contentType: contentType)
+        let isRichDocument = utility.isRichDocument(self)
         return classFile == NKCommon.TypeClassFile.document.rawValue && editors.contains(NCGlobal.shared.editorText) && ((editors.contains(NCGlobal.shared.editorOnlyoffice) || isRichDocument))
     }
 
@@ -236,11 +242,11 @@ extension tableMetadata {
     }
 
     @objc var isDirectoryE2EE: Bool {
-        NCUtility.shared.isDirectoryE2EE(account: account, urlBase: urlBase, userId: userId, serverUrl: serverUrl)
+        NCUtilityFileSystem().isDirectoryE2EE(account: account, urlBase: urlBase, userId: userId, serverUrl: serverUrl)
     }
 
     var isDirectoryE2EETop: Bool {
-        NCUtility.shared.isDirectoryE2EETop(account: account, serverUrl: serverUrl)
+        NCUtilityFileSystem().isDirectoryE2EETop(account: account, serverUrl: serverUrl)
     }
 
     /// Returns false if the user is lokced out of the file. I.e. The file is locked but by somone else
@@ -285,6 +291,7 @@ extension NCManageDatabase {
         metadata.fileName = file.fileName
         metadata.fileNameView = file.fileName
         metadata.hasPreview = file.hasPreview
+        metadata.hidden = file.hidden
         metadata.iconName = file.iconName
         metadata.mountType = file.mountType
         metadata.name = file.name
@@ -334,6 +341,10 @@ extension NCManageDatabase {
         metadata.longitude = file.longitude
         metadata.height = file.height
         metadata.width = file.width
+        metadata.livePhotoFile = file.livePhotoFile
+        if !metadata.livePhotoFile.isEmpty {
+            metadata.livePhoto = true
+        }
 
         // E2EE find the fileName for fileNameView
         if isDirectoryE2EE || file.e2eEncrypted {
@@ -364,7 +375,7 @@ extension NCManageDatabase {
             if let key = listServerUrl[file.serverUrl] {
                 isDirectoryE2EE = key
             } else {
-                isDirectoryE2EE = NCUtility.shared.isDirectoryE2EE(file: file)
+                isDirectoryE2EE = NCUtilityFileSystem().isDirectoryE2EE(file: file)
                 listServerUrl[file.serverUrl] = isDirectoryE2EE
             }
 
@@ -395,6 +406,9 @@ extension NCManageDatabase {
                 ((metadata.classFile == NKCommon.TypeClassFile.image.rawValue && metadatas[index + 1].classFile == NKCommon.TypeClassFile.video.rawValue) || (metadata.classFile == NKCommon.TypeClassFile.video.rawValue && metadatas[index + 1].classFile == NKCommon.TypeClassFile.image.rawValue)) {
                 metadata.livePhoto = true
                 metadatas[index + 1].livePhoto = true
+                let metadata1 = metadata
+                let metadata2 = metadatas[index + 1]
+                NCLivePhoto().setLivePhoto(metadata1: metadata1, metadata2: metadata2)
             }
             metadataOutput.append(metadata)
         }
@@ -626,7 +640,7 @@ extension NCManageDatabase {
         return ([], [], [])
     }
 
-    func setMetadataSession(ocId: String, newFileName: String? = nil, session: String? = nil, sessionError: String? = nil, sessionSelector: String? = nil, sessionTaskIdentifier: Int? = nil, status: Int? = nil, etag: String? = nil) {
+    func setMetadataSession(ocId: String, newFileName: String? = nil, session: String?, sessionError: String?, sessionSelector: String?, sessionTaskIdentifier: Int?, status: Int?, etag: String? = nil, errorCode: Int?) {
 
         do {
             let realm = try Realm()
@@ -636,24 +650,33 @@ extension NCManageDatabase {
                         result.fileName = newFileName
                         result.fileNameView = newFileName
                     }
-                    if let session = session {
+                    if let session {
                         result.session = session
                     }
-                    if let sessionError = sessionError {
+                    if let sessionError {
                         result.sessionError = sessionError
                     }
-                    if let sessionSelector = sessionSelector {
+                    if let sessionSelector {
                         result.sessionSelector = sessionSelector
                     }
-                    if let sessionTaskIdentifier = sessionTaskIdentifier {
+                    if let sessionTaskIdentifier {
                         result.sessionTaskIdentifier = sessionTaskIdentifier
                     }
-                    if let status = status {
+                    if let status {
                         result.status = status
                     }
-                    if let etag = etag {
+                    if let etag {
                         result.etag = etag
                     }
+                    if let errorCode {
+                        result.errorCode = errorCode
+                        if errorCode == 0 {
+                            result.errorCodeCounter = 0
+                        } else {
+                            result.errorCodeCounter += 1
+                            result.errorCodeDate = Date()
+                        }
+                    }
                 }
             }
         } catch let error {
@@ -783,48 +806,6 @@ extension NCManageDatabase {
         return nil
     }
 
-    func getMetadatasViewer(predicate: NSPredicate, sorted: String, ascending: Bool) -> [tableMetadata]? {
-
-        let results: Results<tableMetadata>
-        var finals: [tableMetadata] = []
-
-        do {
-            let realm = try Realm()
-            realm.refresh()
-            if (tableMetadata().objectSchema.properties.contains { $0.name == sorted }) {
-                results = realm.objects(tableMetadata.self).filter(predicate).sorted(byKeyPath: sorted, ascending: ascending)
-            } else {
-                results = realm.objects(tableMetadata.self).filter(predicate)
-            }
-
-            // For Live Photo
-            var fileNameImages: [String] = []
-            let filtered = results.filter { $0.classFile.contains(NKCommon.TypeClassFile.image.rawValue) }
-            filtered.forEach { print($0)
-                let fileName = ($0.fileNameView as NSString).deletingPathExtension
-                fileNameImages.append(fileName)
-            }
-
-            for result in results {
-
-                let ext = (result.fileNameView as NSString).pathExtension.uppercased()
-                let fileName = (result.fileNameView as NSString).deletingPathExtension
-
-                if !(ext == "MOV" && fileNameImages.contains(fileName)) {
-                    finals.append(result)
-                }
-            }
-        } catch let error as NSError {
-            NextcloudKit.shared.nkCommonInstance.writeLog("Could not access database: \(error)")
-        }
-
-        if finals.isEmpty {
-            return nil
-        } else {
-            return Array(finals.map { tableMetadata.init(value: $0) })
-        }
-    }
-
     func getMetadatas(predicate: NSPredicate) -> [tableMetadata] {
 
         do {
@@ -934,13 +915,13 @@ extension NCManageDatabase {
         var serverUrl = serverUrl
         var fileName = ""
 
-        let serverUrlHome = NCUtilityFileSystem.shared.getHomeServer(urlBase: urlBase, userId: userId)
+        let serverUrlHome = utilityFileSystem.getHomeServer(urlBase: urlBase, userId: userId)
         if serverUrlHome == serverUrl {
             fileName = "."
             serverUrl = ".."
         } else {
             fileName = (serverUrl as NSString).lastPathComponent
-            if let path = NCUtilityFileSystem.shared.deleteLastPath(serverUrlPath: serverUrl) {
+            if let path = utilityFileSystem.deleteLastPath(serverUrlPath: serverUrl) {
                 serverUrl = path
             }
         }
@@ -1045,7 +1026,7 @@ extension NCManageDatabase {
         var classFile = metadata.classFile
         var fileName = (metadata.fileNameView as NSString).deletingPathExtension
 
-        if !metadata.livePhoto || !CCUtility.getLivePhoto() {
+        if !metadata.livePhoto || !NCKeychain().livePhoto {
             return nil
         }
 
@@ -1089,6 +1070,9 @@ extension NCManageDatabase {
                             if !results[index + 1].livePhoto {
                                 results[index + 1].livePhoto = true
                             }
+                            let metadata1 = tableMetadata(value: results[index + 1])
+                            let metadata2 = tableMetadata(value: results[index])
+                            NCLivePhoto().setLivePhoto(metadata1: metadata1, metadata2: metadata2)
                         }
                         if metadata.livePhoto {
                             if metadata.classFile == NKCommon.TypeClassFile.image.rawValue {
@@ -1136,7 +1120,7 @@ extension NCManageDatabase {
     func isDownloadMetadata(_ metadata: tableMetadata, download: Bool) -> Bool {
 
         let localFile = getTableLocalFile(predicate: NSPredicate(format: "ocId == %@", metadata.ocId))
-        let fileSize = CCUtility.fileProviderStorageSize(metadata.ocId, fileNameView: metadata.fileNameView)
+        let fileSize = utilityFileSystem.fileProviderStorageSize(metadata.ocId, fileNameView: metadata.fileNameView)
         if (localFile != nil || download) && (localFile?.etag != metadata.etag || fileSize == 0) {
             return true
         }
@@ -1150,7 +1134,7 @@ extension NCManageDatabase {
         let fileNameNoExtension = (fileNameView as NSString).deletingPathExtension
         var fileNameConflict = fileNameView
 
-        if fileNameExtension == "heic" && CCUtility.getFormatCompatibility() {
+        if fileNameExtension == "heic", NCKeychain().formatCompatibility {
             fileNameConflict = fileNameNoExtension + ".jpg"
         }
         return getMetadata(predicate: NSPredicate(format: "account == %@ AND serverUrl == %@ AND fileNameView == %@", account, serverUrl, fileNameConflict))
@@ -1187,7 +1171,7 @@ extension NCManageDatabase {
     func getMetadatasFromGroupfolders(account: String, urlBase: String, userId: String) -> [tableMetadata] {
 
         var metadatas: [tableMetadata] = []
-        let homeServerUrl = NCUtilityFileSystem.shared.getHomeServer(urlBase: urlBase, userId: userId)
+        let homeServerUrl = utilityFileSystem.getHomeServer(urlBase: urlBase, userId: userId)
 
         do {
             let realm = try Realm()
@@ -1207,4 +1191,35 @@ extension NCManageDatabase {
 
         return metadatas
     }
+
+    func getMetadatasInError(account: String) -> Results<tableMetadata>? {
+
+        do {
+            let realm = try Realm()
+            let results = realm.objects(tableMetadata.self).filter("account == %@ AND errorCodeCounter > 1", account)
+            return results
+        } catch let error as NSError {
+            NextcloudKit.shared.nkCommonInstance.writeLog("Could not access database: \(error)")
+        }
+
+        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)")
+        }
+    }
 }

+ 100 - 0
iOSClient/Data/NCManageDatabase+PhotoLibrary.swift

@@ -0,0 +1,100 @@
+//
+//  NCManageDatabase+PhotoLibrary.swift
+//  Nextcloud
+//
+//  Created by Marino Faggiana on 13/11/23.
+//  Copyright © 2023 Marino Faggiana. All rights reserved.
+//
+//  Author Marino Faggiana <marino.faggiana@nextcloud.com>
+//
+//  This program is free software: you can redistribute it and/or modify
+//  it under the terms of the GNU General Public License as published by
+//  the Free Software Foundation, either version 3 of the License, or
+//  (at your option) any later version.
+//
+//  This program is distributed in the hope that it will be useful,
+//  but WITHOUT ANY WARRANTY; without even the implied warranty of
+//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+//  GNU General Public License for more details.
+//
+//  You should have received a copy of the GNU General Public License
+//  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+//
+
+import Foundation
+import RealmSwift
+import NextcloudKit
+
+class tablePhotoLibrary: Object {
+
+    @objc dynamic var account = ""
+    @objc dynamic var assetLocalIdentifier = ""
+    @objc dynamic var creationDate: NSDate?
+    @objc dynamic var idAsset = ""
+    @objc dynamic var modificationDate: NSDate?
+    @objc dynamic var mediaType: Int = 0
+
+    override static func primaryKey() -> String {
+        return "idAsset"
+    }
+}
+
+extension NCManageDatabase {
+
+    @discardableResult
+    func addPhotoLibrary(_ assets: [PHAsset], account: String) -> Bool {
+
+        do {
+            let realm = try Realm()
+            try realm.write {
+                for asset in assets {
+                    var creationDateString = ""
+                    let addObject = tablePhotoLibrary()
+                    addObject.account = account
+                    addObject.assetLocalIdentifier = asset.localIdentifier
+                    addObject.mediaType = asset.mediaType.rawValue
+                    if let creationDate = asset.creationDate {
+                        addObject.creationDate = creationDate as NSDate
+                        creationDateString = String(describing: creationDate)
+                    }
+                    if let modificationDate = asset.modificationDate {
+                        addObject.modificationDate = modificationDate as NSDate
+                    }
+                    addObject.idAsset = account + asset.localIdentifier + creationDateString
+                    realm.add(addObject, update: .all)
+                }
+            }
+        } catch let error {
+            NextcloudKit.shared.nkCommonInstance.writeLog("Could not write to database: \(error)")
+            return false
+        }
+
+        return true
+    }
+
+    func getPhotoLibraryIdAsset(image: Bool, video: Bool, account: String) -> [String]? {
+
+        var predicate = NSPredicate()
+
+        if image && video {
+            predicate = NSPredicate(format: "account == %@ AND (mediaType == %d OR mediaType == %d)", account, PHAssetMediaType.image.rawValue, PHAssetMediaType.video.rawValue)
+        } else if image {
+            predicate = NSPredicate(format: "account == %@ AND mediaType == %d", account, PHAssetMediaType.image.rawValue)
+        } else if video {
+            predicate = NSPredicate(format: "account == %@ AND mediaType == %d", account, PHAssetMediaType.video.rawValue)
+        }
+
+        do {
+            let realm = try Realm()
+            realm.refresh()
+            let results = realm.objects(tablePhotoLibrary.self).filter(predicate)
+            let idsAsset = results.map { $0.idAsset }
+            return Array(idsAsset)
+        } catch let error as NSError {
+            NextcloudKit.shared.nkCommonInstance.writeLog("Could not write to database: \(error)")
+        }
+
+        return nil
+    }
+
+}

+ 1 - 1
iOSClient/Data/NCManageDatabase+Share.swift

@@ -83,7 +83,7 @@ extension NCManageDatabase {
             try realm.write {
                 for share in shares {
                     let serverUrlPath = home + share.path
-                    guard let serverUrl = NCUtilityFileSystem.shared.deleteLastPath(serverUrlPath: serverUrlPath, home: home) else { continue }
+                    guard let serverUrl = utilityFileSystem.deleteLastPath(serverUrlPath: serverUrlPath, home: home) else { continue }
                     let object = tableShare()
                     object.account = account
                     if let fileName = share.path.components(separatedBy: "/").last {

+ 97 - 0
iOSClient/Data/NCManageDatabase+Tag.swift

@@ -0,0 +1,97 @@
+//
+//  NCManageDatabase+Tag.swift
+//  Nextcloud
+//
+//  Created by Marino Faggiana on 13/11/23.
+//  Copyright © 2023 Marino Faggiana. All rights reserved.
+//
+//  Author Marino Faggiana <marino.faggiana@nextcloud.com>
+//
+//  This program is free software: you can redistribute it and/or modify
+//  it under the terms of the GNU General Public License as published by
+//  the Free Software Foundation, either version 3 of the License, or
+//  (at your option) any later version.
+//
+//  This program is distributed in the hope that it will be useful,
+//  but WITHOUT ANY WARRANTY; without even the implied warranty of
+//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+//  GNU General Public License for more details.
+//
+//  You should have received a copy of the GNU General Public License
+//  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+//
+
+import Foundation
+import RealmSwift
+import NextcloudKit
+
+class tableTag: Object {
+
+    @objc dynamic var account = ""
+    @objc dynamic var ocId = ""
+    @objc dynamic var tagIOS: Data?
+
+    override static func primaryKey() -> String {
+        return "ocId"
+    }
+}
+
+extension NCManageDatabase {
+
+    func addTag(_ ocId: String, tagIOS: Data?, account: String) {
+
+        do {
+            let realm = try Realm()
+            try realm.write {
+                let addObject = tableTag()
+                addObject.account = account
+                addObject.ocId = ocId
+                addObject.tagIOS = tagIOS
+                realm.add(addObject, update: .all)
+            }
+        } catch let error {
+            NextcloudKit.shared.nkCommonInstance.writeLog("Could not write to database: \(error)")
+        }
+    }
+
+    func deleteTag(_ ocId: String) {
+
+        do {
+            let realm = try Realm()
+            try realm.write {
+                let results = realm.objects(tableTag.self).filter("ocId == %@", ocId)
+                realm.delete(results)
+            }
+        } catch let error {
+            NextcloudKit.shared.nkCommonInstance.writeLog("Could not write to database: \(error)")
+        }
+    }
+
+    func getTags(predicate: NSPredicate) -> [tableTag] {
+
+        do {
+            let realm = try Realm()
+            realm.refresh()
+            let results = realm.objects(tableTag.self).filter(predicate)
+            return Array(results.map { tableTag.init(value: $0) })
+        } catch let error as NSError {
+            NextcloudKit.shared.nkCommonInstance.writeLog("Could not write to database: \(error)")
+        }
+
+        return []
+    }
+
+    func getTag(predicate: NSPredicate) -> tableTag? {
+
+        do {
+            let realm = try Realm()
+            realm.refresh()
+            guard let result = realm.objects(tableTag.self).filter(predicate).first else { return nil }
+            return tableTag.init(value: result)
+        } catch let error as NSError {
+            NextcloudKit.shared.nkCommonInstance.writeLog("Could not write to database: \(error)")
+        }
+
+        return nil
+    }
+}

+ 64 - 0
iOSClient/Data/NCManageDatabase+Tip.swift

@@ -0,0 +1,64 @@
+//
+//  NCManageDatabase+Tip.swift
+//  Nextcloud
+//
+//  Created by Marino Faggiana on 13/11/23.
+//  Copyright © 2023 Marino Faggiana. All rights reserved.
+//
+//  Author Marino Faggiana <marino.faggiana@nextcloud.com>
+//
+//  This program is free software: you can redistribute it and/or modify
+//  it under the terms of the GNU General Public License as published by
+//  the Free Software Foundation, either version 3 of the License, or
+//  (at your option) any later version.
+//
+//  This program is distributed in the hope that it will be useful,
+//  but WITHOUT ANY WARRANTY; without even the implied warranty of
+//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+//  GNU General Public License for more details.
+//
+//  You should have received a copy of the GNU General Public License
+//  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+//
+
+import Foundation
+import RealmSwift
+import NextcloudKit
+
+class tableTip: Object {
+
+    @Persisted(primaryKey: true) var tipName = ""
+}
+
+extension NCManageDatabase {
+
+    func tipExists(_ tipName: String) -> Bool {
+
+        do {
+            let realm = try Realm()
+            guard (realm.objects(tableTip.self).where {
+                $0.tipName == tipName
+            }.first) == nil else {
+                return true
+            }
+        } catch let error as NSError {
+            NextcloudKit.shared.nkCommonInstance.writeLog("Could not write to database: \(error)")
+        }
+
+        return false
+    }
+
+    func addTip(_ tipName: String) {
+
+        do {
+            let realm = try Realm()
+            try realm.write {
+                let addObject = tableTip()
+                addObject.tipName = tipName
+                realm.add(addObject, update: .all)
+            }
+        } catch let error {
+            NextcloudKit.shared.nkCommonInstance.writeLog("Could not write to database: \(error)")
+        }
+    }
+}

+ 151 - 0
iOSClient/Data/NCManageDatabase+Trash.swift

@@ -0,0 +1,151 @@
+//
+//  NCManageDatabase+Trash.swift
+//  Nextcloud
+//
+//  Created by Marino Faggiana on 13/11/23.
+//  Copyright © 2023 Marino Faggiana. All rights reserved.
+//
+//  Author Marino Faggiana <marino.faggiana@nextcloud.com>
+//
+//  This program is free software: you can redistribute it and/or modify
+//  it under the terms of the GNU General Public License as published by
+//  the Free Software Foundation, either version 3 of the License, or
+//  (at your option) any later version.
+//
+//  This program is distributed in the hope that it will be useful,
+//  but WITHOUT ANY WARRANTY; without even the implied warranty of
+//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+//  GNU General Public License for more details.
+//
+//  You should have received a copy of the GNU General Public License
+//  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+//
+
+import Foundation
+import RealmSwift
+import NextcloudKit
+
+class tableTrash: Object {
+
+    @objc dynamic var account = ""
+    @objc dynamic var classFile = ""
+    @objc dynamic var contentType = ""
+    @objc dynamic var date = NSDate()
+    @objc dynamic var directory: Bool = false
+    @objc dynamic var fileId = ""
+    @objc dynamic var fileName = ""
+    @objc dynamic var filePath = ""
+    @objc dynamic var hasPreview: Bool = false
+    @objc dynamic var iconName = ""
+    @objc dynamic var size: Int64 = 0
+    @objc dynamic var trashbinFileName = ""
+    @objc dynamic var trashbinOriginalLocation = ""
+    @objc dynamic var trashbinDeletionTime = NSDate()
+
+    override static func primaryKey() -> String {
+        return "fileId"
+    }
+}
+
+extension NCManageDatabase {
+
+    func addTrash(account: String, items: [NKTrash]) {
+
+        do {
+            let realm = try Realm()
+            try realm.write {
+                for trash in items {
+                    let object = tableTrash()
+                    object.account = account
+                    object.contentType = trash.contentType
+                    object.date = trash.date
+                    object.directory = trash.directory
+                    object.fileId = trash.fileId
+                    object.fileName = trash.fileName
+                    object.filePath = trash.filePath
+                    object.hasPreview = trash.hasPreview
+                    object.iconName = trash.iconName
+                    object.size = trash.size
+                    object.trashbinDeletionTime = trash.trashbinDeletionTime
+                    object.trashbinFileName = trash.trashbinFileName
+                    object.trashbinOriginalLocation = trash.trashbinOriginalLocation
+                    object.classFile = trash.classFile
+                    realm.add(object, update: .all)
+                }
+            }
+        } catch let error {
+            NextcloudKit.shared.nkCommonInstance.writeLog("Could not write to database: \(error)")
+        }
+    }
+
+    func deleteTrash(filePath: String?, account: String) {
+
+        var predicate = NSPredicate()
+
+        do {
+            let realm = try Realm()
+            try realm.write {
+                if filePath == nil {
+                    predicate = NSPredicate(format: "account == %@", account)
+                } else {
+                    predicate = NSPredicate(format: "account == %@ AND filePath == %@", account, filePath!)
+                }
+                let result = realm.objects(tableTrash.self).filter(predicate)
+                realm.delete(result)
+            }
+        } catch let error {
+            NextcloudKit.shared.nkCommonInstance.writeLog("Could not write to database: \(error)")
+        }
+    }
+
+    func deleteTrash(fileId: String?, account: String) {
+
+        var predicate = NSPredicate()
+
+        do {
+            let realm = try Realm()
+            try realm.write {
+                if fileId == nil {
+                    predicate = NSPredicate(format: "account == %@", account)
+                } else {
+                    predicate = NSPredicate(format: "account == %@ AND fileId == %@", account, fileId!)
+                }
+                let result = realm.objects(tableTrash.self).filter(predicate)
+                realm.delete(result)
+            }
+        } catch let error {
+            NextcloudKit.shared.nkCommonInstance.writeLog("Could not write to database: \(error)")
+        }
+    }
+
+    func getTrash(filePath: String, sort: String?, ascending: Bool?, account: String) -> [tableTrash]? {
+
+        let sort = sort ?? "date"
+        let ascending = ascending ?? false
+
+        do {
+            let realm = try Realm()
+            realm.refresh()
+            let results = realm.objects(tableTrash.self).filter("account == %@ AND filePath == %@", account, filePath).sorted(byKeyPath: sort, ascending: ascending)
+            return Array(results.map { tableTrash.init(value: $0) })
+        } catch let error as NSError {
+            NextcloudKit.shared.nkCommonInstance.writeLog("Could not write to database: \(error)")
+        }
+
+        return nil
+    }
+
+    func getTrashItem(fileId: String, account: String) -> tableTrash? {
+
+        do {
+            let realm = try Realm()
+            realm.refresh()
+            guard let result = realm.objects(tableTrash.self).filter("account == %@ AND fileId == %@", account, fileId).first else { return nil }
+            return tableTrash.init(value: result)
+        } catch let error as NSError {
+            NextcloudKit.shared.nkCommonInstance.writeLog("Could not write to database: \(error)")
+        }
+
+        return nil
+    }
+}

+ 70 - 0
iOSClient/Data/NCManageDatabase+UserStatus.swift

@@ -0,0 +1,70 @@
+//
+//  NCManageDatabase+UserStatus.swift
+//  Nextcloud
+//
+//  Created by Marino Faggiana on 13/11/23.
+//  Copyright © 2023 Marino Faggiana. All rights reserved.
+//
+//  Author Marino Faggiana <marino.faggiana@nextcloud.com>
+//
+//  This program is free software: you can redistribute it and/or modify
+//  it under the terms of the GNU General Public License as published by
+//  the Free Software Foundation, either version 3 of the License, or
+//  (at your option) any later version.
+//
+//  This program is distributed in the hope that it will be useful,
+//  but WITHOUT ANY WARRANTY; without even the implied warranty of
+//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+//  GNU General Public License for more details.
+//
+//  You should have received a copy of the GNU General Public License
+//  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+//
+
+import Foundation
+import RealmSwift
+import NextcloudKit
+
+class tableUserStatus: Object {
+
+    @objc dynamic var account = ""
+    @objc dynamic var clearAt: NSDate?
+    @objc dynamic var clearAtTime: String?
+    @objc dynamic var clearAtType: String?
+    @objc dynamic var icon: String?
+    @objc dynamic var id: String?
+    @objc dynamic var message: String?
+    @objc dynamic var predefined: Bool = false
+    @objc dynamic var status: String?
+    @objc dynamic var userId: String?
+}
+
+extension NCManageDatabase {
+
+    func addUserStatus(_ userStatuses: [NKUserStatus], account: String, predefined: Bool) {
+
+        do {
+            let realm = try Realm()
+            try realm.write {
+                let results = realm.objects(tableUserStatus.self).filter("account == %@ AND predefined == %@", account, predefined)
+                realm.delete(results)
+                for userStatus in userStatuses {
+                    let object = tableUserStatus()
+                    object.account = account
+                    object.clearAt = userStatus.clearAt
+                    object.clearAtTime = userStatus.clearAtTime
+                    object.clearAtType = userStatus.clearAtType
+                    object.icon = userStatus.icon
+                    object.id = userStatus.id
+                    object.message = userStatus.message
+                    object.predefined = userStatus.predefined
+                    object.status = userStatus.status
+                    object.userId = userStatus.userId
+                    realm.add(object)
+                }
+            }
+        } catch let error {
+            NextcloudKit.shared.nkCommonInstance.writeLog("Could not write to database: \(error)")
+        }
+    }
+}

+ 52 - 548
iOSClient/Data/NCManageDatabase.swift

@@ -28,13 +28,17 @@ import NextcloudKit
 import CoreMedia
 import Photos
 
+protocol DateCompareable {
+    var dateKey: Date { get }
+}
+
 class NCManageDatabase: NSObject {
     @objc static let shared: NCManageDatabase = {
         let instance = NCManageDatabase()
         return instance
     }()
 
-    let serialQueue = DispatchQueue(label: "realmSerialQueue")
+    let utilityFileSystem = NCUtilityFileSystem()
 
     override init() {
 
@@ -66,9 +70,7 @@ class NCManageDatabase: NSObject {
 
         if isAppex {
 
-            // App Extension config
-
-            let config = Realm.Configuration(
+            Realm.Configuration.defaultConfiguration = Realm.Configuration(
                 fileURL: dirGroup?.appendingPathComponent(NCGlobal.shared.appDatabaseNextcloud + "/" + databaseName),
                 schemaVersion: databaseSchemaVersion,
                 objectTypes: [tableMetadata.self,
@@ -93,59 +95,54 @@ class NCManageDatabase: NSObject {
                               NCDBLayoutForView.self]
             )
 
-            Realm.Configuration.defaultConfiguration = config
-
         } else {
 
-            // App config
-
-            let configCompact = Realm.Configuration(
-
-                fileURL: databaseFileUrlPath,
-                schemaVersion: databaseSchemaVersion,
-
-                migrationBlock: { migration, oldSchemaVersion in
-
-                    if oldSchemaVersion < 255 {
-                        migration.deleteData(forType: tableActivity.className())
-                        migration.deleteData(forType: tableActivityLatestId.className())
-                        migration.deleteData(forType: tableActivityPreview.className())
-                        migration.deleteData(forType: tableActivitySubjectRich.className())
-                        migration.deleteData(forType: tableDirectory.className())
-                        migration.deleteData(forType: tableMetadata.className())
-                    }
-
-                    if oldSchemaVersion < 292 {
-                        migration.deleteData(forType: tableVideo.className())
-                    }
-
-                    if oldSchemaVersion < 319 {
-                        migration.deleteData(forType: tableChunk.className())
-                        migration.deleteData(forType: tableMetadata.className())
-                        migration.deleteData(forType: tableDirectory.className())
-                        migration.deleteData(forType: tableE2eEncryptionLock.className())
-                        migration.deleteData(forType: tableGPS.className())
+            do {
+                _ = try Realm(configuration: Realm.Configuration(
+
+                    fileURL: databaseFileUrlPath,
+                    schemaVersion: databaseSchemaVersion,
+
+                    migrationBlock: { migration, oldSchemaVersion in
+
+                        if oldSchemaVersion < 255 {
+                            migration.deleteData(forType: tableActivity.className())
+                            migration.deleteData(forType: tableActivityLatestId.className())
+                            migration.deleteData(forType: tableActivityPreview.className())
+                            migration.deleteData(forType: tableActivitySubjectRich.className())
+                            migration.deleteData(forType: tableDirectory.className())
+                            migration.deleteData(forType: tableMetadata.className())
+                        }
+
+                        if oldSchemaVersion < 292 {
+                            migration.deleteData(forType: tableVideo.className())
+                        }
+
+                        if oldSchemaVersion < 319 {
+                            migration.deleteData(forType: tableChunk.className())
+                            migration.deleteData(forType: tableMetadata.className())
+                            migration.deleteData(forType: tableDirectory.className())
+                            migration.deleteData(forType: tableE2eEncryptionLock.className())
+                            migration.deleteData(forType: tableGPS.className())
+                        }
+
+                    }, shouldCompactOnLaunch: { totalBytes, usedBytes in
+
+                        // totalBytes refers to the size of the file on disk in bytes (data + free space)
+                        // usedBytes refers to the number of bytes used by data in the file
+
+                        // Compact if the file is over 100MB in size and less than 50% 'used'
+                        let oneHundredMB = 100 * 1024 * 1024
+                        return (totalBytes > oneHundredMB) && (Double(usedBytes) / Double(totalBytes)) < 0.5
                     }
+                ))
 
-                }, shouldCompactOnLaunch: { totalBytes, usedBytes in
-
-                    // totalBytes refers to the size of the file on disk in bytes (data + free space)
-                    // usedBytes refers to the number of bytes used by data in the file
-
-                    // Compact if the file is over 100MB in size and less than 50% 'used'
-                    let oneHundredMB = 100 * 1024 * 1024
-                    return (totalBytes > oneHundredMB) && (Double(usedBytes) / Double(totalBytes)) < 0.5
-                }
-            )
-
-            do {
-                _ = try Realm(configuration: configCompact)
             } catch let error {
                 if let databaseFileUrlPath = databaseFileUrlPath {
                     do {
 #if !EXTENSION
                         let nkError = NKError(errorCode: NCGlobal.shared.errorInternalError, errorDescription: error.localizedDescription)
-                        NCContentPresenter.shared.showError(error: nkError, priority: .max)
+                        NCContentPresenter().showError(error: nkError, priority: .max)
 #endif
                         NextcloudKit.shared.nkCommonInstance.writeLog("DATABASE ERROR: \(error.localizedDescription)")
                         try FileManager.default.removeItem(at: databaseFileUrlPath)
@@ -153,12 +150,10 @@ class NCManageDatabase: NSObject {
                 }
             }
 
-            let config = Realm.Configuration(
+            Realm.Configuration.defaultConfiguration = Realm.Configuration(
                 fileURL: dirGroup?.appendingPathComponent(NCGlobal.shared.appDatabaseNextcloud + "/" + databaseName),
                 schemaVersion: databaseSchemaVersion
             )
-
-            Realm.Configuration.defaultConfiguration = config
         }
 
         // Verify Database, if corrupt remove it
@@ -169,7 +164,7 @@ class NCManageDatabase: NSObject {
                 do {
 #if !EXTENSION
                     let nkError = NKError(errorCode: NCGlobal.shared.errorInternalError, errorDescription: error.localizedDescription)
-                    NCContentPresenter.shared.showError(error: nkError, priority: .max)
+                    NCContentPresenter().showError(error: nkError, priority: .max)
 #endif
                     NextcloudKit.shared.nkCommonInstance.writeLog("DATABASE ERROR: \(error.localizedDescription)")
                     try FileManager.default.removeItem(at: databaseFileUrlPath)
@@ -177,7 +172,6 @@ class NCManageDatabase: NSObject {
             }
         }
 
-        // Open Real
         do {
             _ = try Realm()
         } catch let error as NSError {
@@ -210,6 +204,10 @@ class NCManageDatabase: NSObject {
 
     @objc func clearDatabase(account: String?, removeAccount: Bool) {
 
+        if removeAccount {
+            self.clearTable(tableAccount.self, account: account)
+        }
+
         self.clearTable(tableActivity.self, account: account)
         self.clearTable(tableActivityLatestId.self, account: account)
         self.clearTable(tableActivityPreview.self, account: account)
@@ -223,6 +221,7 @@ class NCManageDatabase: NSObject {
         self.clearTable(tableDirectEditingCreators.self, account: account)
         self.clearTable(tableDirectEditingEditors.self, account: account)
         self.clearTable(tableDirectory.self, account: account)
+        self.clearTablesE2EE(account: account)
         self.clearTable(tableExternalSites.self, account: account)
         self.clearTable(tableGPS.self, account: nil)
         self.clearTable(TableGroupfolders.self, account: account)
@@ -237,11 +236,6 @@ class NCManageDatabase: NSObject {
         self.clearTable(tableTrash.self, account: account)
         self.clearTable(tableUserStatus.self, account: account)
         self.clearTable(tableVideo.self, account: account)
-        self.clearTablesE2EE(account: account)
-
-        if removeAccount {
-            self.clearTable(tableAccount.self, account: account)
-        }
     }
 
     func clearTablesE2EE(account: String?) {
@@ -289,494 +283,4 @@ class NCManageDatabase: NSObject {
 
         return nil
     }
-
-    func isTableInvalidated(_ object: Object) -> Bool {
-
-        return object.isInvalidated
-    }
-
-    // MARK: -
-    // MARK: Table Direct Editing
-
-    func addDirectEditing(account: String, editors: [NKEditorDetailsEditors], creators: [NKEditorDetailsCreators]) {
-
-        do {
-            let realm = try Realm()
-            try realm.write {
-
-                let resultsCreators = realm.objects(tableDirectEditingCreators.self).filter("account == %@", account)
-                realm.delete(resultsCreators)
-
-                let resultsEditors = realm.objects(tableDirectEditingEditors.self).filter("account == %@", account)
-                realm.delete(resultsEditors)
-
-                for creator in creators {
-
-                    let addObject = tableDirectEditingCreators()
-
-                    addObject.account = account
-                    addObject.editor = creator.editor
-                    addObject.ext = creator.ext
-                    addObject.identifier = creator.identifier
-                    addObject.mimetype = creator.mimetype
-                    addObject.name = creator.name
-                    addObject.templates = creator.templates
-
-                    realm.add(addObject)
-                }
-
-                for editor in editors {
-
-                    let addObject = tableDirectEditingEditors()
-
-                    addObject.account = account
-                    for mimeType in editor.mimetypes {
-                        addObject.mimetypes.append(mimeType)
-                    }
-                    addObject.name = editor.name
-                    if editor.name.lowercased() == NCGlobal.shared.editorOnlyoffice {
-                        addObject.editor = NCGlobal.shared.editorOnlyoffice
-                    } else {
-                        addObject.editor = NCGlobal.shared.editorText
-                    }
-                    for mimeType in editor.optionalMimetypes {
-                        addObject.optionalMimetypes.append(mimeType)
-                    }
-                    addObject.secure = editor.secure
-
-                    realm.add(addObject)
-                }
-            }
-        } catch let error {
-            NextcloudKit.shared.nkCommonInstance.writeLog("Could not write to database: \(error)")
-        }
-    }
-
-    func getDirectEditingCreators(account: String) -> [tableDirectEditingCreators]? {
-
-        do {
-            let realm = try Realm()
-            realm.refresh()
-            let results = realm.objects(tableDirectEditingCreators.self).filter("account == %@", account)
-            if results.isEmpty {
-                return nil
-            } else {
-                return Array(results.map { tableDirectEditingCreators.init(value: $0) })
-            }
-        } catch let error as NSError {
-            NextcloudKit.shared.nkCommonInstance.writeLog("Could not write to database: \(error)")
-        }
-
-        return nil
-    }
-
-    func getDirectEditingCreators(predicate: NSPredicate) -> [tableDirectEditingCreators]? {
-
-        do {
-            let realm = try Realm()
-            realm.refresh()
-            let results = realm.objects(tableDirectEditingCreators.self).filter(predicate)
-            if results.isEmpty {
-                return nil
-            } else {
-                return Array(results.map { tableDirectEditingCreators.init(value: $0) })
-            }
-        } catch let error as NSError {
-            NextcloudKit.shared.nkCommonInstance.writeLog("Could not write to database: \(error)")
-        }
-
-        return nil
-    }
-
-    func getDirectEditingEditors(account: String) -> [tableDirectEditingEditors]? {
-
-        do {
-            let realm = try Realm()
-            realm.refresh()
-            let results = realm.objects(tableDirectEditingEditors.self).filter("account == %@", account)
-            if results.isEmpty {
-                return nil
-            } else {
-                return Array(results.map { tableDirectEditingEditors.init(value: $0) })
-            }
-        } catch let error as NSError {
-            NextcloudKit.shared.nkCommonInstance.writeLog("Could not write to database: \(error)")
-        }
-
-        return nil
-    }
-
-    // MARK: -
-    // MARK: Table External Sites
-
-    func addExternalSites(_ externalSite: NKExternalSite, account: String) {
-
-        do {
-            let realm = try Realm()
-            try realm.write {
-                let addObject = tableExternalSites()
-
-                addObject.account = account
-                addObject.idExternalSite = externalSite.idExternalSite
-                addObject.icon = externalSite.icon
-                addObject.lang = externalSite.lang
-                addObject.name = externalSite.name
-                addObject.url = externalSite.url
-                addObject.type = externalSite.type
-
-                realm.add(addObject)
-            }
-        } catch let error {
-            NextcloudKit.shared.nkCommonInstance.writeLog("Could not write to database: \(error)")
-        }
-    }
-
-    func deleteExternalSites(account: String) {
-
-        do {
-            let realm = try Realm()
-            try realm.write {
-                let results = realm.objects(tableExternalSites.self).filter("account == %@", account)
-                realm.delete(results)
-            }
-        } catch let error {
-            NextcloudKit.shared.nkCommonInstance.writeLog("Could not write to database: \(error)")
-        }
-    }
-
-    func getAllExternalSites(account: String) -> [tableExternalSites]? {
-
-        do {
-            let realm = try Realm()
-            realm.refresh()
-            let results = realm.objects(tableExternalSites.self).filter("account == %@", account).sorted(byKeyPath: "idExternalSite", ascending: true)
-            if results.isEmpty {
-                return nil
-            } else {
-                return Array(results.map { tableExternalSites.init(value: $0) })
-            }
-        } catch let error as NSError {
-            NextcloudKit.shared.nkCommonInstance.writeLog("Could not write to database: \(error)")
-        }
-
-        return nil
-    }
-
-    // MARK: -
-    // MARK: Table GPS
-
-    @objc func addGeocoderLocation(_ location: String, latitude: Double, longitude: Double) {
-
-        do {
-            let realm = try Realm()
-            realm.refresh()
-            guard realm.objects(tableGPS.self).filter("latitude == %@ AND longitude == %@", latitude, longitude).first == nil else { return }
-            try realm.write {
-                let addObject = tableGPS()
-                addObject.latitude = latitude
-                addObject.location = location
-                addObject.longitude = longitude
-                realm.add(addObject)
-            }
-        } catch let error as NSError {
-            NextcloudKit.shared.nkCommonInstance.writeLog("Could not write to database: \(error)")
-        }
-    }
-
-    @objc func getLocationFromLatAndLong(latitude: Double, longitude: Double) -> String? {
-        do {
-            let realm = try Realm()
-            realm.refresh()
-            let result = realm.objects(tableGPS.self).filter("latitude == %@ AND longitude == %@", latitude, longitude).first
-            return result?.location
-        } catch let error as NSError {
-            NextcloudKit.shared.nkCommonInstance.writeLog("Could not write to database: \(error)")
-        }
-
-        return nil
-    }
-
-    // MARK: Table Photo Library
-
-    @discardableResult
-    func addPhotoLibrary(_ assets: [PHAsset], account: String) -> Bool {
-
-        do {
-            let realm = try Realm()
-            try realm.write {
-                for asset in assets {
-                    var creationDateString = ""
-                    let addObject = tablePhotoLibrary()
-                    addObject.account = account
-                    addObject.assetLocalIdentifier = asset.localIdentifier
-                    addObject.mediaType = asset.mediaType.rawValue
-                    if let creationDate = asset.creationDate {
-                        addObject.creationDate = creationDate as NSDate
-                        creationDateString = String(describing: creationDate)
-                    }
-                    if let modificationDate = asset.modificationDate {
-                        addObject.modificationDate = modificationDate as NSDate
-                    }
-                    addObject.idAsset = account + asset.localIdentifier + creationDateString
-                    realm.add(addObject, update: .all)
-                }
-            }
-        } catch let error {
-            NextcloudKit.shared.nkCommonInstance.writeLog("Could not write to database: \(error)")
-            return false
-        }
-
-        return true
-    }
-
-    func getPhotoLibraryIdAsset(image: Bool, video: Bool, account: String) -> [String]? {
-
-        var predicate = NSPredicate()
-
-        if image && video {
-            predicate = NSPredicate(format: "account == %@ AND (mediaType == %d OR mediaType == %d)", account, PHAssetMediaType.image.rawValue, PHAssetMediaType.video.rawValue)
-        } else if image {
-            predicate = NSPredicate(format: "account == %@ AND mediaType == %d", account, PHAssetMediaType.image.rawValue)
-        } else if video {
-            predicate = NSPredicate(format: "account == %@ AND mediaType == %d", account, PHAssetMediaType.video.rawValue)
-        }
-
-        do {
-            let realm = try Realm()
-            realm.refresh()
-            let results = realm.objects(tablePhotoLibrary.self).filter(predicate)
-            let idsAsset = results.map { $0.idAsset }
-            return Array(idsAsset)
-        } catch let error as NSError {
-            NextcloudKit.shared.nkCommonInstance.writeLog("Could not write to database: \(error)")
-        }
-
-        return nil
-    }
-
-    // MARK: -
-    // MARK: Table Tag
-
-    func addTag(_ ocId: String, tagIOS: Data?, account: String) {
-
-        do {
-            let realm = try Realm()
-            try realm.write {
-                let addObject = tableTag()
-                addObject.account = account
-                addObject.ocId = ocId
-                addObject.tagIOS = tagIOS
-                realm.add(addObject, update: .all)
-            }
-        } catch let error {
-            NextcloudKit.shared.nkCommonInstance.writeLog("Could not write to database: \(error)")
-        }
-    }
-
-    func deleteTag(_ ocId: String) {
-
-        do {
-            let realm = try Realm()
-            try realm.write {
-                let results = realm.objects(tableTag.self).filter("ocId == %@", ocId)
-                realm.delete(results)
-            }
-        } catch let error {
-            NextcloudKit.shared.nkCommonInstance.writeLog("Could not write to database: \(error)")
-        }
-    }
-
-    func getTags(predicate: NSPredicate) -> [tableTag] {
-
-        do {
-            let realm = try Realm()
-            realm.refresh()
-            let results = realm.objects(tableTag.self).filter(predicate)
-            return Array(results.map { tableTag.init(value: $0) })
-        } catch let error as NSError {
-            NextcloudKit.shared.nkCommonInstance.writeLog("Could not write to database: \(error)")
-        }
-
-        return []
-    }
-
-    func getTag(predicate: NSPredicate) -> tableTag? {
-
-        do {
-            let realm = try Realm()
-            realm.refresh()
-            guard let result = realm.objects(tableTag.self).filter(predicate).first else { return nil }
-            return tableTag.init(value: result)
-        } catch let error as NSError {
-            NextcloudKit.shared.nkCommonInstance.writeLog("Could not write to database: \(error)")
-        }
-
-        return nil
-    }
-
-    // MARK: -
-    // MARK: Table Tip
-
-    func tipExists(_ tipName: String) -> Bool {
-
-        do {
-            let realm = try Realm()
-            guard (realm.objects(tableTip.self).where {
-                $0.tipName == tipName
-            }.first) == nil else {
-                return true
-            }
-        } catch let error as NSError {
-            NextcloudKit.shared.nkCommonInstance.writeLog("Could not write to database: \(error)")
-        }
-
-        return false
-    }
-
-    func addTip(_ tipName: String) {
-
-        do {
-            let realm = try Realm()
-            try realm.write {
-                let addObject = tableTip()
-                addObject.tipName = tipName
-                realm.add(addObject, update: .all)
-            }
-        } catch let error {
-            NextcloudKit.shared.nkCommonInstance.writeLog("Could not write to database: \(error)")
-        }
-    }
-
-    // MARK: -
-    // MARK: Table Trash
-
-    func addTrash(account: String, items: [NKTrash]) {
-
-        do {
-            let realm = try Realm()
-            try realm.write {
-                for trash in items {
-                    let object = tableTrash()
-                    object.account = account
-                    object.contentType = trash.contentType
-                    object.date = trash.date
-                    object.directory = trash.directory
-                    object.fileId = trash.fileId
-                    object.fileName = trash.fileName
-                    object.filePath = trash.filePath
-                    object.hasPreview = trash.hasPreview
-                    object.iconName = trash.iconName
-                    object.size = trash.size
-                    object.trashbinDeletionTime = trash.trashbinDeletionTime
-                    object.trashbinFileName = trash.trashbinFileName
-                    object.trashbinOriginalLocation = trash.trashbinOriginalLocation
-                    object.classFile = trash.classFile
-                    realm.add(object, update: .all)
-                }
-            }
-        } catch let error {
-            NextcloudKit.shared.nkCommonInstance.writeLog("Could not write to database: \(error)")
-        }
-    }
-
-    func deleteTrash(filePath: String?, account: String) {
-
-        var predicate = NSPredicate()
-
-        do {
-            let realm = try Realm()
-            try realm.write {
-                if filePath == nil {
-                    predicate = NSPredicate(format: "account == %@", account)
-                } else {
-                    predicate = NSPredicate(format: "account == %@ AND filePath == %@", account, filePath!)
-                }
-                let result = realm.objects(tableTrash.self).filter(predicate)
-                realm.delete(result)
-            }
-        } catch let error {
-            NextcloudKit.shared.nkCommonInstance.writeLog("Could not write to database: \(error)")
-        }
-    }
-
-    func deleteTrash(fileId: String?, account: String) {
-
-        var predicate = NSPredicate()
-
-        do {
-            let realm = try Realm()
-            try realm.write {
-                if fileId == nil {
-                    predicate = NSPredicate(format: "account == %@", account)
-                } else {
-                    predicate = NSPredicate(format: "account == %@ AND fileId == %@", account, fileId!)
-                }
-                let result = realm.objects(tableTrash.self).filter(predicate)
-                realm.delete(result)
-            }
-        } catch let error {
-            NextcloudKit.shared.nkCommonInstance.writeLog("Could not write to database: \(error)")
-        }
-    }
-
-    func getTrash(filePath: String, sort: String?, ascending: Bool?, account: String) -> [tableTrash]? {
-
-        let sort = sort ?? "date"
-        let ascending = ascending ?? false
-
-        do {
-            let realm = try Realm()
-            realm.refresh()
-            let results = realm.objects(tableTrash.self).filter("account == %@ AND filePath == %@", account, filePath).sorted(byKeyPath: sort, ascending: ascending)
-            return Array(results.map { tableTrash.init(value: $0) })
-        } catch let error as NSError {
-            NextcloudKit.shared.nkCommonInstance.writeLog("Could not write to database: \(error)")
-        }
-
-        return nil
-    }
-
-    func getTrashItem(fileId: String, account: String) -> tableTrash? {
-
-        do {
-            let realm = try Realm()
-            realm.refresh()
-            guard let result = realm.objects(tableTrash.self).filter("account == %@ AND fileId == %@", account, fileId).first else { return nil }
-            return tableTrash.init(value: result)
-        } catch let error as NSError {
-            NextcloudKit.shared.nkCommonInstance.writeLog("Could not write to database: \(error)")
-        }
-
-        return nil
-    }
-
-    // MARK: -
-    // MARK: Table UserStatus
-
-    func addUserStatus(_ userStatuses: [NKUserStatus], account: String, predefined: Bool) {
-
-        do {
-            let realm = try Realm()
-            try realm.write {
-                let results = realm.objects(tableUserStatus.self).filter("account == %@ AND predefined == %@", account, predefined)
-                realm.delete(results)
-                for userStatus in userStatuses {
-                    let object = tableUserStatus()
-                    object.account = account
-                    object.clearAt = userStatus.clearAt
-                    object.clearAtTime = userStatus.clearAtTime
-                    object.clearAtType = userStatus.clearAtType
-                    object.icon = userStatus.icon
-                    object.id = userStatus.id
-                    object.message = userStatus.message
-                    object.predefined = userStatus.predefined
-                    object.status = userStatus.status
-                    object.userId = userStatus.userId
-                    realm.add(object)
-                }
-            }
-        } catch let error {
-            NextcloudKit.shared.nkCommonInstance.writeLog("Could not write to database: \(error)")
-        }
-    }
 }

+ 18 - 2
iOSClient/Diagnostics/NCCapabilitiesView.swift

@@ -5,6 +5,21 @@
 //  Created by Marino Faggiana on 19/05/23.
 //  Copyright © 2023 Marino Faggiana. All rights reserved.
 //
+//  Author Marino Faggiana <marino.faggiana@nextcloud.com>
+//
+//  This program is free software: you can redistribute it and/or modify
+//  it under the terms of the GNU General Public License as published by
+//  the Free Software Foundation, either version 3 of the License, or
+//  (at your option) any later version.
+//
+//  This program is distributed in the hope that it will be useful,
+//  but WITHOUT ANY WARRANTY; without even the implied warranty of
+//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+//  GNU General Public License for more details.
+//
+//  You should have received a copy of the GNU General Public License
+//  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+//
 
 import SwiftUI
 import NextcloudKit
@@ -34,6 +49,7 @@ class NCCapabilitiesViewOO: ObservableObject {
 
     @Published var capabililies: [Capability] = []
     @Published var homeServer = ""
+    let utilityFileSystem = NCUtilityFileSystem()
 
     init() {
         guard let activeAccount = NCManageDatabase.shared.getActiveAccount() else { return }
@@ -76,7 +92,7 @@ class NCCapabilitiesViewOO: ObservableObject {
             capabililies.append(Capability(text: "ONLYOFFICE", image: image, resize: true, available: onlyofficeEditors))
         }
         if let image = UIImage(named: "collabora") {
-            capabililies.append(Capability(text: "Collabora", image: image, resize: true, available: !NCGlobal.shared.capabilityRichdocumentsMimetypes.isEmpty))
+            capabililies.append(Capability(text: "Collabora", image: image, resize: true, available: NCGlobal.shared.capabilityRichdocumentsEnabled))
         }
         if let image = UIImage(systemName: "moon") {
             capabililies.append(Capability(text: "User Status", image: image, resize: false, available: NCGlobal.shared.capabilityUserStatusEnabled))
@@ -91,7 +107,7 @@ class NCCapabilitiesViewOO: ObservableObject {
             capabililies.append(Capability(text: "Group folders", image: image, resize: false, available: NCGlobal.shared.capabilityGroupfoldersEnabled))
         }
 
-        homeServer = NCUtilityFileSystem.shared.getHomeServer(urlBase: activeAccount.urlBase, userId: activeAccount.userId) + "/"
+        homeServer = utilityFileSystem.getHomeServer(urlBase: activeAccount.urlBase, userId: activeAccount.userId) + "/"
     }
 }
 

+ 15 - 0
iOSClient/Extensions/Optional+Extension.swift

@@ -5,6 +5,21 @@
 //  Created by Milen on 24.05.23.
 //  Copyright © 2023 Marino Faggiana. All rights reserved.
 //
+//  Author Marino Faggiana <marino.faggiana@nextcloud.com>
+//
+//  This program is free software: you can redistribute it and/or modify
+//  it under the terms of the GNU General Public License as published by
+//  the Free Software Foundation, either version 3 of the License, or
+//  (at your option) any later version.
+//
+//  This program is distributed in the hope that it will be useful,
+//  but WITHOUT ANY WARRANTY; without even the implied warranty of
+//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+//  GNU General Public License for more details.
+//
+//  You should have received a copy of the GNU General Public License
+//  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+//
 
 import Foundation
 

+ 16 - 1
iOSClient/Extensions/PHAsset+Extension.swift

@@ -5,6 +5,21 @@
 //  Created by Milen on 24.05.23.
 //  Copyright © 2023 Marino Faggiana. All rights reserved.
 //
+//  Author Marino Faggiana <marino.faggiana@nextcloud.com>
+//
+//  This program is free software: you can redistribute it and/or modify
+//  it under the terms of the GNU General Public License as published by
+//  the Free Software Foundation, either version 3 of the License, or
+//  (at your option) any later version.
+//
+//  This program is distributed in the hope that it will be useful,
+//  but WITHOUT ANY WARRANTY; without even the implied warranty of
+//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+//  GNU General Public License for more details.
+//
+//  You should have received a copy of the GNU General Public License
+//  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+//
 
 import Foundation
 import UIKit
@@ -15,7 +30,7 @@ extension PHAsset {
             return resource.originalFilename as NSString
         } else {
             return self.value(forKey: "filename") as? NSString
-            ?? ("IMG_" + CCUtility.getIncrementalNumber() + getExtension()) as NSString
+            ?? ("IMG_" + NCKeychain().incrementalNumber + getExtension()) as NSString
         }
     }
 

+ 5 - 5
iOSClient/Extensions/UIAlertController+Extension.swift

@@ -43,10 +43,10 @@ extension UIAlertController {
                     if createFolderResults.error == .success {
                         let error = await NCNetworkingE2EEMarkFolder().markFolderE2ee(account: urlBase.account, fileName: fileNameFolder, serverUrl: serverUrl, userId: urlBase.userId)
                         if error != .success {
-                            NCContentPresenter.shared.showError(error: error)
+                            NCContentPresenter().showError(error: error)
                         }
                     } else {
-                        NCContentPresenter.shared.showError(error: createFolderResults.error)
+                        NCContentPresenter().showError(error: createFolderResults.error)
                     }
                 }
             } else {
@@ -54,7 +54,7 @@ extension UIAlertController {
                     if let completion = completion {
                         completion(error)
                     } else if error != .success {
-                        NCContentPresenter.shared.showError(error: error)
+                        NCContentPresenter().showError(error: error)
                     } // else: successful, no action
                 }
             }
@@ -73,8 +73,8 @@ extension UIAlertController {
             forName: UITextField.textDidChangeNotification,
             object: alertController.textFields?.first,
             queue: .main) { _ in
-                guard let text = alertController.textFields?.first?.text,
-                      let folderName = CCUtility.removeForbiddenCharactersServer(text)?.trimmingCharacters(in: .whitespaces) else { return }
+                guard let text = alertController.textFields?.first?.text else { return }
+                let folderName = NCUtility().removeForbiddenCharacters(text).trimmingCharacters(in: .whitespaces)
                 okAction.isEnabled = !folderName.isEmpty && folderName != "." && folderName != ".."
             }
 

+ 31 - 0
iOSClient/Extensions/UIImage+Extension.swift

@@ -232,4 +232,35 @@ extension UIImage {
         }
         return image
     }
+
+    var heic: Data? { heic() }
+    var cgImageOrientation: CGImagePropertyOrientation { .init(imageOrientation) }
+
+    func heic(compressionQuality: CGFloat = 1) -> Data? {
+        guard
+            let mutableData = CFDataCreateMutable(nil, 0),
+            let destination = CGImageDestinationCreateWithData(mutableData, "public.heic" as CFString, 1, nil),
+            let cgImage = cgImage
+        else { return nil }
+        CGImageDestinationAddImage(destination, cgImage, [kCGImageDestinationLossyCompressionQuality: compressionQuality, kCGImagePropertyOrientation: cgImageOrientation.rawValue] as CFDictionary)
+        guard CGImageDestinationFinalize(destination) else { return nil }
+        return mutableData as Data
+    }
+}
+
+extension CGImagePropertyOrientation {
+    init(_ uiOrientation: UIImage.Orientation) {
+        switch uiOrientation {
+        case .up: self = .up
+        case .upMirrored: self = .upMirrored
+        case .down: self = .down
+        case .downMirrored: self = .downMirrored
+        case .left: self = .left
+        case .leftMirrored: self = .leftMirrored
+        case .right: self = .right
+        case .rightMirrored: self = .rightMirrored
+        @unknown default:
+            fatalError()
+        }
+    }
 }

+ 1 - 1
iOSClient/Favorites/NCFavorite.swift

@@ -91,7 +91,7 @@ class NCFavorite: NCCollectionViewCommon {
         isReloadDataSourceNetworkInProgress = true
         collectionView?.reloadData()
 
-        NextcloudKit.shared.listingFavorites(showHiddenFiles: CCUtility.getShowHiddenFiles(),
+        NextcloudKit.shared.listingFavorites(showHiddenFiles: NCKeychain().showHiddenFiles,
                                              options: NKRequestOptions(queue: NextcloudKit.shared.nkCommonInstance.backgroundQueue)) { account, files, _, error in
 
             self.isReloadDataSourceNetworkInProgress = false

+ 5 - 3
iOSClient/Files/NCFiles.swift

@@ -54,7 +54,7 @@ class NCFiles: NCCollectionViewCommon {
 
                 self.navigationController?.popToRootViewController(animated: false)
 
-                self.serverUrl = NCUtilityFileSystem.shared.getHomeServer(urlBase: self.appDelegate.urlBase, userId: self.appDelegate.userId)
+                self.serverUrl = self.utilityFileSystem.getHomeServer(urlBase: self.appDelegate.urlBase, userId: self.appDelegate.userId)
                 self.appDelegate.activeServerUrl = self.serverUrl
 
                 self.isSearchingMode = false
@@ -82,7 +82,7 @@ class NCFiles: NCCollectionViewCommon {
     override func viewWillAppear(_ animated: Bool) {
 
         if isRoot {
-            serverUrl = NCUtilityFileSystem.shared.getHomeServer(urlBase: appDelegate.urlBase, userId: appDelegate.userId)
+            serverUrl = utilityFileSystem.getHomeServer(urlBase: appDelegate.urlBase, userId: appDelegate.userId)
             titleCurrentFolder = getNavigationTitle()
         }
         super.viewWillAppear(animated)
@@ -165,7 +165,9 @@ class NCFiles: NCCollectionViewCommon {
         networkReadFolder(isForced: isForced) { tableDirectory, metadatas, metadatasUpdate, metadatasDelete, error in
             if error == .success {
                 for metadata in metadatas ?? [] where !metadata.directory && NCManageDatabase.shared.isDownloadMetadata(metadata, download: false) {
-                    NCOperationQueue.shared.download(metadata: metadata, selector: NCGlobal.shared.selectorDownloadFile)
+                    if self.appDelegate.downloadQueue.operations.filter({ ($0 as? NCOperationDownload)?.metadata.ocId == metadata.ocId }).isEmpty {
+                        self.appDelegate.downloadQueue.addOperation(NCOperationDownload(metadata: metadata, selector: NCGlobal.shared.selectorDownloadFile))
+                    }
                 }
             }
 

+ 3 - 3
iOSClient/Groupfolders/NCGroupfolders.swift

@@ -91,7 +91,7 @@ class NCGroupfolders: NCCollectionViewCommon {
         isReloadDataSourceNetworkInProgress = true
         collectionView?.reloadData()
 
-        let homeServerUrl = NCUtilityFileSystem.shared.getHomeServer(urlBase: self.appDelegate.urlBase, userId: self.appDelegate.userId)
+        let homeServerUrl = utilityFileSystem.getHomeServer(urlBase: self.appDelegate.urlBase, userId: self.appDelegate.userId)
 
         NextcloudKit.shared.getGroupfolders(options: NKRequestOptions(queue: NextcloudKit.shared.nkCommonInstance.backgroundQueue)) { account, results, _, error in
 
@@ -102,9 +102,9 @@ class NCGroupfolders: NCCollectionViewCommon {
                         let mountPoint = groupfolder.mountPoint.hasPrefix("/") ? groupfolder.mountPoint : "/" + groupfolder.mountPoint
                         let serverUrlFileName = homeServerUrl + mountPoint
                         if NCManageDatabase.shared.getMetadataFromDirectory(account: self.appDelegate.account, serverUrl: serverUrlFileName) == nil {
-                            let results = await NextcloudKit.shared.readFileOrFolder(serverUrlFileName: serverUrlFileName, depth: "0", showHiddenFiles: CCUtility.getShowHiddenFiles())
+                            let results = await NextcloudKit.shared.readFileOrFolder(serverUrlFileName: serverUrlFileName, depth: "0", showHiddenFiles: NCKeychain().showHiddenFiles)
                             if results.error == .success, let file = results.files.first {
-                                let isDirectoryE2EE = NCUtility.shared.isDirectoryE2EE(file: file)
+                                let isDirectoryE2EE = self.utilityFileSystem.isDirectoryE2EE(file: file)
                                 let metadata = NCManageDatabase.shared.convertFileToMetadata(file, isDirectoryE2EE: isDirectoryE2EE)
                                 NCManageDatabase.shared.addMetadata(metadata)
                                 NCManageDatabase.shared.addDirectory(encrypted: isDirectoryE2EE, favorite: metadata.favorite, ocId: metadata.ocId, fileId: metadata.fileId, etag: nil, permissions: metadata.permissions, serverUrl: serverUrlFileName, account: metadata.account)

+ 12 - 0
iOSClient/Images.xcassets/contact.imageset/Contents.json

@@ -0,0 +1,12 @@
+{
+  "images" : [
+    {
+      "filename" : "contact.svg",
+      "idiom" : "universal"
+    }
+  ],
+  "info" : {
+    "author" : "xcode",
+    "version" : 1
+  }
+}

+ 7 - 0
iOSClient/Images.xcassets/contact.imageset/contact.svg

@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <title>01_24px/icon/user_file/contacts/default</title>
+    <g id="icon/user_file/contacts/default" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+        <path d="M18,1.49999996 C19.5910715,1.49999996 20.9031887,2.75529333 20.994882,4.32441003 L21,4.49999996 L21,19.5 C21,21.0910715 19.7447067,22.4031887 18.1755899,22.494882 L18,22.5 L2.99999996,22.5 L2.99999996,1.49999996 L18,1.49999996 Z M18,2.99999996 L4.49999997,2.99999996 L4.49999997,21 L18,21 C18.8,21 19.4228373,20.4242215 19.4933442,19.6478323 L19.5,19.5 L19.5,4.49999996 C19.5,3.69999997 18.9242215,3.07716259 18.1478323,3.00665577 L18,2.99999996 Z M14,13.4 C14.75,13.4 15.3681641,13.9273438 15.5661011,14.6524414 L15.6,14.8 L15.75,15.5 L8.24999998,15.5 L8.34999998,14.8 C8.49062499,14.05 9.11464843,13.5197266 9.85128173,13.4563721 L9.99999999,13.45 L10.85,13.45 L12,14.6 L13.2,13.4 L14,13.4 Z M12,7.99999998 C13.15,7.99999998 14.1,8.84999999 14.1,10.1 C14.1,11.35 13.2,12.6 12,12.6 C10.8,12.6 9.89999999,11.35 9.89999999,10.1 C9.89999999,8.84999999 10.85,7.99999998 12,7.99999998 Z" id="Shape" fill="#000000"></path>
+    </g>
+</svg>

+ 4 - 11
iOSClient/Login/NCLogin.swift

@@ -36,10 +36,7 @@ class NCLogin: UIViewController, UITextFieldDelegate, NCLoginQRCodeDelegate {
     @IBOutlet weak var qrCode: UIButton!
     @IBOutlet weak var certificate: UIButton!
 
-    // swiftlint:disable force_cast
-    private let appDelegate = UIApplication.shared.delegate as! AppDelegate
-    // swiftlint:enable force_cast
-
+    private let appDelegate = (UIApplication.shared.delegate as? AppDelegate)!
     private var textColor: UIColor = .white
     private var textColorOpponent: UIColor = .black
     private var activeTextfieldDiff: CGFloat = 0
@@ -156,7 +153,7 @@ class NCLogin: UIViewController, UITextFieldDelegate, NCLoginQRCodeDelegate {
         if self.shareAccounts != nil, let image = UIImage(systemName: "person.badge.plus")?.withTintColor(.white, renderingMode: .alwaysOriginal), let backgroundColor = NCBrandColor.shared.brandElement.lighter(by: 10) {
             let title = String(format: NSLocalizedString("_apps_nextcloud_detect_", comment: ""), NCBrandOptions.shared.brand)
             let description = String(format: NSLocalizedString("_add_existing_account_", comment: ""), NCBrandOptions.shared.brand)
-            NCContentPresenter.shared.alertAction(image: image, contentModeImage: .scaleAspectFit, sizeImage: CGSize(width: 45, height: 45), backgroundColor: backgroundColor, textColor: textColor, title: title, description: description, textCancelButton: "_cancel_", textOkButton: "_ok_", attributes: EKAttributes.topFloat) { identifier in
+            NCContentPresenter().alertAction(image: image, contentModeImage: .scaleAspectFit, sizeImage: CGSize(width: 45, height: 45), backgroundColor: backgroundColor, textColor: textColor, title: title, description: description, textCancelButton: "_cancel_", textOkButton: "_ok_", attributes: EKAttributes.topFloat) { identifier in
                 if identifier == "ok" {
                     self.openShareAccountsViewController()
                 }
@@ -398,19 +395,15 @@ class NCLogin: UIViewController, UITextFieldDelegate, NCLoginQRCodeDelegate {
 
                 if error == .success, let userProfile {
 
-                    if NCManageDatabase.shared.getAccounts() == nil {
-                        NCUtility.shared.removeAllSettings()
-                    }
-
                     NCManageDatabase.shared.deleteAccount(account)
                     NCManageDatabase.shared.addAccount(account, urlBase: url, user: user, userId: userProfile.userId, password: password)
 
                     self.appDelegate.changeAccount(account, userProfile: userProfile)
 
-                    if CCUtility.getIntro() {
+                    if NCKeychain().intro {
                         self.dismiss(animated: true)
                     } else {
-                        CCUtility.setIntro(true)
+                        NCKeychain().intro = true
                         if self.presentingViewController == nil {
                             let viewController = UIStoryboard(name: "Main", bundle: nil).instantiateInitialViewController()
                             viewController?.modalPresentationStyle = .fullScreen

+ 8 - 13
iOSClient/Login/NCLoginWeb.swift

@@ -30,9 +30,8 @@ class NCLoginWeb: UIViewController {
 
     var webView: WKWebView?
 
-    // swiftlint:disable force_cast
-    let appDelegate = UIApplication.shared.delegate as! AppDelegate
-    // swiftlint:enable force_cast
+    let appDelegate = (UIApplication.shared.delegate as? AppDelegate)!
+    let utility = NCUtility()
 
     var titleView: String = ""
 
@@ -125,13 +124,13 @@ class NCLoginWeb: UIViewController {
             loadWebPage(webView: webView!, url: url)
         } else {
             let error = NKError(errorCode: NCGlobal.shared.errorInternalError, errorDescription: "_login_url_error_")
-            NCContentPresenter.shared.showError(error: error, priority: .max)
+            NCContentPresenter().showError(error: error, priority: .max)
         }
 
         // TITLE
         if let host = URL(string: urlBase)?.host {
             titleView = host
-            if let account = NCManageDatabase.shared.getActiveAccount(), CCUtility.getPassword(account.account).isEmpty {
+            if let account = NCManageDatabase.shared.getActiveAccount(), NCKeychain().getPassword(account: account.account).isEmpty {
                 titleView = NSLocalizedString("_user_", comment: "") + " " + account.userId + " " + NSLocalizedString("_in_", comment: "") + " " + host
             }
         }
@@ -144,7 +143,7 @@ class NCLoginWeb: UIViewController {
         // Stop timer error network
         appDelegate.timerErrorNetworking?.invalidate()
 
-        if let account = NCManageDatabase.shared.getActiveAccount(), CCUtility.getPassword(account.account).isEmpty {
+        if let account = NCManageDatabase.shared.getActiveAccount(), NCKeychain().getPassword(account: account.account).isEmpty {
 
             let message = "\n" + NSLocalizedString("_password_not_present_", comment: "")
             let alertController = UIAlertController(title: titleView, message: message, preferredStyle: .alert)
@@ -190,7 +189,7 @@ class NCLoginWeb: UIViewController {
             if error == .success, let password = token {
                 self.createAccount(server: serverUrl, username: username, password: password)
             } else {
-                NCContentPresenter.shared.showError(error: error)
+                NCContentPresenter().showError(error: error)
                 self.dismiss(animated: true, completion: nil)
             }
         }
@@ -294,19 +293,15 @@ extension NCLoginWeb: WKNavigationDelegate {
 
             if error == .success, let userProfile {
 
-                if NCManageDatabase.shared.getAccounts() == nil {
-                    NCUtility.shared.removeAllSettings()
-                }
-
                 NCManageDatabase.shared.deleteAccount(account)
                 NCManageDatabase.shared.addAccount(account, urlBase: urlBase, user: user, userId: userProfile.userId, password: password)
 
                 self.appDelegate.changeAccount(account, userProfile: userProfile)
 
-                if CCUtility.getIntro() {
+                if NCKeychain().intro {
                     self.dismiss(animated: true)
                 } else {
-                    CCUtility.setIntro(true)
+                    NCKeychain().intro = true
                     if self.presentingViewController == nil {
                         if let viewController = UIStoryboard(name: "Main", bundle: nil).instantiateInitialViewController() {
                             viewController.modalPresentationStyle = .fullScreen

+ 3 - 2
iOSClient/Login/NCViewCertificateDetails.swift

@@ -39,7 +39,8 @@ class NCViewCertificateDetails: UIViewController {
     @IBOutlet weak var scrollView: UIScrollView!
     @IBOutlet weak var textView: UITextView!
 
-    public var delegate: NCViewCertificateDetailsDelegate?
+    var delegate: NCViewCertificateDetailsDelegate?
+    let utilityFileSystem = NCUtilityFileSystem()
     @objc public var host: String = ""
     public var fileNamePath: String = ""
     @objc public var certificateTitle = NSLocalizedString("_certificate_view_", comment: "")
@@ -53,7 +54,7 @@ class NCViewCertificateDetails: UIViewController {
         buttonCancel.title = NSLocalizedString("_close_", comment: "")
 
         if fileNamePath.isEmpty {
-            fileNamePath = CCUtility.getDirectoryCerificates()! + "/" + host + ".txt"
+            fileNamePath = utilityFileSystem.directoryCertificates + "/" + host + ".txt"
         }
 
         if FileManager.default.fileExists(atPath: fileNamePath) {

+ 202 - 90
iOSClient/Main/Collection Common/NCCollectionViewCommon.swift

@@ -26,15 +26,15 @@ import Realm
 import NextcloudKit
 import EasyTipView
 import JGProgressHUD
+import Queuer
 
 class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UISearchResultsUpdating, UISearchControllerDelegate, UISearchBarDelegate, NCListCellDelegate, NCGridCellDelegate, NCSectionHeaderMenuDelegate, NCSectionFooterDelegate, UIAdaptivePresentationControllerDelegate, NCEmptyDataSetDelegate, UIContextMenuInteractionDelegate, NCAccountRequestDelegate, NCSelectableNavigationView {
 
     @IBOutlet weak var collectionView: UICollectionView!
 
-    // swiftlint:disable force_cast
-    let appDelegate = UIApplication.shared.delegate as! AppDelegate
-    // swiftlint:enable force_cast
-
+    internal let appDelegate = (UIApplication.shared.delegate as? AppDelegate)!
+    internal let utilityFileSystem = NCUtilityFileSystem()
+    internal let utility = NCUtility()
     internal let refreshControl = UIRefreshControl()
     internal var searchController: UISearchController?
     internal var emptyDataSet: NCEmptyDataSet?
@@ -201,7 +201,7 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS
         NotificationCenter.default.addObserver(self, selector: #selector(triggerProgressTask(_:)), name: NSNotification.Name(rawValue: NCGlobal.shared.notificationCenterProgressTask), object: nil)
 
         if serverUrl.isEmpty {
-            appDelegate.activeServerUrl = NCUtilityFileSystem.shared.getHomeServer(urlBase: appDelegate.urlBase, userId: appDelegate.userId)
+            appDelegate.activeServerUrl = utilityFileSystem.getHomeServer(urlBase: appDelegate.urlBase, userId: appDelegate.userId)
         } else {
             appDelegate.activeServerUrl = serverUrl
         }
@@ -320,21 +320,12 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS
 
         guard let userInfo = notification.userInfo as NSDictionary?,
               let error = userInfo["error"] as? NKError else { return }
-        let onlyLocalCache: Bool = userInfo["onlyLocalCache"] as? Bool ?? false
 
         self.queryDB(isForced: true)
+        self.collectionView?.reloadData()
 
-        if error == .success, let indexPath = userInfo["indexPath"] as? [IndexPath], !indexPath.isEmpty, !onlyLocalCache {
-            collectionView?.performBatchUpdates({
-                collectionView?.deleteItems(at: indexPath)
-            }, completion: { _ in
-                self.collectionView?.reloadData()
-            })
-        } else {
-            if error != .success {
-                NCContentPresenter.shared.showError(error: error)
-            }
-            self.collectionView?.reloadData()
+        if error != .success {
+            NCContentPresenter().showError(error: error)
         }
 
         if let hud = userInfo["hud"] as? JGProgressHUD {
@@ -539,7 +530,7 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS
                 if progressNumber.floatValue == 1 && !(cell is NCTransferCell) {
                     cell.fileProgressView?.isHidden = true
                     cell.fileProgressView?.progress = .zero
-                    cell.setButtonMore(named: NCGlobal.shared.buttonMoreMore, image: NCBrandColor.cacheImages.buttonMore)
+                    cell.setButtonMore(named: NCGlobal.shared.buttonMoreMore, image: NCImageCache.images.buttonMore)
                     if let metadata = NCManageDatabase.shared.getMetadataFromOcId(ocId) {
                         cell.writeInfoDateSize(date: metadata.date, size: metadata.size)
                     } else {
@@ -548,14 +539,14 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS
                 } else {
                     cell.fileProgressView?.isHidden = false
                     cell.fileProgressView?.progress = progressNumber.floatValue
-                    cell.setButtonMore(named: NCGlobal.shared.buttonMoreStop, image: NCBrandColor.cacheImages.buttonStop)
+                    cell.setButtonMore(named: NCGlobal.shared.buttonMoreStop, image: NCImageCache.images.buttonStop)
                     if status == NCGlobal.shared.metadataStatusInDownload {
-                        cell.fileInfoLabel?.text = CCUtility.transformedSize(totalBytesExpected) + " - ↓ " + CCUtility.transformedSize(totalBytes)
+                        cell.fileInfoLabel?.text = utilityFileSystem.transformedSize(totalBytesExpected) + " - ↓ " + utilityFileSystem.transformedSize(totalBytes)
                     } else if status == NCGlobal.shared.metadataStatusInUpload {
                         if totalBytes > 0 {
-                            cell.fileInfoLabel?.text = CCUtility.transformedSize(totalBytesExpected) + " - ↑ " + CCUtility.transformedSize(totalBytes)
+                            cell.fileInfoLabel?.text = utilityFileSystem.transformedSize(totalBytesExpected) + " - ↑ " + utilityFileSystem.transformedSize(totalBytes)
                         } else {
-                            cell.fileInfoLabel?.text = CCUtility.transformedSize(totalBytesExpected) + " - ↑ …"
+                            cell.fileInfoLabel?.text = utilityFileSystem.transformedSize(totalBytesExpected) + " - ↑ …"
                         }
                     }
                 }
@@ -567,7 +558,7 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS
 
     func showTip() {
 
-        if self is NCFiles, self.view.window != nil, !NCBrandOptions.shared.disable_multiaccount, !NCBrandOptions.shared.disable_manage_account, self.serverUrl == NCUtilityFileSystem.shared.getHomeServer(urlBase: appDelegate.urlBase, userId: appDelegate.userId), let view = self.navigationItem.leftBarButtonItem?.customView {
+        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)
             }
@@ -587,7 +578,7 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS
 
         let activeAccount = NCManageDatabase.shared.getActiveAccount()
 
-        let image = NCUtility.shared.loadUserImage(
+        let image = utility.loadUserImage(
             for: appDelegate.user,
                displayName: activeAccount?.displayName,
                userBaseUrl: appDelegate)
@@ -595,7 +586,7 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS
         let button = UIButton(type: .custom)
         button.setImage(image, for: .normal)
 
-        if serverUrl == NCUtilityFileSystem.shared.getHomeServer(urlBase: appDelegate.urlBase, userId: appDelegate.userId) {
+        if serverUrl == utilityFileSystem.getHomeServer(urlBase: appDelegate.urlBase, userId: appDelegate.userId) {
 
             var titleButton = "  "
 
@@ -949,7 +940,7 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS
                     searchResults: self.searchResults)
             } update: { _, _, searchResult, metadatas in
                 guard let metadatas, !metadatas.isEmpty, self.isSearchingMode, let searchResult else { return }
-                NCOperationQueue.shared.unifiedSearchAddSection(collectionViewCommon: self, metadatas: metadatas, searchResult: searchResult)
+                self.appDelegate.unifiedSearchQueue.addOperation(NCOperationUnifiedSearch(collectionViewCommon: self, metadatas: metadatas, searchResult: searchResult))
             } completion: { _, _ in
                 self.refreshControl.endRefreshing()
                 self.isReloadDataSourceNetworkInProgress = false
@@ -987,7 +978,7 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS
 
         NCNetworking.shared.unifiedSearchFilesProvider(userBaseUrl: appDelegate, id: lastSearchResult.id, term: term, limit: 5, cursor: cursor) { _, searchResult, metadatas, error in
             if error != .success {
-                NCContentPresenter.shared.showError(error: error)
+                NCContentPresenter().showError(error: error)
             }
 
             metadataForSection.unifiedSearchInProgress = false
@@ -1023,29 +1014,29 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS
                     self.metadataFolder = metadataFolder
                     // E2EE
                     if let metadataFolder = metadataFolder,
-                        metadataFolder.e2eEncrypted,
-                        CCUtility.isEnd(toEndEnabled: self.appDelegate.account),
-                       !NCNetworkingE2EE.shared.isInUpload(account: self.appDelegate.account, serverUrl: self.serverUrl) {
+                       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, route: NCNetworkingE2EE.shared.getRoute()) { _, e2eMetadata, signature, _, error in
+                        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.shared.showError(error: error)
+                                    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.shared.uploadMetadata(account: metadataFolder.account, serverUrl: serverUrl, userId: metadataFolder.userId)
+                                    let error = await NCNetworkingE2EE().uploadMetadata(account: metadataFolder.account, serverUrl: serverUrl, userId: metadataFolder.userId)
                                     if error != .success {
-                                        NCContentPresenter.shared.showError(error: error)
+                                        NCContentPresenter().showError(error: error)
                                     }
                                 }
                             } else {
-                                NCContentPresenter.shared.showError(error: NKError(errorCode: NCGlobal.shared.errorE2EEKeyDecodeMetadata, errorDescription: "_e2e_error_"))
+                                NCContentPresenter().showError(error: NKError(errorCode: NCGlobal.shared.errorE2EEKeyDecodeMetadata, errorDescription: "_e2e_error_"))
                             }
                             completion(tableDirectory, metadatas, metadatasUpdate, metadatasDelete, error)
                         }
@@ -1063,7 +1054,7 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS
 
     func pushMetadata(_ metadata: tableMetadata) {
 
-        guard let serverUrlPush = CCUtility.stringAppendServerUrl(metadata.serverUrl, addFileName: metadata.fileName) else { return }
+        let serverUrlPush = utilityFileSystem.stringAppendServerUrl(metadata.serverUrl, addFileName: metadata.fileName)
         appDelegate.activeMetadata = metadata
 
         if let viewController = appDelegate.listFilesVC[serverUrlPush], viewController.isViewLoaded {
@@ -1116,14 +1107,14 @@ extension NCCollectionViewCommon: UICollectionViewDelegate {
 
         if metadata.e2eEncrypted {
             if NCGlobal.shared.capabilityE2EEEnabled {
-                if !CCUtility.isEnd(toEndEnabled: appDelegate.account) {
+                if !NCKeychain().isEndToEndEnabled(account: appDelegate.account) {
                     let e2ee = NCEndToEndInitialize()
                     e2ee.delegate = self
                     e2ee.initEndToEndEncryption()
                     return
                 }
             } else {
-                NCContentPresenter.shared.showInfo(error: NKError(errorCode: NCGlobal.shared.errorE2EENotEnabled, errorDescription: "_e2e_server_disabled_"))
+                NCContentPresenter().showInfo(error: NKError(errorCode: NCGlobal.shared.errorE2EENotEnabled, errorDescription: "_e2e_server_disabled_"))
                 return
             }
         }
@@ -1134,7 +1125,7 @@ extension NCCollectionViewCommon: UICollectionViewDelegate {
 
         } else {
 
-            let imageIcon = UIImage(contentsOfFile: CCUtility.getDirectoryProviderStorageIconOcId(metadata.ocId, etag: metadata.etag))
+            let imageIcon = UIImage(contentsOfFile: utilityFileSystem.getDirectoryProviderStorageIconOcId(metadata.ocId, etag: metadata.etag))
 
             if !metadata.isDirectoryE2EE && (metadata.isImage || metadata.isAudioOrVideo) {
                 var metadatas: [tableMetadata] = []
@@ -1143,17 +1134,17 @@ extension NCCollectionViewCommon: UICollectionViewDelegate {
                         metadatas.append(metadata)
                     }
                 }
-                NCViewer.shared.view(viewController: self, metadata: metadata, metadatas: metadatas, imageIcon: imageIcon)
+                NCViewer().view(viewController: self, metadata: metadata, metadatas: metadatas, imageIcon: imageIcon)
                 return
             }
 
-            if CCUtility.fileProviderStorageExists(metadata) {
-                NCViewer.shared.view(viewController: self, metadata: metadata, metadatas: [metadata], imageIcon: imageIcon)
+            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 {
                 let error = NKError(errorCode: NCGlobal.shared.errorOffline, errorDescription: "_go_online_")
-                NCContentPresenter.shared.showInfo(error: error)
+                NCContentPresenter().showInfo(error: error)
             }
         }
     }
@@ -1207,38 +1198,46 @@ extension NCCollectionViewCommon: UICollectionViewDataSource {
         // Thumbnail
         if !metadata.directory {
             if metadata.name == NCGlobal.shared.appName {
-                    if let image = NCUtility.shared.createFilePreviewImage(ocId: metadata.ocId, etag: metadata.etag, fileNameView: metadata.fileNameView, classFile: metadata.classFile, status: metadata.status, createPreviewMedia: !metadata.hasPreview) {
+                    if let image = utility.createFilePreviewImage(ocId: metadata.ocId, etag: metadata.etag, fileNameView: metadata.fileNameView, classFile: metadata.classFile, status: metadata.status, createPreviewMedia: !metadata.hasPreview) {
                     (cell as? NCCellProtocol)?.filePreviewImageView?.image = image
                 } else {
-                    NCOperationQueue.shared.downloadThumbnail(metadata: metadata, placeholder: true, cell: cell, view: collectionView)
+                    if metadata.iconName.isEmpty {
+                        (cell as? NCCellProtocol)?.filePreviewImageView?.image = NCImageCache.images.file
+                    } else {
+                        (cell as? NCCellProtocol)?.filePreviewImageView?.image = UIImage(named: metadata.iconName)
+                    }
+                    if metadata.hasPreview && metadata.status == NCGlobal.shared.metadataStatusNormal && (!utilityFileSystem.fileProviderStoragePreviewIconExists(metadata.ocId, etag: metadata.etag)) {
+                        for case let operation as NCCollectionViewDownloadThumbnail in appDelegate.downloadThumbnailQueue.operations where operation.metadata.ocId == metadata.ocId { return }
+                        appDelegate.downloadThumbnailQueue.addOperation(NCCollectionViewDownloadThumbnail(metadata: metadata, cell: (cell as? NCCellProtocol), collectionView: collectionView))
+                    }
                 }
             } else {
                 // Unified search
                 switch metadata.iconName {
                 case let str where str.contains("contacts"):
-                    (cell as? NCCellProtocol)?.filePreviewImageView?.image = NCBrandColor.cacheImages.iconContacts
+                    (cell as? NCCellProtocol)?.filePreviewImageView?.image = NCImageCache.images.iconContacts
                 case let str where str.contains("conversation"):
-                    (cell as? NCCellProtocol)?.filePreviewImageView?.image = NCBrandColor.cacheImages.iconTalk
+                    (cell as? NCCellProtocol)?.filePreviewImageView?.image = NCImageCache.images.iconTalk
                 case let str where str.contains("calendar"):
-                    (cell as? NCCellProtocol)?.filePreviewImageView?.image = NCBrandColor.cacheImages.iconCalendar
+                    (cell as? NCCellProtocol)?.filePreviewImageView?.image = NCImageCache.images.iconCalendar
                 case let str where str.contains("deck"):
-                    (cell as? NCCellProtocol)?.filePreviewImageView?.image = NCBrandColor.cacheImages.iconDeck
+                    (cell as? NCCellProtocol)?.filePreviewImageView?.image = NCImageCache.images.iconDeck
                 case let str where str.contains("mail"):
-                    (cell as? NCCellProtocol)?.filePreviewImageView?.image = NCBrandColor.cacheImages.iconMail
+                    (cell as? NCCellProtocol)?.filePreviewImageView?.image = NCImageCache.images.iconMail
                 case let str where str.contains("talk"):
-                    (cell as? NCCellProtocol)?.filePreviewImageView?.image = NCBrandColor.cacheImages.iconTalk
+                    (cell as? NCCellProtocol)?.filePreviewImageView?.image = NCImageCache.images.iconTalk
                 case let str where str.contains("confirm"):
-                    (cell as? NCCellProtocol)?.filePreviewImageView?.image = NCBrandColor.cacheImages.iconConfirm
+                    (cell as? NCCellProtocol)?.filePreviewImageView?.image = NCImageCache.images.iconConfirm
                 case let str where str.contains("pages"):
-                    (cell as? NCCellProtocol)?.filePreviewImageView?.image = NCBrandColor.cacheImages.iconPages
+                    (cell as? NCCellProtocol)?.filePreviewImageView?.image = NCImageCache.images.iconPages
                 default:
-                    (cell as? NCCellProtocol)?.filePreviewImageView?.image = NCBrandColor.cacheImages.file
+                    (cell as? NCCellProtocol)?.filePreviewImageView?.image = NCImageCache.images.file
                 }
 
                 if !metadata.iconUrl.isEmpty {
-                    if let ownerId = NCUtility.shared.getAvatarFromIconUrl(metadata: metadata), let cell = cell as? NCCellProtocol {
+                    if let ownerId = getAvatarFromIconUrl(metadata: metadata), let cell = cell as? NCCellProtocol {
                         let fileName = metadata.userBaseUrl + "-" + ownerId + ".png"
-                        NCOperationQueue.shared.downloadAvatar(user: ownerId, dispalyName: nil, fileName: fileName, cell: cell, view: collectionView, cellImageView: cell.filePreviewImageView)
+                        NCNetworking.shared.downloadAvatar(user: ownerId, dispalyName: nil, fileName: fileName, cell: cell, view: collectionView, cellImageView: cell.filePreviewImageView)
                     }
                 }
             }
@@ -1250,14 +1249,16 @@ extension NCCollectionViewCommon: UICollectionViewDataSource {
            appDelegate.account == metadata.account,
            let cell = cell as? NCCellProtocol {
             let fileName = metadata.userBaseUrl + "-" + metadata.ownerId + ".png"
-            NCOperationQueue.shared.downloadAvatar(user: metadata.ownerId, dispalyName: metadata.ownerDisplayName, fileName: fileName, cell: cell, view: collectionView, cellImageView: cell.fileAvatarImageView)
+            NCNetworking.shared.downloadAvatar(user: metadata.ownerId, dispalyName: metadata.ownerDisplayName, fileName: fileName, cell: cell, view: collectionView, cellImageView: cell.fileAvatarImageView)
         }
     }
 
-    func collectionView(_ collectionView: UICollectionView, didEndDisplaying cell: UICollectionViewCell, forItemAt indexPath: IndexPath) { 
+    func collectionView(_ collectionView: UICollectionView, didEndDisplaying cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
         if !collectionView.indexPathsForVisibleItems.contains(indexPath) {
             guard let metadata = dataSource.cellForItemAt(indexPath: indexPath) else { return }
-            NCOperationQueue.shared.cancelDownloadThumbnail(metadata: metadata)
+            for case let operation as NCCollectionViewDownloadThumbnail in appDelegate.downloadThumbnailQueue.operations where operation.metadata.ocId == metadata.ocId {
+                operation.cancel()
+            }
         }
     }
 
@@ -1326,7 +1327,7 @@ extension NCCollectionViewCommon: UICollectionViewDataSource {
             cell.fileTitleLabel?.text = metadata.fileName
             cell.fileTitleLabel?.lineBreakMode = .byTruncatingTail
             if metadata.name == NCGlobal.shared.appName {
-                cell.fileInfoLabel?.text = NSLocalizedString("_in_", comment: "") + " " + NCUtilityFileSystem.shared.getPath(path: metadata.path, user: metadata.user)
+                cell.fileInfoLabel?.text = NSLocalizedString("_in_", comment: "") + " " + utilityFileSystem.getPath(path: metadata.path, user: metadata.user)
             } else {
                 cell.fileInfoLabel?.text = metadata.subline
             }
@@ -1350,28 +1351,28 @@ extension NCCollectionViewCommon: UICollectionViewDataSource {
             let tableDirectory = NCManageDatabase.shared.getTableDirectory(ocId: metadata.ocId)
 
             if metadata.e2eEncrypted {
-                cell.filePreviewImageView?.image = NCBrandColor.cacheImages.folderEncrypted
+                cell.filePreviewImageView?.image = NCImageCache.images.folderEncrypted
             } else if isShare {
-                cell.filePreviewImageView?.image = NCBrandColor.cacheImages.folderSharedWithMe
+                cell.filePreviewImageView?.image = NCImageCache.images.folderSharedWithMe
             } else if !metadata.shareType.isEmpty {
                 metadata.shareType.contains(3) ?
-                (cell.filePreviewImageView?.image = NCBrandColor.cacheImages.folderPublic) :
-                (cell.filePreviewImageView?.image = NCBrandColor.cacheImages.folderSharedWithMe)
+                (cell.filePreviewImageView?.image = NCImageCache.images.folderPublic) :
+                (cell.filePreviewImageView?.image = NCImageCache.images.folderSharedWithMe)
             } else if !metadata.shareType.isEmpty && metadata.shareType.contains(3) {
-                cell.filePreviewImageView?.image = NCBrandColor.cacheImages.folderPublic
+                cell.filePreviewImageView?.image = NCImageCache.images.folderPublic
             } else if metadata.mountType == "group" {
-                cell.filePreviewImageView?.image = NCBrandColor.cacheImages.folderGroup
+                cell.filePreviewImageView?.image = NCImageCache.images.folderGroup
             } else if isMounted {
-                cell.filePreviewImageView?.image = NCBrandColor.cacheImages.folderExternal
+                cell.filePreviewImageView?.image = NCImageCache.images.folderExternal
             } else if metadata.fileName == autoUploadFileName && metadata.serverUrl == autoUploadDirectory {
-                cell.filePreviewImageView?.image = NCBrandColor.cacheImages.folderAutomaticUpload
+                cell.filePreviewImageView?.image = NCImageCache.images.folderAutomaticUpload
             } else {
-                cell.filePreviewImageView?.image = NCBrandColor.cacheImages.folder
+                cell.filePreviewImageView?.image = NCImageCache.images.folder
             }
 
             // Local image: offline
             if let tableDirectory, tableDirectory.offline {
-                cell.fileLocalImage?.image = NCBrandColor.cacheImages.offlineFlag
+                cell.fileLocalImage?.image = NCImageCache.images.offlineFlag
             }
 
             // color folder
@@ -1382,58 +1383,58 @@ extension NCCollectionViewCommon: UICollectionViewDataSource {
             // image local
             if NCManageDatabase.shared.getTableLocalFile(ocId: metadata.ocId) != nil {
                 a11yValues.append(NSLocalizedString("_offline_", comment: ""))
-                cell.fileLocalImage?.image = NCBrandColor.cacheImages.offlineFlag
-            } else if CCUtility.fileProviderStorageExists(metadata) {
-                cell.fileLocalImage?.image = NCBrandColor.cacheImages.local
+                cell.fileLocalImage?.image = NCImageCache.images.offlineFlag
+            } else if utilityFileSystem.fileProviderStorageExists(metadata) {
+                cell.fileLocalImage?.image = NCImageCache.images.local
             }
         }
 
         // image Favorite
         if metadata.favorite {
-            cell.fileFavoriteImage?.image = NCBrandColor.cacheImages.favorite
+            cell.fileFavoriteImage?.image = NCImageCache.images.favorite
             a11yValues.append(NSLocalizedString("_favorite_", comment: ""))
         }
 
         // Share image
         if isShare {
-            cell.fileSharedImage?.image = NCBrandColor.cacheImages.shared
+            cell.fileSharedImage?.image = NCImageCache.images.shared
         } else if !metadata.shareType.isEmpty {
             metadata.shareType.contains(3) ?
-            (cell.fileSharedImage?.image = NCBrandColor.cacheImages.shareByLink) :
-            (cell.fileSharedImage?.image = NCBrandColor.cacheImages.shared)
+            (cell.fileSharedImage?.image = NCImageCache.images.shareByLink) :
+            (cell.fileSharedImage?.image = NCImageCache.images.shared)
         } else {
-            cell.fileSharedImage?.image = NCBrandColor.cacheImages.canShare
+            cell.fileSharedImage?.image = NCImageCache.images.canShare
         }
         if appDelegate.account != metadata.account {
-            cell.fileSharedImage?.image = NCBrandColor.cacheImages.shared
+            cell.fileSharedImage?.image = NCImageCache.images.shared
         }
 
         // Button More
         if metadata.isInTransfer || metadata.isWaitingTransfer {
-            cell.setButtonMore(named: NCGlobal.shared.buttonMoreStop, image: NCBrandColor.cacheImages.buttonStop)
+            cell.setButtonMore(named: NCGlobal.shared.buttonMoreStop, image: NCImageCache.images.buttonStop)
         } else if metadata.lock == true {
-            cell.setButtonMore(named: NCGlobal.shared.buttonMoreLock, image: NCBrandColor.cacheImages.buttonMoreLock)
+            cell.setButtonMore(named: NCGlobal.shared.buttonMoreLock, image: NCImageCache.images.buttonMoreLock)
             a11yValues.append(String(format: NSLocalizedString("_locked_by_", comment: ""), metadata.lockOwnerDisplayName))
         } else {
-            cell.setButtonMore(named: NCGlobal.shared.buttonMoreMore, image: NCBrandColor.cacheImages.buttonMore)
+            cell.setButtonMore(named: NCGlobal.shared.buttonMoreMore, image: NCImageCache.images.buttonMore)
         }
 
         // Write status on Label Info
         switch metadata.status {
         case NCGlobal.shared.metadataStatusWaitDownload:
-            cell.fileInfoLabel?.text = CCUtility.transformedSize(metadata.size) + " - " + NSLocalizedString("_status_wait_download_", comment: "")
+            cell.fileInfoLabel?.text = utilityFileSystem.transformedSize(metadata.size) + " - " + NSLocalizedString("_status_wait_download_", comment: "")
         case NCGlobal.shared.metadataStatusInDownload:
-            cell.fileInfoLabel?.text = CCUtility.transformedSize(metadata.size) + " - " + NSLocalizedString("_status_in_download_", comment: "")
+            cell.fileInfoLabel?.text = utilityFileSystem.transformedSize(metadata.size) + " - " + NSLocalizedString("_status_in_download_", comment: "")
         case NCGlobal.shared.metadataStatusDownloading:
-            cell.fileInfoLabel?.text = CCUtility.transformedSize(metadata.size) + " - ↓ …"
+            cell.fileInfoLabel?.text = utilityFileSystem.transformedSize(metadata.size) + " - ↓ …"
         case NCGlobal.shared.metadataStatusWaitUpload:
-            cell.fileInfoLabel?.text = CCUtility.transformedSize(metadata.size) + " - " + NSLocalizedString("_status_wait_upload_", comment: "")
+            cell.fileInfoLabel?.text = utilityFileSystem.transformedSize(metadata.size) + " - " + NSLocalizedString("_status_wait_upload_", comment: "")
             cell.fileLocalImage?.image = nil
         case NCGlobal.shared.metadataStatusInUpload:
-            cell.fileInfoLabel?.text = CCUtility.transformedSize(metadata.size) + " - " + NSLocalizedString("_status_in_upload_", comment: "")
+            cell.fileInfoLabel?.text = utilityFileSystem.transformedSize(metadata.size) + " - " + NSLocalizedString("_status_in_upload_", comment: "")
             cell.fileLocalImage?.image = nil
         case NCGlobal.shared.metadataStatusUploading:
-            cell.fileInfoLabel?.text = CCUtility.transformedSize(metadata.size) + " - ↑ …"
+            cell.fileInfoLabel?.text = utilityFileSystem.transformedSize(metadata.size) + " - ↑ …"
             cell.fileLocalImage?.image = nil
         case NCGlobal.shared.metadataStatusUploadError:
             if metadata.sessionError.isEmpty {
@@ -1447,7 +1448,7 @@ extension NCCollectionViewCommon: UICollectionViewDataSource {
 
         // Live Photo
         if metadata.livePhoto {
-            cell.fileStatusImage?.image = NCBrandColor.cacheImages.livePhoto
+            cell.fileStatusImage?.image = NCImageCache.images.livePhoto
             a11yValues.append(NSLocalizedString("_upload_mov_livephoto_", comment: ""))
         }
 
@@ -1456,7 +1457,7 @@ extension NCCollectionViewCommon: UICollectionViewDataSource {
             cell.fileLocalImage?.image = nil
             cell.hideButtonShare(true)
             cell.hideButtonMore(true)
-            if let ownerId = NCUtility.shared.getAvatarFromIconUrl(metadata: metadata) {
+            if let ownerId = getAvatarFromIconUrl(metadata: metadata) {
                 cell.fileUser = ownerId
             }
         }
@@ -1703,3 +1704,114 @@ extension NCCollectionViewCommon: EasyTipViewDelegate {
         self.tipView?.dismiss()
     }
 }
+
+extension NCCollectionViewCommon {
+
+    func getAvatarFromIconUrl(metadata: tableMetadata) -> String? {
+
+        var ownerId: String?
+        if metadata.iconUrl.contains("http") && metadata.iconUrl.contains("avatar") {
+            let splitIconUrl = metadata.iconUrl.components(separatedBy: "/")
+            var found: Bool = false
+            for item in splitIconUrl {
+                if found {
+                    ownerId = item
+                    break
+                }
+                if item == "avatar" { found = true}
+            }
+        }
+        return ownerId
+    }
+}
+
+// MARK: -
+
+class NCOperationUnifiedSearch: ConcurrentOperation {
+
+    var collectionViewCommon: NCCollectionViewCommon
+    var metadatas: [tableMetadata]
+    var searchResult: NKSearchResult
+
+    init(collectionViewCommon: NCCollectionViewCommon, metadatas: [tableMetadata], searchResult: NKSearchResult) {
+        self.collectionViewCommon = collectionViewCommon
+        self.metadatas = metadatas
+        self.searchResult = searchResult
+    }
+
+    func reloadDataThenPerform(_ closure: @escaping (() -> Void)) {
+        DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
+            CATransaction.begin()
+            CATransaction.setCompletionBlock(closure)
+            self.collectionViewCommon.collectionView.reloadData()
+            CATransaction.commit()
+        }
+    }
+
+    override func start() {
+
+        guard !isCancelled else { return self.finish() }
+
+        self.collectionViewCommon.dataSource.addSection(metadatas: metadatas, searchResult: searchResult)
+        self.collectionViewCommon.searchResults?.append(self.searchResult)
+        reloadDataThenPerform {
+            self.finish()
+        }
+    }
+}
+
+class NCCollectionViewDownloadThumbnail: ConcurrentOperation {
+
+    var metadata: tableMetadata
+    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)
+    }
+
+    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)) { _, _, imageIcon, _, etag, error in
+
+            if error == .success, let image = imageIcon {
+                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()
+                    }
+                }
+            }
+            self.finish()
+        }
+    }
+}

+ 2 - 2
iOSClient/Main/Collection Common/NCGridCell.swift

@@ -194,7 +194,7 @@ class NCGridCell: UICollectionViewCell, UIGestureRecognizerDelegate, NCCellProto
                 imageVisualEffect.effect = UIBlurEffect(style: .extraLight)
                 imageVisualEffect.backgroundColor = .lightGray
             }
-            imageSelect.image = NCBrandColor.cacheImages.checkedYes
+            imageSelect.image = NCImageCache.images.checkedYes
             imageVisualEffect.isHidden = false
         } else {
             imageSelect.isHidden = true
@@ -209,7 +209,7 @@ class NCGridCell: UICollectionViewCell, UIGestureRecognizerDelegate, NCCellProto
         dateFormatter.timeStyle = .none
         dateFormatter.locale = Locale.current
 
-        labelInfo.text = dateFormatter.string(from: date as Date) + " · " + CCUtility.transformedSize(size)
+        labelInfo.text = dateFormatter.string(from: date as Date) + " · " + NCUtilityFileSystem().transformedSize(size)
     }
 
     func setAccessibility(label: String, value: String) {

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

@@ -249,7 +249,7 @@ class NCListCell: UICollectionViewCell, UIGestureRecognizerDelegate, NCCellProto
         if status {
             var blurEffect: UIVisualEffect?
             var blurEffectView: UIView?
-            imageSelect.image = NCBrandColor.cacheImages.checkedYes
+            imageSelect.image = NCImageCache.images.checkedYes
             if traitCollection.userInterfaceStyle == .dark {
                 blurEffect = UIBlurEffect(style: .dark)
                 blurEffectView = UIVisualEffectView(effect: blurEffect)
@@ -264,14 +264,14 @@ class NCListCell: UICollectionViewCell, UIGestureRecognizerDelegate, NCCellProto
             backgroundView = blurEffectView
             separator.isHidden = true
         } else {
-            imageSelect.image = NCBrandColor.cacheImages.checkedNo
+            imageSelect.image = NCImageCache.images.checkedNo
             backgroundView = nil
             separator.isHidden = false
         }
     }
 
     func writeInfoDateSize(date: NSDate, size: Int64) {
-        labelInfo.text = CCUtility.dateDiff(date as Date) + " · " + CCUtility.transformedSize(size)
+        labelInfo.text = NCUtility().dateDiff(date as Date) + " · " + NCUtilityFileSystem().transformedSize(size)
     }
 
     func setAccessibility(label: String, value: String) {

+ 21 - 21
iOSClient/Main/Create cloud/NCCreateFormUploadConflict.swift

@@ -61,6 +61,9 @@ class NCCreateFormUploadConflict: UIViewController {
     let fileNamesPath = ThreadSafeDictionary<String, String>()
     var blurView: UIVisualEffectView!
 
+    let utility = NCUtility()
+    let utilityFileSystem = NCUtilityFileSystem()
+
     // MARK: - View Life Cycle
 
     required init?(coder aDecoder: NSCoder) {
@@ -222,7 +225,7 @@ class NCCreateFormUploadConflict: UIViewController {
 
             switchAlreadyExistingFiles.isOn = true
             let error = NKError(errorCode: NCGlobal.shared.errorInternalError, errorDescription: "_file_not_rewite_doc_")
-            NCContentPresenter.shared.showInfo(error: error)
+            NCContentPresenter().showInfo(error: error)
         }
 
         tableView.reloadData()
@@ -245,11 +248,11 @@ class NCCreateFormUploadConflict: UIViewController {
                 var fileName = metadata.fileNameView
                 let fileNameExtension = (fileName as NSString).pathExtension.lowercased()
                 let fileNameNoExtension = (fileName as NSString).deletingPathExtension
-                if fileNameExtension == "heic" && CCUtility.getFormatCompatibility() {
+                if fileNameExtension == "heic" && NCKeychain().formatCompatibility {
                     fileName = fileNameNoExtension + ".jpg"
                 }
-                let oldPath = CCUtility.getDirectoryProviderStorageOcId(metadata.ocId, fileNameView: metadata.fileNameView)
-                let newFileName = NCUtilityFileSystem.shared.createFileName(fileName, serverUrl: metadata.serverUrl, account: metadata.account)
+                let oldPath = utilityFileSystem.getDirectoryProviderStorageOcId(metadata.ocId, fileNameView: metadata.fileNameView)
+                let newFileName = utilityFileSystem.createFileName(fileName, serverUrl: metadata.serverUrl, account: metadata.account)
 
                 metadata.ocId = UUID().uuidString
                 metadata.fileName = newFileName
@@ -257,8 +260,8 @@ class NCCreateFormUploadConflict: UIViewController {
 
                 // This is not an asset - [file]
                 if metadata.assetLocalIdentifier.isEmpty || metadata.isExtractFile {
-                    let newPath = CCUtility.getDirectoryProviderStorageOcId(metadata.ocId, fileNameView: newFileName)
-                    CCUtility.moveFile(atPath: oldPath, toPath: newPath)
+                    let newPath = utilityFileSystem.getDirectoryProviderStorageOcId(metadata.ocId, fileNameView: newFileName)
+                    utilityFileSystem.moveFile(atPath: oldPath, toPath: newPath)
                 }
 
                 metadatasNOConflict.append(metadata)
@@ -323,12 +326,12 @@ extension NCCreateFormUploadConflict: UITableViewDataSource {
             // -----> Already Existing File
 
             guard let metadataAlreadyExists = NCManageDatabase.shared.getMetadataConflict(account: metadataNewFile.account, serverUrl: metadataNewFile.serverUrl, fileNameView: metadataNewFile.fileNameView) else { return UITableViewCell() }
-            if FileManager().fileExists(atPath: CCUtility.getDirectoryProviderStorageIconOcId(metadataAlreadyExists.ocId, etag: metadataAlreadyExists.etag)) {
-                cell.imageAlreadyExistingFile.image = UIImage(contentsOfFile: CCUtility.getDirectoryProviderStorageIconOcId(metadataAlreadyExists.ocId, etag: metadataAlreadyExists.etag))
-            } else if FileManager().fileExists(atPath: CCUtility.getDirectoryProviderStorageOcId(metadataAlreadyExists.ocId, fileNameView: metadataAlreadyExists.fileNameView)) && metadataAlreadyExists.contentType == "application/pdf" {
+            if FileManager().fileExists(atPath: utilityFileSystem.getDirectoryProviderStorageIconOcId(metadataAlreadyExists.ocId, etag: metadataAlreadyExists.etag)) {
+                cell.imageAlreadyExistingFile.image = UIImage(contentsOfFile: utilityFileSystem.getDirectoryProviderStorageIconOcId(metadataAlreadyExists.ocId, etag: metadataAlreadyExists.etag))
+            } else if FileManager().fileExists(atPath: utilityFileSystem.getDirectoryProviderStorageOcId(metadataAlreadyExists.ocId, fileNameView: metadataAlreadyExists.fileNameView)) && metadataAlreadyExists.contentType == "application/pdf" {
 
-                let url = URL(fileURLWithPath: CCUtility.getDirectoryProviderStorageOcId(metadataAlreadyExists.ocId, fileNameView: metadataAlreadyExists.fileNameView))
-                if let image = NCUtility.shared.pdfThumbnail(url: url) {
+                let url = URL(fileURLWithPath: utilityFileSystem.getDirectoryProviderStorageOcId(metadataAlreadyExists.ocId, fileNameView: metadataAlreadyExists.fileNameView))
+                if let image = utility.pdfThumbnail(url: url) {
                     cell.imageAlreadyExistingFile.image = image
                 } else {
                     cell.imageAlreadyExistingFile.image = UIImage(named: metadataAlreadyExists.iconName)
@@ -341,7 +344,7 @@ extension NCCreateFormUploadConflict: UITableViewDataSource {
                     cell.imageAlreadyExistingFile.image = UIImage(named: metadataAlreadyExists.iconName)
                 }
             }
-            cell.labelDetailAlreadyExistingFile.text = CCUtility.dateDiff(metadataAlreadyExists.date as Date) + "\n" + CCUtility.transformedSize(metadataAlreadyExists.size)
+            cell.labelDetailAlreadyExistingFile.text = utility.dateDiff(metadataAlreadyExists.date as Date) + "\n" + utilityFileSystem.transformedSize(metadataAlreadyExists.size)
 
             if metadatasConflictAlreadyExistingFiles.contains(metadataNewFile.ocId) {
                 cell.switchAlreadyExistingFile.isOn = true
@@ -356,7 +359,7 @@ extension NCCreateFormUploadConflict: UITableViewDataSource {
             } else {
                 cell.imageNewFile.image = UIImage(named: metadataNewFile.iconName)
             }
-            let filePathNewFile = CCUtility.getDirectoryProviderStorageOcId(metadataNewFile.ocId, fileNameView: metadataNewFile.fileNameView)!
+            let filePathNewFile = utilityFileSystem.getDirectoryProviderStorageOcId(metadataNewFile.ocId, fileNameView: metadataNewFile.fileNameView)
             if !metadataNewFile.assetLocalIdentifier.isEmpty {
 
                 let result = PHAsset.fetchAssets(withLocalIdentifiers: [metadataNewFile.assetLocalIdentifier], options: nil)
@@ -372,7 +375,7 @@ extension NCCreateFormUploadConflict: UITableViewDataSource {
                                 cell.imageNewFile.image = image
                             }
                         } else if mediaType == PHAssetMediaType.video {
-                            if let image = NCUtility.shared.imageFromVideo(url: URL(fileURLWithPath: fileNamePath), at: 0) {
+                            if let image = utility.imageFromVideo(url: URL(fileURLWithPath: fileNamePath), at: 0) {
                                 cell.imageNewFile.image = image
                             }
                         }
@@ -380,7 +383,7 @@ extension NCCreateFormUploadConflict: UITableViewDataSource {
                         let fileDictionary = try FileManager.default.attributesOfItem(atPath: fileNamePath)
                         let fileSize = fileDictionary[FileAttributeKey.size] as? Int64 ?? 0
 
-                        cell.labelDetailNewFile.text = CCUtility.dateDiff(date) + "\n" + CCUtility.transformedSize(fileSize)
+                        cell.labelDetailNewFile.text = utility.dateDiff(date) + "\n" + utilityFileSystem.transformedSize(fileSize)
 
                     } catch { print("Error: \(error)") }
 
@@ -400,11 +403,11 @@ extension NCCreateFormUploadConflict: UITableViewDataSource {
                                         DispatchQueue.main.async { cell.imageNewFile.image = image }
                                     }
                                 } else if mediaType == PHAssetMediaType.video {
-                                    if let image = NCUtility.shared.imageFromVideo(url: URL(fileURLWithPath: fileNamePath!), at: 0) {
+                                    if let image = self.utility.imageFromVideo(url: URL(fileURLWithPath: fileNamePath!), at: 0) {
                                         DispatchQueue.main.async { cell.imageNewFile.image = image }
                                     }
                                 }
-                                DispatchQueue.main.async { cell.labelDetailNewFile.text = CCUtility.dateDiff(date) + "\n" + CCUtility.transformedSize(fileSize) }
+                                DispatchQueue.main.async { cell.labelDetailNewFile.text = self.utility.dateDiff(date) + "\n" + self.utilityFileSystem.transformedSize(fileSize) }
                             } catch { print("Error: \(error)") }
                         }
                     }
@@ -423,13 +426,10 @@ extension NCCreateFormUploadConflict: UITableViewDataSource {
                     let fileDictionary = try FileManager.default.attributesOfItem(atPath: filePathNewFile)
                     let fileSize = fileDictionary[FileAttributeKey.size] as? Int64 ?? 0
 
-                    cell.labelDetailNewFile.text = CCUtility.dateDiff(metadataNewFile.date as Date) + "\n" + CCUtility.transformedSize(fileSize)
+                    cell.labelDetailNewFile.text = utility.dateDiff(metadataNewFile.date as Date) + "\n" + utilityFileSystem.transformedSize(fileSize)
 
                 } catch { print("Error: \(error)") }
 
-            } else {
-
-                CCUtility.dateDiff(metadataNewFile.date as Date)
             }
 
             if metadatasConflictNewFiles.contains(metadataNewFile.ocId) {

+ 18 - 19
iOSClient/Main/Create cloud/NCCreateFormUploadDocuments.swift

@@ -33,10 +33,7 @@ import XLForm
     @IBOutlet weak var collectionView: UICollectionView!
     @IBOutlet weak var collectionViewHeigth: NSLayoutConstraint!
 
-    // swiftlint:disable force_cast
-    let appDelegate = UIApplication.shared.delegate as! AppDelegate
-    // swiftlint:enable force_cast
-
+    let appDelegate = (UIApplication.shared.delegate as? AppDelegate)!
     var editorId = ""
     var creatorId = ""
     var typeTemplate = ""
@@ -48,6 +45,8 @@ import XLForm
     var titleForm = ""
     var listOfTemplate: [NKEditorTemplates] = []
     var selectTemplate: NKEditorTemplates?
+    let utilityFileSystem = NCUtilityFileSystem()
+    let utility = NCUtility()
 
     // Layout
     let numItems = 2
@@ -59,7 +58,7 @@ import XLForm
     override func viewDidLoad() {
         super.viewDidLoad()
 
-        if serverUrl == NCUtilityFileSystem.shared.getHomeServer(urlBase: appDelegate.urlBase, userId: appDelegate.userId) {
+        if serverUrl == utilityFileSystem.getHomeServer(urlBase: appDelegate.urlBase, userId: appDelegate.userId) {
             fileNameFolder = "/"
         } else {
             fileNameFolder = (serverUrl as NSString).lastPathComponent
@@ -169,7 +168,7 @@ import XLForm
         // image
         let imagePreview = cell.viewWithTag(100) as? UIImageView
         if !template.preview.isEmpty {
-            let fileNameLocalPath = String(CCUtility.getDirectoryUserData()) + "/" + template.name + ".png"
+            let fileNameLocalPath = utilityFileSystem.directoryUserData + "/" + template.name + ".png"
             if FileManager.default.fileExists(atPath: fileNameLocalPath) {
                 let imageURL = URL(fileURLWithPath: fileNameLocalPath)
                 if let image = UIImage(contentsOfFile: imageURL.path) {
@@ -218,7 +217,7 @@ import XLForm
         }
 
         self.serverUrl = serverUrl
-        if serverUrl == NCUtilityFileSystem.shared.getHomeServer(urlBase: appDelegate.urlBase, userId: appDelegate.userId) {
+        if serverUrl == utilityFileSystem.getHomeServer(urlBase: appDelegate.urlBase, userId: appDelegate.userId) {
             fileNameFolder = "/"
         } else {
             fileNameFolder = (serverUrl as NSString).lastPathComponent
@@ -259,7 +258,7 @@ import XLForm
         fileNameForm = fileNameForm.trimmingCharacters(in: .whitespacesAndNewlines)
 
         let result = NextcloudKit.shared.nkCommonInstance.getInternalType(fileName: fileNameForm, mimeType: "", directory: false)
-        if NCUtility.shared.isDirectEditing(account: appDelegate.account, contentType: result.mimeType).isEmpty {
+        if utility.isDirectEditing(account: appDelegate.account, contentType: result.mimeType).isEmpty {
             fileNameForm = (fileNameForm as NSString).deletingPathExtension + "." + fileNameExtension
         }
 
@@ -279,7 +278,7 @@ import XLForm
 
         } else {
 
-            let fileNamePath = CCUtility.returnFileNamePath(fromFileName: String(describing: fileNameForm), serverUrl: serverUrl, urlBase: appDelegate.urlBase, userId: appDelegate.userId, account: appDelegate.account)!
+            let fileNamePath = utilityFileSystem.getFileNamePath(String(describing: fileNameForm), serverUrl: serverUrl, urlBase: appDelegate.urlBase, userId: appDelegate.userId)
             createDocument(fileNamePath: fileNamePath, fileName: String(describing: fileNameForm))
         }
     }
@@ -288,7 +287,7 @@ import XLForm
 
         if let metadatas {
             let fileName = metadatas[0].fileName
-            let fileNamePath = CCUtility.returnFileNamePath(fromFileName: fileName, serverUrl: serverUrl, urlBase: appDelegate.urlBase, userId: appDelegate.userId, account: appDelegate.account)!
+            let fileNamePath = utilityFileSystem.getFileNamePath(fileName, serverUrl: serverUrl, urlBase: appDelegate.urlBase, userId: appDelegate.userId)
             createDocument(fileNamePath: fileNamePath, fileName: fileName)
         } else {
             DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
@@ -307,15 +306,15 @@ import XLForm
 
             var options = NKRequestOptions()
             if self.editorId == NCGlobal.shared.editorOnlyoffice {
-                options = NKRequestOptions(customUserAgent: NCUtility.shared.getCustomUserAgentOnlyOffice())
+                options = NKRequestOptions(customUserAgent: utility.getCustomUserAgentOnlyOffice())
             } else if editorId == NCGlobal.shared.editorText {
-                options = NKRequestOptions(customUserAgent: NCUtility.shared.getCustomUserAgentNCText())
+                options = NKRequestOptions(customUserAgent: utility.getCustomUserAgentNCText())
             }
 
             NextcloudKit.shared.NCTextCreateFile(fileNamePath: fileNamePath, editorId: editorId, creatorId: creatorId, templateId: templateIdentifier, options: options) { account, url, _, error in
                 guard error == .success, account == self.appDelegate.account, let url = url else {
                     self.navigationItem.rightBarButtonItem?.isEnabled = true
-                    NCContentPresenter.shared.showError(error: error)
+                    NCContentPresenter().showError(error: error)
                     return
                 }
 
@@ -328,7 +327,7 @@ import XLForm
                 self.dismiss(animated: true, completion: {
                     let metadata = NCManageDatabase.shared.createMetadata(account: self.appDelegate.account, user: self.appDelegate.user, userId: self.appDelegate.userId, fileName: fileName, fileNameView: fileName, ocId: UUID, serverUrl: self.serverUrl, urlBase: self.appDelegate.urlBase, url: url, contentType: results.mimeType)
                     if let viewController = self.appDelegate.activeViewController {
-                        NCViewer.shared.view(viewController: viewController, metadata: metadata, metadatas: [metadata], imageIcon: nil)
+                        NCViewer().view(viewController: viewController, metadata: metadata, metadatas: [metadata], imageIcon: nil)
                     }
                 })
             }
@@ -339,7 +338,7 @@ import XLForm
             NextcloudKit.shared.createRichdocuments(path: fileNamePath, templateId: templateIdentifier) { account, url, _, error in
                 guard error == .success, account == self.appDelegate.account, let url = url else {
                     self.navigationItem.rightBarButtonItem?.isEnabled = true
-                    NCContentPresenter.shared.showError(error: error)
+                    NCContentPresenter().showError(error: error)
                     return
                 }
 
@@ -347,7 +346,7 @@ import XLForm
                     let createFileName = (fileName as NSString).deletingPathExtension + "." + self.fileNameExtension
                     let metadata = NCManageDatabase.shared.createMetadata(account: self.appDelegate.account, user: self.appDelegate.user, userId: self.appDelegate.userId, fileName: createFileName, fileNameView: createFileName, ocId: UUID, serverUrl: self.serverUrl, urlBase: self.appDelegate.urlBase, url: url, contentType: "")
                     if let viewController = self.appDelegate.activeViewController {
-                        NCViewer.shared.view(viewController: viewController, metadata: metadata, metadatas: [metadata], imageIcon: nil)
+                        NCViewer().view(viewController: viewController, metadata: metadata, metadatas: [metadata], imageIcon: nil)
                     }
                })
             }
@@ -370,9 +369,9 @@ import XLForm
 
             var options = NKRequestOptions()
             if self.editorId == NCGlobal.shared.editorOnlyoffice {
-                options = NKRequestOptions(customUserAgent: NCUtility.shared.getCustomUserAgentOnlyOffice())
+                options = NKRequestOptions(customUserAgent: utility.getCustomUserAgentOnlyOffice())
             } else if editorId == NCGlobal.shared.editorText {
-                options = NKRequestOptions(customUserAgent: NCUtility.shared.getCustomUserAgentNCText())
+                options = NKRequestOptions(customUserAgent: utility.getCustomUserAgentNCText())
             }
 
             NextcloudKit.shared.NCTextGetListOfTemplates(options: options) { account, templates, _, error in
@@ -489,7 +488,7 @@ import XLForm
 
     func getImageFromTemplate(name: String, preview: String, indexPath: IndexPath) {
 
-        let fileNameLocalPath = String(CCUtility.getDirectoryUserData()) + "/" + name + ".png"
+        let fileNameLocalPath = utilityFileSystem.directoryUserData + "/" + name + ".png"
 
         NextcloudKit.shared.download(serverUrlFileName: preview, fileNameLocalPath: fileNameLocalPath, requestHandler: { _ in
 

+ 9 - 10
iOSClient/Main/Create cloud/NCCreateFormUploadVoiceNote.swift

@@ -31,10 +31,9 @@ class NCCreateFormUploadVoiceNote: XLFormViewController, NCSelectDelegate, AVAud
     @IBOutlet weak var labelDuration: UILabel!
     @IBOutlet weak var progressView: UIProgressView!
 
-    // swiftlint:disable force_cast
-    let appDelegate = UIApplication.shared.delegate as! AppDelegate
-    // swiftlint:enable force_cast
-
+    private let appDelegate = (UIApplication.shared.delegate as? AppDelegate)!
+    let utilityFileSystem = NCUtilityFileSystem()
+    let utility = NCUtility()
     private var serverUrl = ""
     private var titleServerUrl = ""
     private var fileName = ""
@@ -93,7 +92,7 @@ class NCCreateFormUploadVoiceNote: XLFormViewController, NCSelectDelegate, AVAud
 
     public func setup(serverUrl: String, fileNamePath: String, fileName: String) {
 
-        if serverUrl == NCUtilityFileSystem.shared.getHomeServer(urlBase: appDelegate.urlBase, userId: appDelegate.userId) {
+        if serverUrl == utilityFileSystem.getHomeServer(urlBase: appDelegate.urlBase, userId: appDelegate.userId) {
             titleServerUrl = "/"
         } else {
             titleServerUrl = (serverUrl as NSString).lastPathComponent
@@ -170,8 +169,8 @@ class NCCreateFormUploadVoiceNote: XLFormViewController, NCSelectDelegate, AVAud
 
             self.form.delegate = nil
 
-            if let fileNameNew = formRow.value {
-                self.fileName = CCUtility.removeForbiddenCharactersServer(fileNameNew as? String)
+            if let fileNameNew = formRow.value as? String {
+                self.fileName = utility.removeForbiddenCharacters(fileNameNew)
             }
 
             formRow.value = self.fileName
@@ -198,7 +197,7 @@ class NCCreateFormUploadVoiceNote: XLFormViewController, NCSelectDelegate, AVAud
 
             self.serverUrl = serverUrl!
 
-            if serverUrl == NCUtilityFileSystem.shared.getHomeServer(urlBase: appDelegate.urlBase, userId: appDelegate.userId) {
+            if serverUrl == utilityFileSystem.getHomeServer(urlBase: appDelegate.urlBase, userId: appDelegate.userId) {
                 self.titleServerUrl = "/"
             } else {
                 self.titleServerUrl = (serverUrl! as NSString).lastPathComponent
@@ -229,7 +228,7 @@ class NCCreateFormUploadVoiceNote: XLFormViewController, NCSelectDelegate, AVAud
         metadataForUpload.session = NCNetworking.shared.sessionIdentifierBackground
         metadataForUpload.sessionSelector = NCGlobal.shared.selectorUploadFile
         metadataForUpload.status = NCGlobal.shared.metadataStatusWaitUpload
-        metadataForUpload.size = NCUtilityFileSystem.shared.getFileSize(filePath: fileNamePath)
+        metadataForUpload.size = utilityFileSystem.getFileSize(filePath: fileNamePath)
 
         if NCManageDatabase.shared.getMetadataConflict(account: appDelegate.account, serverUrl: serverUrl, fileNameView: fileNameSave) != nil {
 
@@ -259,7 +258,7 @@ class NCCreateFormUploadVoiceNote: XLFormViewController, NCSelectDelegate, AVAud
 
     func dismissAndUpload(_ metadata: tableMetadata) {
 
-        CCUtility.copyFile(atPath: self.fileNamePath, toPath: CCUtility.getDirectoryProviderStorageOcId(metadata.ocId, fileNameView: metadata.fileNameView))
+        utilityFileSystem.copyFile(atPath: self.fileNamePath, toPath: utilityFileSystem.getDirectoryProviderStorageOcId(metadata.ocId, fileNameView: metadata.fileNameView))
 
         NCNetworkingProcessUpload.shared.createProcessUploads(metadatas: [metadata], completion: { _ in })
 

+ 15 - 12
iOSClient/Main/Create cloud/NCUploadAssets.swift

@@ -133,7 +133,7 @@ class NCUploadAssets: NSObject, ObservableObject, NCCreateFormUploadConflictDele
                         createProcessUploads()
                     } else {
                         let error = NKError(errorCode: NCGlobal.shared.errorInternalError, errorDescription: "_error_createsubfolders_upload_")
-                        NCContentPresenter.shared.showError(error: error)
+                        NCContentPresenter().showError(error: error)
                     }
                 }
             }
@@ -147,9 +147,9 @@ class NCUploadAssets: NSObject, ObservableObject, NCCreateFormUploadConflictDele
 
 struct UploadAssetsView: View {
 
-    @State private var fileName: String = CCUtility.getFileNameMask(NCGlobal.shared.keyFileNameMask)
-    @State private var isMaintainOriginalFilename: Bool = CCUtility.getOriginalFileName(NCGlobal.shared.keyFileNameOriginal)
-    @State private var isAddFilenametype: Bool = CCUtility.getFileNameType(NCGlobal.shared.keyFileNameType)
+    @State private var fileName: String = NCKeychain().getFileNameMask(key: NCGlobal.shared.keyFileNameMask)
+    @State private var isMaintainOriginalFilename: Bool = NCKeychain().getOriginalFileName(key: NCGlobal.shared.keyFileNameOriginal)
+    @State private var isAddFilenametype: Bool = NCKeychain().getFileNameType(key: NCGlobal.shared.keyFileNameType)
     @State private var isPresentedSelect = false
     @State private var isPresentedUploadConflict = false
     @State private var isPresentedQuickLook = false
@@ -185,9 +185,9 @@ struct UploadAssetsView: View {
         var preview: String = ""
         let creationDate = asset.creationDate ?? Date()
 
-        CCUtility.setOriginalFileName(isMaintainOriginalFilename, key: NCGlobal.shared.keyFileNameOriginal)
-        CCUtility.setFileNameType(isAddFilenametype, key: NCGlobal.shared.keyFileNameType)
-        CCUtility.setFileNameMask(fileName, key: NCGlobal.shared.keyFileNameMask)
+        NCKeychain().setOriginalFileName(key: NCGlobal.shared.keyFileNameOriginal, value: isMaintainOriginalFilename)
+        NCKeychain().setFileNameType(key: NCGlobal.shared.keyFileNameType, prefix: isAddFilenametype)
+        NCKeychain().setFileNameMask(key: NCGlobal.shared.keyFileNameMask, mask: fileName)
 
         preview = CCUtility.createFileName(
             getOriginalFilenameForPreview() as String,
@@ -206,6 +206,7 @@ struct UploadAssetsView: View {
 
     private func save(completion: @escaping (_ metadatasNOConflict: [tableMetadata], _ metadatasUploadInConflict: [tableMetadata]) -> Void) {
 
+        let utilityFileSystem = NCUtilityFileSystem()
         var metadatasNOConflict: [tableMetadata] = []
         var metadatasUploadInConflict: [tableMetadata] = []
         let autoUploadPath = NCManageDatabase.shared.getAccountAutoUploadPath(urlBase: uploadAssets.userBaseUrl.urlBase, userId: uploadAssets.userBaseUrl.userId, account: uploadAssets.userBaseUrl.account)
@@ -230,7 +231,7 @@ struct UploadAssetsView: View {
                                        forcedNewFileName: false)!
             : (previewStore.fileName + "." + ext)
 
-            if previewStore.assetType == .livePhoto && CCUtility.getLivePhoto() && previewStore.data == nil {
+            if previewStore.assetType == .livePhoto && NCKeychain().livePhoto && previewStore.data == nil {
                 livePhoto = true
             }
 
@@ -271,11 +272,11 @@ struct UploadAssetsView: View {
                     metadata.fileName = fileNameNoExtension + ".jpg"
                     metadata.fileNameView = fileNameNoExtension + ".jpg"
                 }
-                let fileNamePath = CCUtility.getDirectoryProviderStorageOcId(metadata.ocId, fileNameView: metadata.fileNameView)!
+                let fileNamePath = utilityFileSystem.getDirectoryProviderStorageOcId(metadata.ocId, fileNameView: metadata.fileNameView)
                 do {
                     try data.write(to: URL(fileURLWithPath: fileNamePath))
                     metadata.isExtractFile = true
-                    metadata.size = NCUtilityFileSystem.shared.getFileSize(filePath: fileNamePath)
+                    metadata.size = utilityFileSystem.getFileSize(filePath: fileNamePath)
                     metadata.creationDate = asset.creationDate as? NSDate ?? (Date() as NSDate)
                     metadata.date = asset.modificationDate as? NSDate ?? (Date() as NSDate)
                 } catch {  }
@@ -323,7 +324,7 @@ struct UploadAssetsView: View {
     }
 
     private func getOriginalFilenameForPreview() -> NSString {
-        CCUtility.setOriginalFileName(isMaintainOriginalFilename, key: NCGlobal.shared.keyFileNameOriginal)
+        NCKeychain().setOriginalFileName(key: NCGlobal.shared.keyFileNameOriginal, value: isMaintainOriginalFilename)
 
         if let asset = uploadAssets.assets.first?.phAsset {
             return asset.originalFilename
@@ -334,6 +335,8 @@ struct UploadAssetsView: View {
 
     var body: some View {
 
+        let utilityFileSystem = NCUtilityFileSystem()
+
         NavigationView {
             ZStack(alignment: .top) {
                 List {
@@ -438,7 +441,7 @@ struct UploadAssetsView: View {
                         if !uploadAssets.isUseAutoUploadFolder {
                             HStack {
                                 Label {
-                                    if NCUtilityFileSystem.shared.getHomeServer(urlBase: uploadAssets.userBaseUrl.urlBase, userId: uploadAssets.userBaseUrl.userId) == uploadAssets.serverUrl {
+                                    if utilityFileSystem.getHomeServer(urlBase: uploadAssets.userBaseUrl.urlBase, userId: uploadAssets.userBaseUrl.userId) == uploadAssets.serverUrl {
                                         Text("/")
                                             .font(.system(size: 15))
                                             .frame(maxWidth: .infinity, alignment: .trailing)

+ 51 - 51
iOSClient/Main/NCActionCenter.swift

@@ -39,6 +39,8 @@ class NCActionCenter: NSObject, UIDocumentInteractionControllerDelegate, NCSelec
 
     var viewerQuickLook: NCViewerQuickLook?
     var documentController: UIDocumentInteractionController?
+    let utilityFileSystem = NCUtilityFileSystem()
+    let utility = NCUtility()
 
     // MARK: - Download
 
@@ -57,12 +59,12 @@ class NCActionCenter: NSObject, UIDocumentInteractionControllerDelegate, NCSelec
             // File do not exists on server, remove in local
             if error.errorCode == NCGlobal.shared.errorResourceNotFound || error.errorCode == NCGlobal.shared.errorBadServerResponse {
                 do {
-                    try FileManager.default.removeItem(atPath: CCUtility.getDirectoryProviderStorageOcId(ocId))
+                    try FileManager.default.removeItem(atPath: utilityFileSystem.getDirectoryProviderStorageOcId(ocId))
                 } catch { }
                 NCManageDatabase.shared.deleteMetadata(predicate: NSPredicate(format: "ocId == %@", ocId))
                 NCManageDatabase.shared.deleteLocalFile(predicate: NSPredicate(format: "ocId == %@", ocId))
             } else {
-                NCContentPresenter.shared.messageNotification("_download_file_", error: error, delay: NCGlobal.shared.dismissAfterSecond, type: NCContentPresenter.messageType.error, priority: .max)
+                NCContentPresenter().messageNotification("_download_file_", error: error, delay: NCGlobal.shared.dismissAfterSecond, type: NCContentPresenter.messageType.error, priority: .max)
             }
             return
         }
@@ -70,7 +72,7 @@ class NCActionCenter: NSObject, UIDocumentInteractionControllerDelegate, NCSelec
 
         switch selector {
         case NCGlobal.shared.selectorLoadFileQuickLook:
-            let fileNamePath = CCUtility.getDirectoryProviderStorageOcId(metadata.ocId, fileNameView: metadata.fileNameView)!
+            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) {
@@ -85,21 +87,21 @@ class NCActionCenter: NSObject, UIDocumentInteractionControllerDelegate, NCSelec
                 navigationController.modalPresentationStyle = .fullScreen
                 appDelegate.window?.rootViewController?.present(navigationController, animated: true)
             } else {
-                CCUtility.copyFile(atPath: fileNamePath, toPath: fileNameTemp)
+                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") && !NCUtility.shared.isRichDocument(metadata) {
+            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: CCUtility.getDirectoryProviderStorageIconOcId(metadata.ocId, etag: metadata.etag))
-                    NCViewer.shared.view(viewController: viewController, metadata: metadata, metadatas: [metadata], imageIcon: imageIcon)
+                    let imageIcon = UIImage(contentsOfFile: utilityFileSystem.getDirectoryProviderStorageIconOcId(metadata.ocId, etag: metadata.etag))
+                    NCViewer().view(viewController: viewController, metadata: metadata, metadatas: [metadata], imageIcon: imageIcon)
                 }
             }
 
@@ -159,17 +161,15 @@ class NCActionCenter: NSObject, UIDocumentInteractionControllerDelegate, NCSelec
         var downloadRequest: DownloadRequest?
 
         if let metadata = NCManageDatabase.shared.getMetadataFromFileId(fileId) {
-            if let filePath = CCUtility.getDirectoryProviderStorageOcId(metadata.ocId, fileNameView: metadata.fileNameView) {
-                do {
-                    let attr = try FileManager.default.attributesOfItem(atPath: filePath)
-                    let fileSize = attr[FileAttributeKey.size] as? UInt64 ?? 0
-                    if fileSize > 0 {
-                        NCViewer.shared.view(viewController: viewController, metadata: metadata, metadatas: [metadata], imageIcon: nil)
-                        return
-                    }
-                } catch {
-                    print("Error: \(error)")
+            do {
+                let attr = try FileManager.default.attributesOfItem(atPath: utilityFileSystem.getDirectoryProviderStorageOcId(metadata.ocId, fileNameView: metadata.fileNameView))
+                let fileSize = attr[FileAttributeKey.size] as? UInt64 ?? 0
+                if fileSize > 0 {
+                    NCViewer().view(viewController: viewController, metadata: metadata, metadatas: [metadata], imageIcon: nil)
+                    return
                 }
+            } catch {
+                print("Error: \(error)")
             }
         }
 
@@ -189,18 +189,18 @@ class NCActionCenter: NSObject, UIDocumentInteractionControllerDelegate, NCSelec
 
             hud.dismiss()
             if error != .success {
-                NCContentPresenter.shared.showError(error: error)
+                NCContentPresenter().showError(error: error)
             } else if let file = file {
 
-                let isDirectoryE2EE = NCUtility.shared.isDirectoryE2EE(file: file)
+                let isDirectoryE2EE = self.utilityFileSystem.isDirectoryE2EE(file: file)
                 let metadata = NCManageDatabase.shared.convertFileToMetadata(file, isDirectoryE2EE: isDirectoryE2EE)
                 NCManageDatabase.shared.addMetadata(metadata)
 
                 let serverUrlFileName = metadata.serverUrl + "/" + metadata.fileName
-                let fileNameLocalPath = CCUtility.getDirectoryProviderStorageOcId(metadata.ocId, fileNameView: metadata.fileNameView)!
+                let fileNameLocalPath = self.utilityFileSystem.getDirectoryProviderStorageOcId(metadata.ocId, fileNameView: metadata.fileNameView)
 
                 if metadata.isAudioOrVideo {
-                    NCViewer.shared.view(viewController: viewController, metadata: metadata, metadatas: [metadata], imageIcon: nil)
+                    NCViewer().view(viewController: viewController, metadata: metadata, metadatas: [metadata], imageIcon: nil)
                 } else {
                     hud.show(in: hudView)
                     NextcloudKit.shared.download(serverUrlFileName: serverUrlFileName, fileNameLocalPath: fileNameLocalPath, requestHandler: { request in
@@ -212,13 +212,13 @@ class NCActionCenter: NSObject, UIDocumentInteractionControllerDelegate, NCSelec
                         hud.dismiss()
                         if account == accountDownload && error == .success {
                             NCManageDatabase.shared.addLocalFile(metadata: metadata)
-                            NCViewer.shared.view(viewController: viewController, metadata: metadata, metadatas: [metadata], imageIcon: nil)
+                            NCViewer().view(viewController: viewController, metadata: metadata, metadatas: [metadata], imageIcon: nil)
                         }
                     }
                 }
             } else {
                 let error = NKError(errorCode: NCGlobal.shared.errorInternalError, errorDescription: "_file_not_found_")
-                NCContentPresenter.shared.showError(error: error)
+                NCContentPresenter().showError(error: error)
             }
         }
     }
@@ -235,7 +235,7 @@ class NCActionCenter: NSObject, UIDocumentInteractionControllerDelegate, NCSelec
         else { return }
 
         if error != .success, error.errorCode != NSURLErrorCancelled, error.errorCode != NCGlobal.shared.errorRequestExplicityCancelled {
-            NCContentPresenter.shared.messageNotification("_upload_file_", error: error, delay: NCGlobal.shared.dismissAfterSecond, type: NCContentPresenter.messageType.error, priority: .max)
+            NCContentPresenter().messageNotification("_upload_file_", error: error, delay: NCGlobal.shared.dismissAfterSecond, type: NCContentPresenter.messageType.error, priority: .max)
         }
     }
 
@@ -296,7 +296,7 @@ class NCActionCenter: NSObject, UIDocumentInteractionControllerDelegate, NCSelec
 
         guard let appDelegate = UIApplication.shared.delegate as? AppDelegate,
               let mainTabBar = appDelegate.mainTabBar else { return }
-        let fileURL = URL(fileURLWithPath: CCUtility.getDirectoryProviderStorageOcId(metadata.ocId, fileNameView: metadata.fileNameView))
+        let fileURL = URL(fileURLWithPath: utilityFileSystem.getDirectoryProviderStorageOcId(metadata.ocId, fileNameView: metadata.fileNameView))
 
         documentController = UIDocumentInteractionController(url: fileURL)
         documentController?.presentOptionsMenu(from: mainTabBar.menuRect, in: mainTabBar, animated: true)
@@ -310,8 +310,8 @@ class NCActionCenter: NSObject, UIDocumentInteractionControllerDelegate, NCSelec
         var downloadMetadata: [(tableMetadata, URL)] = []
 
         for metadata in metadatas {
-            let fileURL = URL(fileURLWithPath: CCUtility.getDirectoryProviderStorageOcId(metadata.ocId, fileNameView: metadata.fileNameView))
-            if CCUtility.fileProviderStorageExists(metadata) {
+            let fileURL = URL(fileURLWithPath: utilityFileSystem.getDirectoryProviderStorageOcId(metadata.ocId, fileNameView: metadata.fileNameView))
+            if utilityFileSystem.fileProviderStorageExists(metadata) {
                 items.append(fileURL)
             } else {
                 downloadMetadata.append((metadata, fileURL))
@@ -325,7 +325,7 @@ class NCActionCenter: NSObject, UIDocumentInteractionControllerDelegate, NCSelec
                 } progressHandler: { progress in
                     processor.hud?.progress = Float(progress.fractionCompleted)
                 } completion: { _, _ in
-                    if CCUtility.fileProviderStorageExists(metadata) { items.append(url) }
+                    if self.utilityFileSystem.fileProviderStorageExists(metadata) { items.append(url) }
                     completion()
                 }
             }
@@ -346,11 +346,11 @@ class NCActionCenter: NSObject, UIDocumentInteractionControllerDelegate, NCSelec
     func saveAsScan(metadata: tableMetadata) {
         guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else { return }
 
-        let fileNamePath = CCUtility.getDirectoryProviderStorageOcId(metadata.ocId, fileNameView: metadata.fileNameView)!
+        let fileNamePath = utilityFileSystem.getDirectoryProviderStorageOcId(metadata.ocId, fileNameView: metadata.fileNameView)
         let fileNameDestination = CCUtility.createFileName("scan.png", fileDate: Date(), fileType: PHAssetMediaType.image, keyFileName: NCGlobal.shared.keyFileNameMask, keyFileNameType: NCGlobal.shared.keyFileNameType, keyFileNameOriginal: NCGlobal.shared.keyFileNameOriginal, forcedNewFileName: true)!
-        let fileNamePathDestination = CCUtility.getDirectoryScan() + "/" + fileNameDestination
+        let fileNamePathDestination = utilityFileSystem.directoryScan + "/" + fileNameDestination
 
-        NCUtilityFileSystem.shared.copyFile(atPath: fileNamePath, toPath: fileNamePathDestination)
+        utilityFileSystem.copyFile(atPath: fileNamePath, toPath: fileNamePathDestination)
 
         let storyboard = UIStoryboard(name: "NCScan", bundle: nil)
         let navigationController = storyboard.instantiateInitialViewController()!
@@ -364,7 +364,7 @@ class NCActionCenter: NSObject, UIDocumentInteractionControllerDelegate, NCSelec
 
     func printDocument(metadata: tableMetadata) {
 
-        let fileNameURL = URL(fileURLWithPath: CCUtility.getDirectoryProviderStorageOcId(metadata.ocId, fileNameView: metadata.fileNameView)!)
+        let fileNameURL = URL(fileURLWithPath: utilityFileSystem.getDirectoryProviderStorageOcId(metadata.ocId, fileNameView: metadata.fileNameView))
         let printController = UIPrintInteractionController.shared
         let printInfo = UIPrintInfo(dictionary: nil)
 
@@ -403,12 +403,12 @@ class NCActionCenter: NSObject, UIDocumentInteractionControllerDelegate, NCSelec
     func saveAlbum(metadata: tableMetadata) {
         guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else { return }
 
-        let fileNamePath = CCUtility.getDirectoryProviderStorageOcId(metadata.ocId, fileNameView: metadata.fileNameView)!
+        let fileNamePath = utilityFileSystem.getDirectoryProviderStorageOcId(metadata.ocId, fileNameView: metadata.fileNameView)
 
-        NCAskAuthorization.shared.askAuthorizationPhotoLibrary(viewController: appDelegate.mainTabBar?.window?.rootViewController) { hasPermission in
+        NCAskAuthorization().askAuthorizationPhotoLibrary(viewController: appDelegate.mainTabBar?.window?.rootViewController) { hasPermission in
             guard hasPermission else {
                 let error = NKError(errorCode: NCGlobal.shared.errorFileNotSaved, errorDescription: "_access_photo_not_enabled_msg_")
-                return NCContentPresenter.shared.messageNotification("_access_photo_not_enabled_", error: error, delay: NCGlobal.shared.dismissAfterSecond, type: NCContentPresenter.messageType.error)
+                return NCContentPresenter().messageNotification("_access_photo_not_enabled_", error: error, delay: NCGlobal.shared.dismissAfterSecond, type: NCContentPresenter.messageType.error)
             }
 
             let errorSave = NKError(errorCode: NCGlobal.shared.errorFileNotSaved, errorDescription: "_file_not_saved_cameraroll_")
@@ -421,7 +421,7 @@ class NCActionCenter: NSObject, UIDocumentInteractionControllerDelegate, NCSelec
                         assetRequest.addResource(with: .photo, data: data, options: nil)
                     }) { success, _ in
                         if !success {
-                            NCContentPresenter.shared.messageNotification("_save_selected_files_", error: errorSave, delay: NCGlobal.shared.dismissAfterSecond, type: NCContentPresenter.messageType.error)
+                            NCContentPresenter().messageNotification("_save_selected_files_", error: errorSave, delay: NCGlobal.shared.dismissAfterSecond, type: NCContentPresenter.messageType.error)
                         }
                     }
                 } else if metadata.isVideo {
@@ -429,15 +429,15 @@ class NCActionCenter: NSObject, UIDocumentInteractionControllerDelegate, NCSelec
                         PHAssetChangeRequest.creationRequestForAssetFromVideo(atFileURL: URL(fileURLWithPath: fileNamePath))
                     }) { success, _ in
                         if !success {
-                            NCContentPresenter.shared.messageNotification("_save_selected_files_", error: errorSave, delay: NCGlobal.shared.dismissAfterSecond, type: NCContentPresenter.messageType.error)
+                            NCContentPresenter().messageNotification("_save_selected_files_", error: errorSave, delay: NCGlobal.shared.dismissAfterSecond, type: NCContentPresenter.messageType.error)
                         }
                     }
                 } else {
-                    NCContentPresenter.shared.messageNotification("_save_selected_files_", error: errorSave, delay: NCGlobal.shared.dismissAfterSecond, type: NCContentPresenter.messageType.error)
+                    NCContentPresenter().messageNotification("_save_selected_files_", error: errorSave, delay: NCGlobal.shared.dismissAfterSecond, type: NCContentPresenter.messageType.error)
                     return
                 }
             } catch {
-                NCContentPresenter.shared.messageNotification("_save_selected_files_", error: errorSave, delay: NCGlobal.shared.dismissAfterSecond, type: NCContentPresenter.messageType.error)
+                NCContentPresenter().messageNotification("_save_selected_files_", error: errorSave, delay: NCGlobal.shared.dismissAfterSecond, type: NCContentPresenter.messageType.error)
             }
         }
     }
@@ -503,14 +503,14 @@ class NCActionCenter: NSObject, UIDocumentInteractionControllerDelegate, NCSelec
             } completionHandler: { account, ocId, etag, _, _, _, afError, error in
                 NCNetworking.shared.uploadRequest.removeValue(forKey: fileNameLocalPath)
                 if error == .success && etag != nil && ocId != nil {
-                    let toPath = CCUtility.getDirectoryProviderStorageOcId(ocId!, fileNameView: fileName)!
-                    NCUtilityFileSystem.shared.moveFile(atPath: fileNameLocalPath, toPath: toPath)
+                    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)
                 } else if afError?.isExplicitlyCancelledError ?? false {
                     print("cancel")
                 } else {
-                    NCContentPresenter.shared.showError(error: error)
+                    NCContentPresenter().showError(error: error)
                 }
                 fractionCompleted = 0
                 completion()
@@ -523,10 +523,10 @@ class NCActionCenter: NSObject, UIDocumentInteractionControllerDelegate, NCSelec
                 guard !results.ext.isEmpty,
                       let data = UIPasteboard.general.data(forPasteboardType: item.key, inItemSet: IndexSet([index]))?.first
                 else { continue }
-                let fileName = results.name + "_" + CCUtility.getIncrementalNumber() + "." + results.ext
+                let fileName = results.name + "_" + NCKeychain().incrementalNumber + "." + results.ext
                 let serverUrlFileName = serverUrl + "/" + fileName
                 let ocIdUpload = UUID().uuidString
-                let fileNameLocalPath = CCUtility.getDirectoryProviderStorageOcId(ocIdUpload, fileNameView: fileName)!
+                let fileNameLocalPath = utilityFileSystem.getDirectoryProviderStorageOcId(ocIdUpload, fileNameView: fileName)
                 do { try data.write(to: URL(fileURLWithPath: fileNameLocalPath)) } catch { continue }
                 processor.execute { completion in
                     uploadPastePasteboard(fileName: fileName, serverUrlFileName: serverUrlFileName, fileNameLocalPath: fileNameLocalPath, serverUrl: serverUrl, completion: completion)
@@ -543,7 +543,7 @@ class NCActionCenter: NSObject, UIDocumentInteractionControllerDelegate, NCSelec
 
         DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
             var topNavigationController: UINavigationController?
-            var pushServerUrl = NCUtilityFileSystem.shared.getHomeServer(urlBase: appDelegate.urlBase, userId: appDelegate.userId)
+            var pushServerUrl = self.utilityFileSystem.getHomeServer(urlBase: appDelegate.urlBase, userId: appDelegate.userId)
             guard var mostViewController = appDelegate.window?.rootViewController?.topMostViewController() else { return }
 
             if mostViewController.isModal {
@@ -615,7 +615,7 @@ class NCActionCenter: NSObject, UIDocumentInteractionControllerDelegate, NCSelec
                         }
                     }
                     if error != .success {
-                        NCContentPresenter.shared.showError(error: error)
+                        NCContentPresenter().showError(error: error)
                     }
                     NotificationCenter.default.postOnMainThread(name: NCGlobal.shared.notificationCenterCopyFile, userInfo: ["ocId": ocId, "indexPath": indexPath, "error": error, "hud": hud])
                 }
@@ -630,7 +630,7 @@ class NCActionCenter: NSObject, UIDocumentInteractionControllerDelegate, NCSelec
                         }
                     }
                     if error != .success {
-                        NCContentPresenter.shared.showError(error: error)
+                        NCContentPresenter().showError(error: error)
                     }
                     NotificationCenter.default.postOnMainThread(name: NCGlobal.shared.notificationCenterMoveFile, userInfo: ["ocId": ocId, "indexPath": indexPath, "error": error, "hud": hud])
                 }
@@ -650,7 +650,7 @@ class NCActionCenter: NSObject, UIDocumentInteractionControllerDelegate, NCSelec
             copyItems.append(item)
         }
 
-        let homeUrl = NCUtilityFileSystem.shared.getHomeServer(urlBase: appDelegate.urlBase, userId: appDelegate.userId)
+        let homeUrl = utilityFileSystem.getHomeServer(urlBase: appDelegate.urlBase, userId: appDelegate.userId)
         var serverUrl = copyItems[0].serverUrl
 
         // Setup view controllers such that the current view is of the same directory the items to be copied are in
@@ -679,7 +679,7 @@ class NCActionCenter: NSObject, UIDocumentInteractionControllerDelegate, NCSelec
             listViewController.insert(vc, at: 0)
 
             if serverUrl != homeUrl {
-                if let path = NCUtilityFileSystem.shared.deleteLastPath(serverUrlPath: serverUrl) {
+                if let path = utilityFileSystem.deleteLastPath(serverUrlPath: serverUrl) {
                     serverUrl = path
                 }
             } else {
@@ -699,8 +699,8 @@ class NCActionCenter: NSObject, UIDocumentInteractionControllerDelegate, NCSelec
 fileprivate extension tableMetadata {
     func toPasteBoardItem() -> [String: Any]? {
         // Get Data
-        let fileUrl = URL(fileURLWithPath: CCUtility.getDirectoryProviderStorageOcId(ocId, fileNameView: fileNameView))
-        guard CCUtility.fileProviderStorageExists(self),
+        let fileUrl = URL(fileURLWithPath: NCUtilityFileSystem().getDirectoryProviderStorageOcId(ocId, fileNameView: fileNameView))
+        guard NCUtilityFileSystem().fileProviderStorageExists(self),
               let data = try? Data(contentsOf: fileUrl),
               let unmanagedFileUTI = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, fileExtension as CFString, nil)
         else { return nil }

+ 2 - 6
iOSClient/Main/NCMainTabBar.swift

@@ -28,11 +28,7 @@ class NCMainTabBar: UITabBar {
 
     private var fillColor: UIColor!
     private var shapeLayer: CALayer?
-
-    // swiftlint:disable force_cast
-    private let appDelegate = UIApplication.shared.delegate as! AppDelegate
-    // swiftlint:enable force_cast
-
+    private let appDelegate = (UIApplication.shared.delegate as? AppDelegate)!
     private let centerButtonY: CGFloat = -28
 
     public var menuRect: CGRect {
@@ -196,7 +192,7 @@ class NCMainTabBar: UITabBar {
 
                 if !directory.permissions.contains("CK") {
                     let error = NKError(errorCode: NCGlobal.shared.errorInternalError, errorDescription: "_no_permission_add_file_")
-                    NCContentPresenter.shared.showWarning(error: error)
+                    NCContentPresenter().showWarning(error: error)
                     return
                 }
             }

+ 9 - 14
iOSClient/Main/NCPickerViewController.swift

@@ -31,10 +31,7 @@ import NextcloudKit
 
 class NCPhotosPickerViewController: NSObject {
 
-    // swiftlint:disable force_cast
-    let appDelegate = UIApplication.shared.delegate as! AppDelegate
-    // swiftlint:enable force_cast
-
+    let appDelegate = (UIApplication.shared.delegate as? AppDelegate)!
     var sourceViewController: UIViewController
     var maxSelectedAssets = 1
     var singleSelectedMode = false
@@ -81,17 +78,17 @@ class NCPhotosPickerViewController: NSObject {
 
         viewController.didExceedMaximumNumberOfSelection = { _ in
             let error = NKError(errorCode: NCGlobal.shared.errorInternalError, errorDescription: "_limited_dimension_")
-            NCContentPresenter.shared.showError(error: error)
+            NCContentPresenter().showError(error: error)
         }
 
         viewController.handleNoAlbumPermissions = { _ in
             let error = NKError(errorCode: NCGlobal.shared.errorInternalError, errorDescription: "_denied_album_")
-            NCContentPresenter.shared.showError(error: error)
+            NCContentPresenter().showError(error: error)
         }
 
         viewController.handleNoCameraPermissions = { _ in
             let error = NKError(errorCode: NCGlobal.shared.errorInternalError, errorDescription: "_denied_camera_")
-            NCContentPresenter.shared.showError(error: error)
+            NCContentPresenter().showError(error: error)
         }
 
         viewController.configure = configure
@@ -118,10 +115,8 @@ class customPhotoPickerViewController: TLPhotosPickerViewController {
 
 class NCDocumentPickerViewController: NSObject, UIDocumentPickerDelegate {
 
-    // swiftlint:disable force_cast
-    let appDelegate = UIApplication.shared.delegate as! AppDelegate
-    // swiftlint:enable force_cast
-
+    let appDelegate = (UIApplication.shared.delegate as? AppDelegate)!
+    let utilityFileSystem = NCUtilityFileSystem()
     var isViewerMedia: Bool
     var viewController: UIViewController?
 
@@ -156,7 +151,7 @@ class NCDocumentPickerViewController: NSObject, UIDocumentPickerDelegate {
                 metadata.classFile = NKCommon.TypeClassFile.video.rawValue
             }
             NCManageDatabase.shared.addMetadata(metadata)
-            NCViewer.shared.view(viewController: viewController, metadata: metadata, metadatas: [metadata], imageIcon: nil)
+            NCViewer().view(viewController: viewController, metadata: metadata, metadatas: [metadata], imageIcon: nil)
 
         } else {
             let serverUrl = appDelegate.activeServerUrl
@@ -168,7 +163,7 @@ class NCDocumentPickerViewController: NSObject, UIDocumentPickerDelegate {
                 let ocId = NSUUID().uuidString
 
                 let fileName = urlIn.lastPathComponent
-                let toPath = CCUtility.getDirectoryProviderStorageOcId(ocId, fileNameView: fileName)!
+                let toPath = utilityFileSystem.getDirectoryProviderStorageOcId(ocId, fileNameView: fileName)
                 let urlOut = URL(fileURLWithPath: toPath)
 
                 guard self.copySecurityScopedResource(url: urlIn, urlOut: urlOut) != nil else { continue }
@@ -177,7 +172,7 @@ class NCDocumentPickerViewController: NSObject, UIDocumentPickerDelegate {
 
                 metadataForUpload.session = NCNetworking.shared.sessionIdentifierBackground
                 metadataForUpload.sessionSelector = NCGlobal.shared.selectorUploadFile
-                metadataForUpload.size = NCUtilityFileSystem.shared.getFileSize(filePath: toPath)
+                metadataForUpload.size = utilityFileSystem.getFileSize(filePath: toPath)
                 metadataForUpload.status = NCGlobal.shared.metadataStatusWaitUpload
 
                 if NCManageDatabase.shared.getMetadataConflict(account: appDelegate.account, serverUrl: serverUrl, fileNameView: fileName) != nil {

+ 5 - 4
iOSClient/Main/Section Header Footer/NCSectionHeaderMenu.swift

@@ -50,7 +50,7 @@ class NCSectionHeaderMenu: UICollectionReusableView, UIGestureRecognizerDelegate
     @IBOutlet weak var transferSeparatorBottomHeightConstraint: NSLayoutConstraint!
 
     weak var delegate: NCSectionHeaderMenuDelegate?
-
+    let utility = NCUtility()
     private var markdownParser = MarkdownParser()
     private var richWorkspaceText: String?
     private var textViewColor: UIColor?
@@ -200,7 +200,7 @@ class NCSectionHeaderMenu: UICollectionReusableView, UIGestureRecognizerDelegate
             var image: UIImage?
             if let ocId,
                let metadata = NCManageDatabase.shared.getMetadataFromOcId(ocId) {
-                image = NCUtility.shared.createFilePreviewImage(ocId: metadata.ocId, etag: metadata.etag, fileNameView: metadata.fileNameView, classFile: metadata.classFile, status: metadata.status, createPreviewMedia: true)?.darken()
+                image = utility.createFilePreviewImage(ocId: metadata.ocId, etag: metadata.etag, fileNameView: metadata.fileNameView, classFile: metadata.classFile, status: metadata.status, createPreviewMedia: true)?.darken()
                 if image == nil {
                     image = UIImage(named: metadata.iconName)
                     buttonTransfer.backgroundColor = .lightGray
@@ -291,6 +291,7 @@ class NCSectionFooter: UICollectionReusableView, NCSectionFooterDelegate {
 
     weak var delegate: NCSectionFooterDelegate?
     var metadataForSection: NCMetadataForSection?
+    let utilityFileSystem = NCUtilityFileSystem()
 
     override func awakeFromNib() {
         super.awakeFromNib()
@@ -319,9 +320,9 @@ class NCSectionFooter: UICollectionReusableView, NCSectionFooterDelegate {
         }
 
         if files > 1 {
-            filesText = "\(files) " + NSLocalizedString("_files_", comment: "") + " " + CCUtility.transformedSize(size)
+            filesText = "\(files) " + NSLocalizedString("_files_", comment: "") + " " + utilityFileSystem.transformedSize(size)
         } else if files == 1 {
-            filesText = "1 " + NSLocalizedString("_file_", comment: "") + " " + CCUtility.transformedSize(size)
+            filesText = "1 " + NSLocalizedString("_file_", comment: "") + " " + utilityFileSystem.transformedSize(size)
         }
 
         if foldersText.isEmpty {

+ 1 - 1
iOSClient/Media/Cell/NCGridMediaCell.swift

@@ -76,7 +76,7 @@ class NCGridMediaCell: UICollectionViewCell, NCCellProtocol {
 
     func selected(_ status: Bool) {
         if status {
-            imageSelect.image = NCBrandColor.cacheImages.checkedYes
+            imageSelect.image = NCImageCache.images.checkedYes
             imageVisualEffect.isHidden = false
             imageVisualEffect.alpha = 0.4
         } else {

+ 140 - 100
iOSClient/Media/NCMedia.swift

@@ -24,6 +24,7 @@
 import UIKit
 import NextcloudKit
 import JGProgressHUD
+import Queuer
 
 class NCMedia: UIViewController, NCEmptyDataSetDelegate, NCSelectDelegate {
 
@@ -34,22 +35,16 @@ class NCMedia: UIViewController, NCEmptyDataSetDelegate, NCSelectDelegate {
     private var gridLayout: NCGridMediaLayout!
     internal var documentPickerViewController: NCDocumentPickerViewController?
 
-    // swiftlint:disable force_cast
-    internal let appDelegate = UIApplication.shared.delegate as! AppDelegate
-    // swiftlint:enable force_cast
-
-    public var metadatas: [tableMetadata] = []
-    private var account: String = ""
-
-    private var predicateDefault: NSPredicate?
-    private var predicate: NSPredicate?
+    internal let appDelegate = (UIApplication.shared.delegate as? AppDelegate)!
+    internal let utilityFileSystem = NCUtilityFileSystem()
+    internal let utility = NCUtility()
 
     internal var isEditMode = false
     internal var selectOcId: [String] = []
     internal var selectIndexPath: [IndexPath] = []
 
-    internal var filterClassTypeImage = false
-    internal var filterClassTypeVideo = false
+    internal var showOnlyImages = false
+    internal var showOnlyVideos = false
 
     private let maxImageGrid: CGFloat = 7
     private var cellHeigth: CGFloat = 0
@@ -59,7 +54,6 @@ class NCMedia: UIViewController, NCEmptyDataSetDelegate, NCSelectDelegate {
 
     private var lastContentOffsetY: CGFloat = 0
     private var mediaPath = ""
-    private var livePhoto: Bool = false
 
     private var timeIntervalSearchNewMedia: TimeInterval = 3.0
     private var timerSearchNewMedia: Timer?
@@ -69,6 +63,7 @@ class NCMedia: UIViewController, NCEmptyDataSetDelegate, NCSelectDelegate {
     struct cacheImages {
         static var cellLivePhotoImage = UIImage()
         static var cellPlayImage = UIImage()
+        static var cellImage = UIImage()
     }
 
     // MARK: - View Life Cycle
@@ -85,7 +80,7 @@ class NCMedia: UIViewController, NCEmptyDataSetDelegate, NCSelectDelegate {
         collectionView.backgroundColor = .systemBackground
 
         gridLayout = NCGridMediaLayout()
-        gridLayout.itemForLine = CGFloat(min(CCUtility.getMediaWidthImage(), 5))
+        gridLayout.itemForLine = CGFloat(min(NCKeychain().mediaWidthImage, 5))
         gridLayout.sectionHeadersPinToVisibleBounds = true
 
         collectionView.collectionViewLayout = gridLayout
@@ -108,8 +103,10 @@ class NCMedia: UIViewController, NCEmptyDataSetDelegate, NCSelectDelegate {
 
         collectionView.prefetchDataSource = self
 
-        cacheImages.cellLivePhotoImage = NCUtility.shared.loadImage(named: "livephoto", color: .white)
-        cacheImages.cellPlayImage = NCUtility.shared.loadImage(named: "play.fill", color: .white)
+        cacheImages.cellLivePhotoImage = utility.loadImage(named: "livephoto", color: .white)
+        cacheImages.cellPlayImage = utility.loadImage(named: "play.fill", color: .white)
+
+        if let activeAccount = NCManageDatabase.shared.getActiveAccount() { self.mediaPath = activeAccount.mediaPath }
     }
 
     override func viewWillAppear(_ animated: Bool) {
@@ -125,10 +122,10 @@ class NCMedia: UIViewController, NCEmptyDataSetDelegate, NCSelectDelegate {
         NotificationCenter.default.addObserver(self, selector: #selector(renameFile(_:)), name: NSNotification.Name(rawValue: NCGlobal.shared.notificationCenterRenameFile), object: nil)
         NotificationCenter.default.addObserver(self, selector: #selector(uploadedFile(_:)), name: NSNotification.Name(rawValue: NCGlobal.shared.notificationCenterUploadedFile), object: nil)
 
-        self.reloadDataSourceWithCompletion { _ in
-            self.timerSearchNewMedia?.invalidate()
-            self.timerSearchNewMedia = Timer.scheduledTimer(timeInterval: self.timeIntervalSearchNewMedia, target: self, selector: #selector(self.searchNewMediaTimer), userInfo: nil, repeats: false)
-        }
+        timerSearchNewMedia?.invalidate()
+        timerSearchNewMedia = Timer.scheduledTimer(timeInterval: timeIntervalSearchNewMedia, target: self, selector: #selector(searchNewMediaTimer), userInfo: nil, repeats: false)
+
+        collectionView.reloadData()
     }
 
     override func viewDidAppear(_ animated: Bool) {
@@ -165,7 +162,7 @@ class NCMedia: UIViewController, NCEmptyDataSetDelegate, NCSelectDelegate {
               let error = userInfo["error"] as? NKError else { return }
         let onlyLocalCache: Bool = userInfo["onlyLocalCache"] as? Bool ?? false
 
-        self.queryDB(isForced: true)
+        NCImageCache.shared.getMediaMetadatas(account: appDelegate.account, predicate: getPredicate())
 
         if error == .success, let indexPath = userInfo["indexPath"] as? [IndexPath], !indexPath.isEmpty, !onlyLocalCache {
             collectionView?.performBatchUpdates({
@@ -175,7 +172,7 @@ class NCMedia: UIViewController, NCEmptyDataSetDelegate, NCSelectDelegate {
             })
         } else {
             if error != .success {
-                NCContentPresenter.shared.showError(error: error)
+                NCContentPresenter().showError(error: error)
             }
             self.collectionView?.reloadData()
         }
@@ -228,8 +225,9 @@ class NCMedia: UIViewController, NCEmptyDataSetDelegate, NCSelectDelegate {
         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 {
-                if cell.date != nil {
-                    mediaCommandView?.title.text = CCUtility.getTitleSectionDate(cell.date)
+                mediaCommandView?.title.text = ""
+                if let date = cell.date {
+                    mediaCommandView?.title.text = utility.getTitleFromDate(date)
                 }
             }
         }
@@ -247,7 +245,7 @@ class NCMedia: UIViewController, NCEmptyDataSetDelegate, NCSelectDelegate {
             }
 
             self.collectionView.collectionViewLayout.invalidateLayout()
-            CCUtility.setMediaWidthImage(Int(self.gridLayout.itemForLine))
+            NCKeychain().mediaWidthImage = Int(self.gridLayout.itemForLine)
         })
     }
 
@@ -263,7 +261,7 @@ class NCMedia: UIViewController, NCEmptyDataSetDelegate, NCSelectDelegate {
             }
 
             self.collectionView.collectionViewLayout.invalidateLayout()
-            CCUtility.setMediaWidthImage(Int(self.gridLayout.itemForLine))
+            NCKeychain().mediaWidthImage = Int(self.gridLayout.itemForLine)
         })
     }
 
@@ -277,8 +275,9 @@ class NCMedia: UIViewController, NCEmptyDataSetDelegate, NCSelectDelegate {
     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 path = CCUtility.returnPathfromServerUrl(serverUrl, urlBase: appDelegate.urlBase, userId: appDelegate.userId, account: appDelegate.account) ?? ""
-            NCManageDatabase.shared.setAccountMediaPath(path, account: appDelegate.account)
+        let home = utilityFileSystem.getHomeServer(urlBase: appDelegate.urlBase, userId: appDelegate.userId)
+        mediaPath = serverUrl.replacingOccurrences(of: home, with: "")
+        NCManageDatabase.shared.setAccountMediaPath(mediaPath, account: appDelegate.account)
         reloadDataSourceWithCompletion { _ in
             self.searchNewMedia()
         }
@@ -304,7 +303,7 @@ extension NCMedia: UICollectionViewDelegate {
 
     func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
 
-        let metadata = metadatas[indexPath.row]
+        let metadata = NCImageCache.shared.metadatas[indexPath.row]
         if isEditMode {
             if let index = selectOcId.firstIndex(of: metadata.ocId) {
                 selectOcId.remove(at: index)
@@ -320,14 +319,14 @@ extension NCMedia: UICollectionViewDelegate {
             // ACTIVE SERVERURL
             appDelegate.activeServerUrl = metadata.serverUrl
             let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "gridCell", for: indexPath) as? NCGridMediaCell
-            NCViewer.shared.view(viewController: self, metadata: metadata, metadatas: metadatas, imageIcon: cell?.imageItem.image)
+            NCViewer().view(viewController: self, metadata: metadata, metadatas: NCImageCache.shared.metadatas, imageIcon: cell?.imageItem.image)
         }
     }
 
     func collectionView(_ collectionView: UICollectionView, contextMenuConfigurationForItemAt indexPath: IndexPath, point: CGPoint) -> UIContextMenuConfiguration? {
 
         guard let cell = collectionView.cellForItem(at: indexPath) as? NCGridMediaCell else { return nil }
-        let metadata = metadatas[indexPath.row]
+        let metadata = NCImageCache.shared.metadatas[indexPath.row]
         let identifier = indexPath as NSCopying
         let image = cell.imageItem.image
 
@@ -363,25 +362,16 @@ extension NCMedia: UICollectionViewDataSource {
     }
 
     func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
-        emptyDataSet?.numberOfItemsInSection(metadatas.count, section: section)
-        return metadatas.count
-    }
-
-    func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
-        guard let cell = (cell as? NCGridMediaCell), indexPath.row < self.metadatas.count else { return }
-        let metadata = self.metadatas[indexPath.row]
-        if FileManager().fileExists(atPath: CCUtility.getDirectoryProviderStorageIconOcId(metadata.ocId, etag: metadata.etag)) {
-            cell.imageItem.backgroundColor = nil
-            cell.imageItem.image = UIImage(contentsOfFile: CCUtility.getDirectoryProviderStorageIconOcId(metadata.ocId, etag: metadata.etag))
-        } else {
-            NCOperationQueue.shared.downloadThumbnail(metadata: metadata, placeholder: false, cell: cell, view: collectionView)
-        }
+        emptyDataSet?.numberOfItemsInSection(NCImageCache.shared.metadatas.count, section: section)
+        return NCImageCache.shared.metadatas.count
     }
 
     func collectionView(_ collectionView: UICollectionView, didEndDisplaying cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
-        if !collectionView.indexPathsForVisibleItems.contains(indexPath) && indexPath.row < metadatas.count {
-            let metadata = metadatas[indexPath.row]
-            NCOperationQueue.shared.cancelDownloadThumbnail(metadata: metadata)
+        if !collectionView.indexPathsForVisibleItems.contains(indexPath) && indexPath.row < NCImageCache.shared.metadatas.count {
+            let metadata = NCImageCache.shared.metadatas[indexPath.row]
+            for case let operation as NCMediaDownloadThumbnaill in appDelegate.downloadThumbnailQueue.operations where operation.metadata.ocId == metadata.ocId {
+                operation.cancel()
+            }
         }
     }
 
@@ -389,9 +379,9 @@ extension NCMedia: UICollectionViewDataSource {
 
         guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "gridCell", for: indexPath) as? NCGridMediaCell else { return UICollectionViewCell() }
 
-        if indexPath.section < collectionView.numberOfSections && indexPath.row < collectionView.numberOfItems(inSection: indexPath.section) && indexPath.row < metadatas.count {
+        if indexPath.section < collectionView.numberOfSections && indexPath.row < collectionView.numberOfItems(inSection: indexPath.section) && indexPath.row < NCImageCache.shared.metadatas.count {
 
-            let metadata = metadatas[indexPath.row]
+            let metadata = NCImageCache.shared.metadatas[indexPath.row]
 
             self.cellHeigth = cell.frame.size.height
 
@@ -400,10 +390,30 @@ extension NCMedia: UICollectionViewDataSource {
             cell.indexPath = indexPath
             cell.fileUser = metadata.ownerId
 
+            if let cachedImage = NCImageCache.shared.getMediaImage(ocId: metadata.ocId), case let .actual(image) = cachedImage {
+                cell.imageItem.backgroundColor = nil
+                cell.imageItem.image = image
+            } else if FileManager().fileExists(atPath: utilityFileSystem.getDirectoryProviderStorageIconOcId(metadata.ocId, etag: metadata.etag)) {
+                if let image = UIImage(contentsOfFile: utilityFileSystem.getDirectoryProviderStorageIconOcId(metadata.ocId, etag: metadata.etag)) {
+                    cell.imageItem.backgroundColor = nil
+                    cell.imageItem.image = image
+                    NCImageCache.shared.setMediaImage(ocId: metadata.ocId, image: .actual(image))
+                }
+            } else {
+                if metadata.hasPreview && metadata.status == NCGlobal.shared.metadataStatusNormal && (!utilityFileSystem.fileProviderStoragePreviewIconExists(metadata.ocId, etag: metadata.etag)) {
+                    if appDelegate.downloadThumbnailQueue.operations.filter({ ($0 as? NCMediaDownloadThumbnaill)?.metadata.ocId == metadata.ocId }).isEmpty {
+                        appDelegate.downloadThumbnailQueue.addOperation(NCMediaDownloadThumbnaill(metadata: metadata, cell: cell, collectionView: collectionView))
+                    }
+                }
+                cell.imageStatus.image = nil
+            }
+
             if metadata.isAudioOrVideo {
                 cell.imageStatus.image = cacheImages.cellPlayImage
-            } else if metadata.livePhoto && livePhoto {
+            } else if metadata.livePhoto && NCImageCache.shared.livePhoto {
                 cell.imageStatus.image = cacheImages.cellLivePhotoImage
+            } else {
+                cell.imageStatus.image = nil
             }
 
             if isEditMode {
@@ -439,59 +449,31 @@ extension NCMedia: UICollectionViewDelegateFlowLayout {
 
 extension NCMedia {
 
-    // MARK: - Datasource
-
-    func queryDB(isForced: Bool) {
-
-        livePhoto = CCUtility.getLivePhoto()
+    func getPredicate(_ predicatedefault: Bool = false) -> NSPredicate {
 
-        if let activeAccount = NCManageDatabase.shared.getActiveAccount() {
-            self.mediaPath = activeAccount.mediaPath
-        }
-        let startServerUrl = NCUtilityFileSystem.shared.getHomeServer(urlBase: appDelegate.urlBase, userId: appDelegate.userId) + mediaPath
-
-        predicateDefault = NSPredicate(format: "account == %@ AND serverUrl BEGINSWITH %@ AND (classFile == %@ OR classFile == %@) AND NOT (session CONTAINS[c] 'upload')", appDelegate.account, startServerUrl, NKCommon.TypeClassFile.image.rawValue, NKCommon.TypeClassFile.video.rawValue)
+        let startServerUrl = NCUtilityFileSystem().getHomeServer(urlBase: appDelegate.urlBase, userId: appDelegate.userId) + mediaPath
+        let showAll = NSPredicate(format: "account == %@ AND serverUrl BEGINSWITH %@ AND (classFile == %@ OR classFile == %@) AND NOT (session CONTAINS[c] 'upload')", appDelegate.account, startServerUrl, NKCommon.TypeClassFile.image.rawValue, NKCommon.TypeClassFile.video.rawValue)
 
-        if filterClassTypeImage {
-            predicate = NSPredicate(format: "account == %@ AND serverUrl BEGINSWITH %@ AND classFile == %@ AND NOT (session CONTAINS[c] 'upload')", appDelegate.account, startServerUrl, NKCommon.TypeClassFile.video.rawValue)
-        } else if filterClassTypeVideo {
-            predicate = NSPredicate(format: "account == %@ AND serverUrl BEGINSWITH %@ AND classFile == %@ AND NOT (session CONTAINS[c] 'upload')", appDelegate.account, startServerUrl, NKCommon.TypeClassFile.image.rawValue)
+        if predicatedefault { return showAll }
+        if showOnlyImages {
+            return NSPredicate(format: "account == %@ AND serverUrl BEGINSWITH %@ AND classFile == %@ AND NOT (session CONTAINS[c] 'upload')", appDelegate.account, startServerUrl, NKCommon.TypeClassFile.image.rawValue)
+        } else if showOnlyVideos {
+            return NSPredicate(format: "account == %@ AND serverUrl BEGINSWITH %@ AND classFile == %@ AND NOT (session CONTAINS[c] 'upload')", appDelegate.account, startServerUrl, NKCommon.TypeClassFile.video.rawValue)
         } else {
-            predicate = predicateDefault
-        }
-
-        guard let predicate = predicate else { return }
-
-        self.metadatas = NCManageDatabase.shared.getMetadatasMedia(predicate: predicate, livePhoto: self.livePhoto)
-
-        switch CCUtility.getMediaSortDate() {
-        case "date":
-            self.metadatas = self.metadatas.sorted(by: {($0.date as Date) > ($1.date as Date)})
-        case "creationDate":
-            self.metadatas = self.metadatas.sorted(by: {($0.creationDate as Date) > ($1.creationDate as Date)})
-        case "uploadDate":
-            self.metadatas = self.metadatas.sorted(by: {($0.uploadDate as Date) > ($1.uploadDate as Date)})
-        default:
-            break
+           return showAll
         }
     }
 
     @objc func reloadDataSourceWithCompletion(_ completion: @escaping (_ metadatas: [tableMetadata]) -> Void) {
         guard !appDelegate.account.isEmpty else { return }
 
-        if account != appDelegate.account {
-            self.metadatas = []
-            account = appDelegate.account
-            DispatchQueue.main.async { self.collectionView?.reloadData() }
-        }
-
         DispatchQueue.global().async {
-            self.queryDB(isForced: true)
+            NCImageCache.shared.getMediaMetadatas(account: self.appDelegate.account, predicate: self.getPredicate())
             DispatchQueue.main.sync {
                 self.reloadDataThenPerform {
                     self.updateMediaControlVisibility()
                     self.mediaCommandTitle()
-                    completion(self.metadatas)
+                    completion(NCImageCache.shared.metadatas)
                 }
             }
         }
@@ -499,8 +481,8 @@ extension NCMedia {
 
     func updateMediaControlVisibility() {
 
-        if self.metadatas.isEmpty {
-            if !self.filterClassTypeImage && !self.filterClassTypeVideo {
+        if NCImageCache.shared.metadatas.isEmpty {
+            if !self.showOnlyImages && !self.showOnlyVideos {
                 self.mediaCommandView?.toggleEmptyView(isEmpty: true)
                 self.mediaCommandView?.isHidden = false
             } else {
@@ -528,10 +510,9 @@ extension NCMedia {
         }
 
         var lessDate = Date()
-        if predicateDefault != nil {
-            if let metadata = NCManageDatabase.shared.getMetadata(predicate: predicateDefault!, sorted: "date", ascending: true) {
-                lessDate = metadata.date as Date
-            }
+        let predicate = getPredicate()
+        if let metadata = NCManageDatabase.shared.getMetadata(predicate: predicate, sorted: "date", ascending: true) {
+            lessDate = metadata.date as Date
         }
 
         var greaterDate: Date
@@ -543,7 +524,7 @@ extension NCMedia {
 
         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: CCUtility.getShowHiddenFiles(), options: options) { account, files, _, error in
+        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 {
@@ -555,7 +536,7 @@ extension NCMedia {
                 if !files.isEmpty {
                     NCManageDatabase.shared.convertFilesToMetadatas(files, useMetadataFolder: false) { _, _, metadatas in
                         let predicateDate = NSPredicate(format: "date > %@ AND date < %@", greaterDate as NSDate, lessDate as NSDate)
-                        let predicateResult = NSCompoundPredicate(andPredicateWithSubpredicates: [predicateDate, self.predicateDefault!])
+                        let predicateResult = NSCompoundPredicate(andPredicateWithSubpredicates: [predicateDate, self.getPredicate(true)])
                         let metadatasResult = NCManageDatabase.shared.getMetadatas(predicate: predicateResult)
                         let metadatasChanged = NCManageDatabase.shared.updateMetadatas(metadatas, metadatasResult: metadatasResult, addCompareLivePhoto: false)
                         if metadatasChanged.metadatasUpdate.isEmpty {
@@ -609,7 +590,7 @@ extension NCMedia {
         if let visibleCells = self.collectionView?.indexPathsForVisibleItems.sorted(by: { $0.row < $1.row }).compactMap({ self.collectionView?.cellForItem(at: $0) }) {
             if let cell = visibleCells.first as? NCGridMediaCell {
                 if cell.date != nil {
-                    if cell.date != self.metadatas.first?.date as Date? {
+                    if cell.date != NCImageCache.shared.metadatas.first?.date as Date? {
                         lessDate = Calendar.current.date(byAdding: .second, value: 1, to: cell.date!)!
                         limit = 0
                     }
@@ -626,7 +607,7 @@ extension NCMedia {
 
             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: CCUtility.getShowHiddenFiles(), options: options) { account, files, _, error in
+            NextcloudKit.shared.searchMedia(path: self.mediaPath, lessDate: lessDate, greaterDate: greaterDate, elementDate: "d:getlastmodified/", limit: limit, showHiddenFiles: NCKeychain().showHiddenFiles, options: options) { account, files, _, error in
 
                 self.newInProgress = false
                 DispatchQueue.main.async {
@@ -636,14 +617,14 @@ extension NCMedia {
                 if error == .success, account == self.appDelegate.account, !files.isEmpty {
                     NCManageDatabase.shared.convertFilesToMetadatas(files, useMetadataFolder: false) { _, _, metadatas in
                         let predicate = NSPredicate(format: "date > %@ AND date < %@", greaterDate as NSDate, lessDate as NSDate)
-                        let predicateResult = NSCompoundPredicate(andPredicateWithSubpredicates: [predicate, self.predicate!])
+                        let predicateResult = NSCompoundPredicate(andPredicateWithSubpredicates: [predicate, self.getPredicate(true)])
                         let metadatasResult = NCManageDatabase.shared.getMetadatas(predicate: predicateResult)
                         let updateMetadatas = NCManageDatabase.shared.updateMetadatas(metadatas, metadatasResult: metadatasResult, addCompareLivePhoto: false)
                         if !updateMetadatas.metadatasUpdate.isEmpty || !updateMetadatas.metadatasDelete.isEmpty {
                             self.reloadDataSourceWithCompletion { _ in }
                         }
                     }
-                } else if error == .success, files.isEmpty, self.metadatas.isEmpty {
+                } else if error == .success, files.isEmpty, NCImageCache.shared.metadatas.isEmpty {
                     self.searchOldMedia()
                 } else if error != .success {
                     NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] Media search new media error code \(error.errorCode) " + error.errorDescription)
@@ -842,3 +823,62 @@ class NCGridMediaLayout: UICollectionViewFlowLayout {
         return proposedContentOffset
     }
 }
+
+// MARK: -
+
+class NCMediaDownloadThumbnaill: ConcurrentOperation {
+
+    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)
+    }
+
+    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 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, image: .actual(image))
+            }
+            self.finish()
+        }
+    }
+}

+ 10 - 14
iOSClient/Menu/AppDelegate+Menu.swift

@@ -32,19 +32,15 @@ extension AppDelegate {
     func toggleMenu(viewController: UIViewController) {
 
         var actions: [NCMenuAction] = []
-
-        // swiftlint:disable force_cast
-        let appDelegate = UIApplication.shared.delegate as! AppDelegate
-        // swiftlint:enable force_cast
-
+        let appDelegate = (UIApplication.shared.delegate as? AppDelegate)!
         let directEditingCreators = NCManageDatabase.shared.getDirectEditingCreators(account: appDelegate.account)
-        let isDirectoryE2EE = NCUtility.shared.isDirectoryE2EE(serverUrl: appDelegate.activeServerUrl, userBase: appDelegate)
+        let isDirectoryE2EE = NCUtilityFileSystem().isDirectoryE2EE(serverUrl: appDelegate.activeServerUrl, userBase: appDelegate)
         let directory = NCManageDatabase.shared.getTableDirectory(predicate: NSPredicate(format: "account == %@ AND serverUrl == %@", appDelegate.account, appDelegate.activeServerUrl))
 
         actions.append(
             NCMenuAction(
                 title: NSLocalizedString("_upload_photos_videos_", comment: ""), icon: UIImage(named: "file_photo")!.image(color: UIColor.systemGray, size: 50), action: { _ in
-                    NCAskAuthorization.shared.askAuthorizationPhotoLibrary(viewController: viewController) { hasPermission in
+                    NCAskAuthorization().askAuthorizationPhotoLibrary(viewController: viewController) { hasPermission in
                         if hasPermission {NCPhotosPickerViewController(viewController: viewController, maxSelectedAssets: 0, singleSelectedMode: false)
                         }
                     }
@@ -86,7 +82,7 @@ extension AppDelegate {
 
         actions.append(
             NCMenuAction(
-                title: NSLocalizedString("_scans_document_", comment: ""), icon: NCUtility.shared.loadImage(named: "doc.text.viewfinder"), action: { _ in
+                title: NSLocalizedString("_scans_document_", comment: ""), icon: NCUtility().loadImage(named: "doc.text.viewfinder"), action: { _ in
                     if let viewController = appDelegate.window?.rootViewController {
                         NCDocumentCamera.shared.openScannerDocument(viewController: viewController)
                     }
@@ -97,9 +93,9 @@ extension AppDelegate {
         actions.append(
             NCMenuAction(
                 title: NSLocalizedString("_create_voice_memo_", comment: ""), icon: UIImage(named: "microphone")!.image(color: UIColor.systemGray, size: 50), action: { _ in
-                    NCAskAuthorization.shared.askAuthorizationAudioRecord(viewController: viewController) { hasPermission in
+                    NCAskAuthorization().askAuthorizationAudioRecord(viewController: viewController) { hasPermission in
                         if hasPermission {
-                            let fileName = CCUtility.createFileNameDate(NSLocalizedString("_voice_memo_filename_", comment: ""), extension: "m4a")!
+                            let fileName = NCUtilityFileSystem().createFileNameDate(NSLocalizedString("_voice_memo_filename_", comment: ""), ext: "m4a")
                             if let viewController = UIStoryboard(name: "NCAudioRecorderViewController", bundle: nil).instantiateInitialViewController() as? NCAudioRecorderViewController {
 
                                 viewController.delegate = self
@@ -115,7 +111,7 @@ extension AppDelegate {
             )
         )
 
-        if CCUtility.isEnd(toEndEnabled: appDelegate.account) {
+        if NCKeychain().isEndToEndEnabled(account: appDelegate.account) {
             actions.append(.seperator(order: 0))
         }
 
@@ -132,7 +128,7 @@ extension AppDelegate {
         )
 
         // Folder encrypted
-        if !isDirectoryE2EE && CCUtility.isEnd(toEndEnabled: appDelegate.account) {
+        if !isDirectoryE2EE && NCKeychain().isEndToEndEnabled(account: appDelegate.account) {
             actions.append(
                 NCMenuAction(title: NSLocalizedString("_create_folder_e2ee_", comment: ""),
                              icon: UIImage(named: "folderEncrypted")!.image(color: NCBrandColor.shared.brandElement, size: 50),
@@ -144,7 +140,7 @@ extension AppDelegate {
             )
         }
 
-        if CCUtility.isEnd(toEndEnabled: appDelegate.account) {
+        if NCKeychain().isEndToEndEnabled(account: appDelegate.account) {
             actions.append(.seperator(order: 0))
         }
 
@@ -237,7 +233,7 @@ extension AppDelegate {
             )
         }
 
-        if !NCGlobal.shared.capabilityRichdocumentsMimetypes.isEmpty {
+        if NCGlobal.shared.capabilityRichdocumentsEnabled {
             if NextcloudKit.shared.isNetworkReachable() && !isDirectoryE2EE {
                 actions.append(
                     NCMenuAction(

+ 22 - 22
iOSClient/Menu/NCCollectionViewCommon+Menu.swift

@@ -46,8 +46,8 @@ extension NCCollectionViewCommon {
             isOffline = localFile.offline
         }
 
-        let editors = NCUtility.shared.isDirectEditing(account: metadata.account, contentType: metadata.contentType)
-        let isRichDocument = NCUtility.shared.isRichDocument(metadata)
+        let editors = utility.isDirectEditing(account: metadata.account, contentType: metadata.contentType)
+        let isRichDocument = utility.isRichDocument(metadata)
         let applicationHandle = NCApplicationHandle()
 
         var iconHeader: UIImage!
@@ -56,9 +56,9 @@ extension NCCollectionViewCommon {
             iconHeader = imageIcon!
         } else {
             if metadata.directory {
-                iconHeader = NCBrandColor.cacheImages.folder
+                iconHeader = NCImageCache.images.folder
             } else {
-                iconHeader = NCBrandColor.cacheImages.file
+                iconHeader = NCImageCache.images.file
             }
         }
 
@@ -78,7 +78,7 @@ extension NCCollectionViewCommon {
             actions.append(
                 NCMenuAction(
                     title: NSLocalizedString("_details_", comment: ""),
-                    icon: NCUtility.shared.loadImage(named: "info"),
+                    icon: utility.loadImage(named: "info"),
                     order: 10,
                     action: { _ in
                         NCActionCenter.shared.openShare(viewController: self, metadata: metadata, page: .activity)
@@ -89,7 +89,7 @@ extension NCCollectionViewCommon {
 
         if metadata.lock {
             var lockOwnerName = metadata.lockOwnerDisplayName.isEmpty ? metadata.lockOwner : metadata.lockOwnerDisplayName
-            var lockIcon = NCUtility.shared.loadUserImage(for: metadata.lockOwner, displayName: lockOwnerName, userBaseUrl: metadata)
+            var lockIcon = utility.loadUserImage(for: metadata.lockOwner, displayName: lockOwnerName, userBaseUrl: metadata)
             if metadata.lockOwnerType != 0 {
                 lockOwnerName += " app"
                 if !metadata.lockOwnerEditor.isEmpty, let appIcon = UIImage(named: metadata.lockOwnerEditor) {
@@ -127,7 +127,7 @@ extension NCCollectionViewCommon {
             actions.append(
                 NCMenuAction(
                     title: NSLocalizedString("_view_in_folder_", comment: ""),
-                    icon: NCUtility.shared.loadImage(named: "questionmark.folder"),
+                    icon: utility.loadImage(named: "questionmark.folder"),
                     order: 21,
                     action: { _ in
                         NCActionCenter.shared.openFileViewInFolder(serverUrl: metadata.serverUrl, fileNameBlink: metadata.fileName, fileNameOpen: nil)
@@ -150,13 +150,13 @@ extension NCCollectionViewCommon {
             actions.append(
                 NCMenuAction(
                     title: NSLocalizedString("_e2e_set_folder_encrypted_", comment: ""),
-                    icon: NCUtility.shared.loadImage(named: "lock"),
+                    icon: utility.loadImage(named: "lock"),
                     order: 30,
                     action: { _ in
                         Task {
                             let error = await NCNetworkingE2EEMarkFolder().markFolderE2ee(account: metadata.account, fileName: metadata.fileName, serverUrl: metadata.serverUrl, userId: metadata.userId)
                             if error != .success {
-                                NCContentPresenter.shared.showError(error: error)
+                                NCContentPresenter().showError(error: error)
                             }
                         }
                     }
@@ -171,7 +171,7 @@ extension NCCollectionViewCommon {
             actions.append(
                 NCMenuAction(
                     title: NSLocalizedString("_e2e_remove_folder_encrypted_", comment: ""),
-                    icon: NCUtility.shared.loadImage(named: "lock"),
+                    icon: utility.loadImage(named: "lock"),
                     order: 30,
                     action: { _ in
                         NextcloudKit.shared.markE2EEFolder(fileId: metadata.fileId, delete: true) { _, error in
@@ -182,7 +182,7 @@ extension NCCollectionViewCommon {
 
                                 NotificationCenter.default.postOnMainThread(name: NCGlobal.shared.notificationCenterChangeStatusFolderE2EE, userInfo: ["serverUrl": metadata.serverUrl])
                             } else {
-                                NCContentPresenter.shared.messageNotification(NSLocalizedString("_e2e_error_", comment: ""), error: error, delay: NCGlobal.shared.dismissAfterSecond, type: .error)
+                                NCContentPresenter().messageNotification(NSLocalizedString("_e2e_error_", comment: ""), error: error, delay: NCGlobal.shared.dismissAfterSecond, type: .error)
                             }
                         }
                     }
@@ -200,12 +200,12 @@ extension NCCollectionViewCommon {
             actions.append(
                 NCMenuAction(
                     title: metadata.favorite ? NSLocalizedString("_remove_favorites_", comment: "") : NSLocalizedString("_add_favorites_", comment: ""),
-                    icon: NCUtility.shared.loadImage(named: "star.fill", color: NCBrandColor.shared.yellowFavorite),
+                    icon: utility.loadImage(named: "star.fill", color: NCBrandColor.shared.yellowFavorite),
                     order: 50,
                     action: { _ in
                         NCNetworking.shared.favoriteMetadata(metadata) { error in
                             if error != .success {
-                                NCContentPresenter.shared.showError(error: error)
+                                NCContentPresenter().showError(error: error)
                             }
                         }
                     }
@@ -234,11 +234,11 @@ extension NCCollectionViewCommon {
             if editors.contains(NCGlobal.shared.editorOnlyoffice) {
                 editor = NCGlobal.shared.editorOnlyoffice
                 title = NSLocalizedString("_open_in_onlyoffice_", comment: "")
-                icon = NCUtility.shared.loadImage(named: "onlyoffice")
+                icon = utility.loadImage(named: "onlyoffice")
             } else if isRichDocument {
                 editor = NCGlobal.shared.editorCollabora
                 title = NSLocalizedString("_open_in_collabora_", comment: "")
-                icon = NCUtility.shared.loadImage(named: "collabora")
+                icon = utility.loadImage(named: "collabora")
             }
 
             if !editor.isEmpty {
@@ -248,7 +248,7 @@ extension NCCollectionViewCommon {
                         icon: icon!,
                         order: 70,
                         action: { _ in
-                            NCViewer.shared.view(viewController: self, metadata: metadata, metadatas: [metadata], imageIcon: imageIcon, editor: editor, isRichDocument: isRichDocument)
+                            NCViewer().view(viewController: self, metadata: metadata, metadatas: [metadata], imageIcon: imageIcon, editor: editor, isRichDocument: isRichDocument)
                         }
                     )
                 )
@@ -283,10 +283,10 @@ extension NCCollectionViewCommon {
             actions.append(
                 NCMenuAction(
                     title: NSLocalizedString("_save_as_scan_", comment: ""),
-                    icon: NCUtility.shared.loadImage(named: "viewfinder.circle"),
+                    icon: utility.loadImage(named: "viewfinder.circle"),
                     order: 110,
                     action: { _ in
-                        if CCUtility.fileProviderStorageExists(metadata) {
+                        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])
                         } else {
                             NCNetworking.shared.download(metadata: metadata, selector: NCGlobal.shared.selectorSaveAsScan) { _, _ in }
@@ -303,7 +303,7 @@ extension NCCollectionViewCommon {
             actions.append(
                 NCMenuAction(
                     title: NSLocalizedString("_rename_", comment: ""),
-                    icon: NCUtility.shared.loadImage(named: "pencil"),
+                    icon: utility.loadImage(named: "pencil"),
                     order: 120,
                     action: { _ in
 
@@ -343,10 +343,10 @@ extension NCCollectionViewCommon {
             actions.append(
                 NCMenuAction(
                     title: NSLocalizedString("_modify_", comment: ""),
-                    icon: NCUtility.shared.loadImage(named: "pencil.tip.crop.circle"),
+                    icon: utility.loadImage(named: "pencil.tip.crop.circle"),
                     order: 150,
                     action: { _ in
-                        if CCUtility.fileProviderStorageExists(metadata) {
+                        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])
                         } else {
                             NCNetworking.shared.download(metadata: metadata, selector: NCGlobal.shared.selectorLoadFileQuickLook) { _, _ in }
@@ -363,7 +363,7 @@ extension NCCollectionViewCommon {
             actions.append(
                 NCMenuAction(
                     title: NSLocalizedString("_change_color_", comment: ""),
-                    icon: NCUtility.shared.loadImage(named: "palette"),
+                    icon: utility.loadImage(named: "palette"),
                     order: 160,
                     action: { _ in
                         if let picker = UIStoryboard(name: "NCColorPicker", bundle: nil).instantiateInitialViewController() as? NCColorPicker {

+ 13 - 8
iOSClient/Menu/NCContextMenu.swift

@@ -28,6 +28,9 @@ import JGProgressHUD
 
 class NCContextMenu: NSObject {
 
+    let utilityFileSystem = NCUtilityFileSystem()
+    let utility = NCUtility()
+
     func viewMenu(ocId: String, indexPath: IndexPath, viewController: UIViewController, image: UIImage?) -> UIMenu {
         guard let metadata = NCManageDatabase.shared.getMetadataFromOcId(ocId) else { return UIMenu() }
 
@@ -60,17 +63,17 @@ class NCContextMenu: NSObject {
         let favorite = UIAction(title: metadata.favorite ?
                                 NSLocalizedString("_remove_favorites_", comment: "") :
                                 NSLocalizedString("_add_favorites_", comment: ""),
-                                image: NCUtility.shared.loadImage(named: "star.fill", color: NCBrandColor.shared.yellowFavorite)) { _ in
+                                image: utility.loadImage(named: "star.fill", color: NCBrandColor.shared.yellowFavorite)) { _ in
             NCNetworking.shared.favoriteMetadata(metadata) { error in
                 if error != .success {
-                    NCContentPresenter.shared.showError(error: error)
+                    NCContentPresenter().showError(error: error)
                 }
             }
         }
 
         let openIn = UIAction(title: NSLocalizedString("_open_in_", comment: ""),
                               image: UIImage(systemName: "square.and.arrow.up") ) { _ in
-            if CCUtility.fileProviderStorageExists(metadata) {
+            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])
             } else {
                 hud.show(in: viewController.view)
@@ -98,9 +101,11 @@ class NCContextMenu: NSObject {
         let save = UIAction(title: titleSave,
                             image: UIImage(systemName: "square.and.arrow.down")) { _ in
             if let metadataMOV = metadataMOV {
-                NCOperationQueue.shared.saveLivePhoto(metadata: metadata, metadataMOV: metadataMOV)
+                if let appDelegate = (UIApplication.shared.delegate as? AppDelegate) {
+                    appDelegate.saveLivePhotoQueue.addOperation(NCOperationSaveLivePhoto(metadata: metadata, metadataMOV: metadataMOV))
+                }
             } else {
-                if CCUtility.fileProviderStorageExists(metadata) {
+                if self.utilityFileSystem.fileProviderStorageExists(metadata) {
                     NCActionCenter.shared.saveAlbum(metadata: metadata)
                 } else {
                     hud.show(in: viewController.view)
@@ -123,7 +128,7 @@ class NCContextMenu: NSObject {
 
         let modify = UIAction(title: NSLocalizedString("_modify_", comment: ""),
                               image: UIImage(systemName: "pencil.tip.crop.circle")) { _ in
-            if CCUtility.fileProviderStorageExists(metadata) {
+            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])
             } else {
                 hud.show(in: viewController.view)
@@ -164,7 +169,7 @@ class NCContextMenu: NSObject {
                     if error == .success {
                         ocId.append(metadata.ocId)
                     } else {
-                        NCContentPresenter.shared.showError(error: error)
+                        NCContentPresenter().showError(error: error)
                     }
                     NotificationCenter.default.postOnMainThread(name: NCGlobal.shared.notificationCenterDeleteFile, userInfo: ["ocId": ocId, "indexPath": [indexPath], "onlyLocalCache": false, "error": error, "hud": hud])
                 }
@@ -181,7 +186,7 @@ class NCContextMenu: NSObject {
                 if error == .success {
                     ocId.append(metadata.ocId)
                 } else {
-                    NCContentPresenter.shared.showError(error: error)
+                    NCContentPresenter().showError(error: error)
                 }
                 NotificationCenter.default.postOnMainThread(name: NCGlobal.shared.notificationCenterDeleteFile, userInfo: ["ocId": ocId, "indexPath": [indexPath], "onlyLocalCache": true, "error": error])
             }

+ 3 - 3
iOSClient/Menu/NCLoginWeb+Menu.swift

@@ -31,13 +31,13 @@ extension NCLoginWeb {
         var actions = [NCMenuAction]()
 
         let accounts = NCManageDatabase.shared.getAllAccount()
-        var avatar = NCUtility.shared.loadImage(named: "person.crop.circle")
+        var avatar = utility.loadImage(named: "person.crop.circle")
 
         for account in accounts {
 
             let title = account.user + " " + (URL(string: account.urlBase)?.host ?? "")
 
-            avatar = NCUtility.shared.loadUserImage(
+            avatar = utility.loadUserImage(
                 for: account.user,
                    displayName: account.displayName,
                    userBaseUrl: account)
@@ -64,7 +64,7 @@ extension NCLoginWeb {
         actions.append(
             NCMenuAction(
                 title: NSLocalizedString("_delete_active_account_", comment: ""),
-                icon: NCUtility.shared.loadImage(named: "trash", color: UIColor.systemGray),
+                icon: utility.loadImage(named: "trash", color: UIColor.systemGray),
                 onTitle: NSLocalizedString("_delete_active_account_", comment: ""),
                 onIcon: avatar,
                 selected: false,

+ 44 - 26
iOSClient/Menu/NCMedia+Menu.swift

@@ -40,11 +40,11 @@ extension NCMedia {
         defer { presentMenu(with: actions) }
 
         if !isEditMode {
-            if !metadatas.isEmpty {
+            if !NCImageCache.shared.metadatas.isEmpty {
                 actions.append(
                     NCMenuAction(
                         title: NSLocalizedString("_select_", comment: ""),
-                        icon: NCUtility.shared.loadImage(named: "checkmark.circle.fill"),
+                        icon: utility.loadImage(named: "checkmark.circle.fill"),
                         action: { _ in
                             self.isEditMode = true
                         }
@@ -52,15 +52,17 @@ extension NCMedia {
                 )
             }
 
+            actions.append(.seperator(order: 0))
+
             actions.append(
                 NCMenuAction(
-                    title: NSLocalizedString("_media_viewimage_hide_", comment: ""),
-                    icon: NCUtility.shared.loadImage(named: "photo"),
-                    selected: filterClassTypeImage,
+                    title: NSLocalizedString("_media_viewimage_show_", comment: ""),
+                    icon: utility.loadImage(named: "photo"),
+                    selected: showOnlyImages,
                     on: true,
                     action: { _ in
-                        self.filterClassTypeImage = !self.filterClassTypeImage
-                        self.filterClassTypeVideo = false
+                        self.showOnlyImages = true
+                        self.showOnlyVideos = false
                         self.reloadDataSourceWithCompletion { _ in }
                     }
                 )
@@ -68,22 +70,38 @@ extension NCMedia {
 
             actions.append(
                 NCMenuAction(
-                    title: NSLocalizedString("_media_viewvideo_hide_", comment: ""),
-                    icon: NCUtility.shared.loadImage(named: "video"),
-                    selected: filterClassTypeVideo,
+                    title: NSLocalizedString("_media_viewvideo_show_", comment: ""),
+                    icon: utility.loadImage(named: "video"),
+                    selected: showOnlyVideos,
                     on: true,
                     action: { _ in
-                        self.filterClassTypeVideo = !self.filterClassTypeVideo
-                        self.filterClassTypeImage = false
+                        self.showOnlyImages = false
+                        self.showOnlyVideos = true
                         self.reloadDataSourceWithCompletion { _ in }
                     }
                 )
             )
 
+            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.reloadDataSourceWithCompletion { _ in }
+                    }
+                )
+            )
+
+            actions.append(.seperator(order: 0))
+
             actions.append(
                 NCMenuAction(
                     title: NSLocalizedString("_select_media_folder_", comment: ""),
-                    icon: NCUtility.shared.loadImage(named: "folder"),
+                    icon: utility.loadImage(named: "folder"),
                     action: { _ in
                         if let navigationController = UIStoryboard(name: "NCSelect", bundle: nil).instantiateInitialViewController() as? UINavigationController,
                            let viewController = navigationController.topViewController as? NCSelect {
@@ -104,7 +122,7 @@ extension NCMedia {
             actions.append(
                 NCMenuAction(
                     title: NSLocalizedString("_play_from_files_", comment: ""),
-                    icon: NCUtility.shared.loadImage(named: "play.circle"),
+                    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)
@@ -116,7 +134,7 @@ extension NCMedia {
             actions.append(
                 NCMenuAction(
                     title: NSLocalizedString("_play_from_url_", comment: ""),
-                    icon: NCUtility.shared.loadImage(named: "network"),
+                    icon: utility.loadImage(named: "network"),
                     action: { _ in
 
                         let alert = UIAlertController(title: NSLocalizedString("_valid_video_url_", comment: ""), message: nil, preferredStyle: .alert)
@@ -131,7 +149,7 @@ extension NCMedia {
                             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.shared.view(viewController: self, metadata: metadata, metadatas: [metadata], imageIcon: nil)
+                            NCViewer().view(viewController: self, metadata: metadata, metadatas: [metadata], imageIcon: nil)
                         }))
 
                         self.present(alert, animated: true)
@@ -145,11 +163,11 @@ extension NCMedia {
             actions.append(
                 NCMenuAction(
                     title: NSLocalizedString("_media_by_modified_date_", comment: ""),
-                    icon: NCUtility.shared.loadImage(named: "circle.grid.cross.up.fill"),
-                    selected: CCUtility.getMediaSortDate() == "date",
+                    icon: utility.loadImage(named: "circle.grid.cross.up.fill"),
+                    selected: NCKeychain().mediaSortDate == "date",
                     on: true,
                     action: { _ in
-                        CCUtility.setMediaSortDate("date")
+                        NCKeychain().mediaSortDate = "date"
                         self.reloadDataSourceWithCompletion { _ in }
                     }
                 )
@@ -158,11 +176,11 @@ extension NCMedia {
             actions.append(
                 NCMenuAction(
                     title: NSLocalizedString("_media_by_created_date_", comment: ""),
-                    icon: NCUtility.shared.loadImage(named: "circle.grid.cross.down.fill"),
-                    selected: CCUtility.getMediaSortDate() == "creationDate",
+                    icon: utility.loadImage(named: "circle.grid.cross.down.fill"),
+                    selected: NCKeychain().mediaSortDate == "creationDate",
                     on: true,
                     action: { _ in
-                        CCUtility.setMediaSortDate("creationDate")
+                        NCKeychain().mediaSortDate = "creationDate"
                         self.reloadDataSourceWithCompletion { _ in }
                     }
                 )
@@ -171,11 +189,11 @@ extension NCMedia {
             actions.append(
                 NCMenuAction(
                     title: NSLocalizedString("_media_by_upload_date_", comment: ""),
-                    icon: NCUtility.shared.loadImage(named: "circle.grid.cross.right.fill"),
-                    selected: CCUtility.getMediaSortDate() == "uploadDate",
+                    icon: utility.loadImage(named: "circle.grid.cross.right.fill"),
+                    selected: NCKeychain().mediaSortDate == "uploadDate",
                     on: true,
                     action: { _ in
-                        CCUtility.setMediaSortDate("uploadDate")
+                        NCKeychain().mediaSortDate = "uploadDate"
                         self.reloadDataSourceWithCompletion { _ in }
                     }
                 )
@@ -189,7 +207,7 @@ extension NCMedia {
             actions.append(
                 NCMenuAction(
                     title: NSLocalizedString("_cancel_", comment: ""),
-                    icon: NCUtility.shared.loadImage(named: "xmark"),
+                    icon: utility.loadImage(named: "xmark"),
                     action: { _ in self.tapSelect() }
                 )
             )

+ 20 - 16
iOSClient/Menu/NCMenuAction.swift

@@ -78,7 +78,7 @@ extension NCMenuAction {
     static func selectAllAction(action: @escaping () -> Void) -> NCMenuAction {
         NCMenuAction(
             title: NSLocalizedString("_select_all_", comment: ""),
-            icon: NCUtility.shared.loadImage(named: "checkmark.circle.fill"),
+            icon: NCUtility().loadImage(named: "checkmark.circle.fill"),
             action: { _ in action() }
         )
     }
@@ -87,7 +87,7 @@ extension NCMenuAction {
     static func cancelAction(action: @escaping () -> Void) -> NCMenuAction {
         NCMenuAction(
             title: NSLocalizedString("_cancel_", comment: ""),
-            icon: NCUtility.shared.loadImage(named: "xmark"),
+            icon: NCUtility().loadImage(named: "xmark"),
             action: { _ in action() }
         )
     }
@@ -96,7 +96,7 @@ extension NCMenuAction {
     static func copyAction(selectOcId: [String], order: Int = 0, completion: (() -> Void)? = nil) -> NCMenuAction {
         NCMenuAction(
             title: NSLocalizedString("_copy_file_", comment: ""),
-            icon: NCUtility.shared.loadImage(named: "doc.on.doc"),
+            icon: NCUtility().loadImage(named: "doc.on.doc"),
             order: order,
             action: { _ in
                 NCActionCenter.shared.copyPasteboard(pasteboardOcIds: selectOcId)
@@ -137,7 +137,7 @@ extension NCMenuAction {
 
         return NCMenuAction(
             title: titleDelete,
-            icon: NCUtility.shared.loadImage(named: "trash"),
+            icon: NCUtility().loadImage(named: "trash"),
             order: order,
             action: { _ in
                 let alertController = UIAlertController(
@@ -180,7 +180,7 @@ extension NCMenuAction {
                                 }
                             }
                             if error != .success {
-                                NCContentPresenter.shared.showError(error: error)
+                                NCContentPresenter().showError(error: error)
                             }
                             NotificationCenter.default.postOnMainThread(name: NCGlobal.shared.notificationCenterDeleteFile, userInfo: ["ocId": ocId, "indexPath": indexPath, "onlyLocalCache": true, "error": error])
                         }
@@ -197,7 +197,7 @@ extension NCMenuAction {
     static func openInAction(selectedMetadatas: [tableMetadata], viewController: UIViewController, order: Int = 0, completion: (() -> Void)? = nil) -> NCMenuAction {
         NCMenuAction(
             title: NSLocalizedString("_open_in_", comment: ""),
-            icon: NCUtility.shared.loadImage(named: "square.and.arrow.up"),
+            icon: NCUtility().loadImage(named: "square.and.arrow.up"),
             order: order,
             action: { _ in
                 NCActionCenter.shared.openActivityViewController(selectedMetadata: selectedMetadatas)
@@ -209,10 +209,10 @@ extension NCMenuAction {
     /// Save selected files to user's photo library
     static func saveMediaAction(selectedMediaMetadatas: [tableMetadata], order: Int = 0, completion: (() -> Void)? = nil) -> NCMenuAction {
         var title: String = NSLocalizedString("_save_selected_files_", comment: "")
-        var icon = NCUtility.shared.loadImage(named: "square.and.arrow.down")
+        var icon = NCUtility().loadImage(named: "square.and.arrow.down")
         if selectedMediaMetadatas.allSatisfy({ NCManageDatabase.shared.getMetadataLivePhoto(metadata: $0) != nil }) {
             title = NSLocalizedString("_livephoto_save_", comment: "")
-            icon = NCUtility.shared.loadImage(named: "livephoto")
+            icon = NCUtility().loadImage(named: "livephoto")
         }
 
         return NCMenuAction(
@@ -222,12 +222,16 @@ extension NCMenuAction {
             action: { _ in
                 for metadata in selectedMediaMetadatas {
                     if let metadataMOV = NCManageDatabase.shared.getMetadataLivePhoto(metadata: metadata) {
-                        NCOperationQueue.shared.saveLivePhoto(metadata: metadata, metadataMOV: metadataMOV)
+                        if let appDelegate = (UIApplication.shared.delegate as? AppDelegate) {
+                            appDelegate.saveLivePhotoQueue.addOperation(NCOperationSaveLivePhoto(metadata: metadata, metadataMOV: metadataMOV))
+                        }
                     } else {
-                        if CCUtility.fileProviderStorageExists(metadata) {
+                        if NCUtilityFileSystem().fileProviderStorageExists(metadata) {
                             NCActionCenter.shared.saveAlbum(metadata: metadata)
                         } else {
-                            NCOperationQueue.shared.download(metadata: metadata, selector: NCGlobal.shared.selectorSaveAlbum)
+                            if let appDelegate = (UIApplication.shared.delegate as? AppDelegate), appDelegate.downloadQueue.operations.filter({ ($0 as? NCOperationDownload)?.metadata.ocId == metadata.ocId }).isEmpty {
+                                appDelegate.downloadQueue.addOperation(NCOperationDownload(metadata: metadata, selector: NCGlobal.shared.selectorSaveAlbum))
+                            }
                         }
                     }
                 }
@@ -240,7 +244,7 @@ extension NCMenuAction {
     static func setAvailableOfflineAction(selectedMetadatas: [tableMetadata], isAnyOffline: Bool, viewController: UIViewController, order: Int = 0, completion: (() -> Void)? = nil) -> NCMenuAction {
         NCMenuAction(
             title: isAnyOffline ? NSLocalizedString("_remove_available_offline_", comment: "") : NSLocalizedString("_set_available_offline_", comment: ""),
-            icon: NCUtility.shared.loadImage(named: "tray.and.arrow.down"),
+            icon: NCUtility().loadImage(named: "tray.and.arrow.down"),
             order: order,
             action: { _ in
                 if !isAnyOffline, selectedMetadatas.count > 3 {
@@ -266,7 +270,7 @@ extension NCMenuAction {
     static func moveOrCopyAction(selectedMetadatas: [tableMetadata], indexPath: [IndexPath], order: Int = 0, completion: (() -> Void)? = nil) -> NCMenuAction {
         NCMenuAction(
             title: NSLocalizedString("_move_or_copy_selected_files_", comment: ""),
-            icon: NCUtility.shared.loadImage(named: "arrow.up.right.square"),
+            icon: NCUtility().loadImage(named: "arrow.up.right.square"),
             order: order,
             action: { _ in
                 NCActionCenter.shared.openSelectView(items: selectedMetadatas, indexPath: indexPath)
@@ -279,10 +283,10 @@ extension NCMenuAction {
     static func printAction(metadata: tableMetadata, order: Int = 0) -> NCMenuAction {
         NCMenuAction(
             title: NSLocalizedString("_print_", comment: ""),
-            icon: NCUtility.shared.loadImage(named: "printer"),
+            icon: NCUtility().loadImage(named: "printer"),
             order: order,
             action: { _ in
-                if CCUtility.fileProviderStorageExists(metadata) {
+                if NCUtilityFileSystem().fileProviderStorageExists(metadata) {
                     NotificationCenter.default.postOnMainThread(name: NCGlobal.shared.notificationCenterDownloadedFile, userInfo: ["ocId": metadata.ocId, "selector": NCGlobal.shared.selectorPrint, "error": NKError(), "account": metadata.account])
                 } else {
                     NCNetworking.shared.download(metadata: metadata, selector: NCGlobal.shared.selectorPrint) { _, _ in }
@@ -302,7 +306,7 @@ extension NCMenuAction {
         let imageName = !shouldLock ? "lock_open" : "lock"
         return NCMenuAction(
             title: NSLocalizedString(titleKey, comment: ""),
-            icon: NCUtility.shared.loadImage(named: imageName),
+            icon: NCUtility().loadImage(named: imageName),
             order: order,
             action: { _ in
                 for metadata in metadatas where metadata.lock != shouldLock {

+ 124 - 0
iOSClient/Menu/NCOperationSaveLivePhoto.swift

@@ -0,0 +1,124 @@
+//
+//  NCOperationSaveLivePhoto.swift
+//  Nextcloud
+//
+//  Created by Marino Faggiana on 19/10/23.
+//  Copyright © 2023 Marino Faggiana. All rights reserved.
+//
+//  Author Marino Faggiana <marino.faggiana@nextcloud.com>
+//
+//  This program is free software: you can redistribute it and/or modify
+//  it under the terms of the GNU General Public License as published by
+//  the Free Software Foundation, either version 3 of the License, or
+//  (at your option) any later version.
+//
+//  This program is distributed in the hope that it will be useful,
+//  but WITHOUT ANY WARRANTY; without even the implied warranty of
+//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+//  GNU General Public License for more details.
+//
+//  You should have received a copy of the GNU General Public License
+//  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+//
+
+import UIKit
+import Queuer
+import JGProgressHUD
+
+class NCOperationSaveLivePhoto: ConcurrentOperation {
+
+    var metadata: tableMetadata
+    var metadataMOV: tableMetadata
+    let hud = JGProgressHUD()
+    let appDelegate = UIApplication.shared.delegate as? AppDelegate
+    let utilityFileSystem = NCUtilityFileSystem()
+
+    init(metadata: tableMetadata, metadataMOV: tableMetadata) {
+        self.metadata = tableMetadata.init(value: metadata)
+        self.metadataMOV = tableMetadata.init(value: metadataMOV)
+    }
+
+    override func start() {
+        guard !isCancelled else { return self.finish() }
+
+        DispatchQueue.main.async {
+            self.hud.indicatorView = JGProgressHUDRingIndicatorView()
+            if let indicatorView = self.hud.indicatorView as? JGProgressHUDRingIndicatorView {
+                indicatorView.ringWidth = 1.5
+            }
+            self.hud.textLabel.text = NSLocalizedString("_download_image_", comment: "")
+            self.hud.detailTextLabel.text = self.metadata.fileName
+            self.hud.show(in: (self.appDelegate?.window?.rootViewController?.view)!)
+        }
+
+        NCNetworking.shared.download(metadata: metadata, selector: "", notificationCenterProgressTask: false, checkfileProviderStorageExists: true) { _ in
+        } progressHandler: { progress in
+            self.hud.progress = Float(progress.fractionCompleted)
+        } completion: { _, error in
+            guard error == .success else {
+                DispatchQueue.main.async {
+                    self.hud.indicatorView = JGProgressHUDErrorIndicatorView()
+                    self.hud.textLabel.text = NSLocalizedString("_livephoto_save_error_", comment: "")
+                    self.hud.dismiss()
+                }
+                return self.finish()
+            }
+            NCNetworking.shared.download(metadata: self.metadataMOV, selector: "", notificationCenterProgressTask: false, checkfileProviderStorageExists: true) { _ in
+                DispatchQueue.main.async {
+                    self.hud.textLabel.text = NSLocalizedString("_download_video_", comment: "")
+                    self.hud.detailTextLabel.text = self.metadataMOV.fileName
+                }
+            } progressHandler: { progress in
+                self.hud.progress = Float(progress.fractionCompleted)
+            } completion: { _, error in
+                guard error == .success else {
+                    DispatchQueue.main.async {
+                        self.hud.indicatorView = JGProgressHUDErrorIndicatorView()
+                        self.hud.textLabel.text = NSLocalizedString("_livephoto_save_error_", comment: "")
+                        self.hud.dismiss()
+                    }
+                    return self.finish()
+                }
+                self.saveLivePhotoToDisk(metadata: self.metadata, metadataMov: self.metadataMOV)
+            }
+        }
+    }
+
+    func saveLivePhotoToDisk(metadata: tableMetadata, metadataMov: tableMetadata) {
+
+        let fileNameImage = URL(fileURLWithPath: utilityFileSystem.getDirectoryProviderStorageOcId(metadata.ocId, fileNameView: metadata.fileNameView))
+        let fileNameMov = URL(fileURLWithPath: utilityFileSystem.getDirectoryProviderStorageOcId(metadataMov.ocId, fileNameView: metadataMov.fileNameView))
+
+        DispatchQueue.main.async {
+            self.hud.textLabel.text = NSLocalizedString("_livephoto_save_", comment: "")
+            self.hud.detailTextLabel.text = ""
+        }
+
+        NCLivePhoto.generate(from: fileNameImage, videoURL: fileNameMov, progress: { progress in
+            self.hud.progress = Float(progress)
+        }, completion: { _, resources in
+            if let resources {
+                NCLivePhoto.saveToLibrary(resources) { result in
+                    DispatchQueue.main.async {
+                        if !result {
+                            self.hud.indicatorView = JGProgressHUDErrorIndicatorView()
+                            self.hud.textLabel.text = NSLocalizedString("_livephoto_save_error_", comment: "")
+                        } else {
+                            self.hud.indicatorView = JGProgressHUDSuccessIndicatorView()
+                            self.hud.textLabel.text = NSLocalizedString("_success_", comment: "")
+                        }
+                        self.hud.dismiss()
+                    }
+                    return self.finish()
+                }
+            } else {
+                DispatchQueue.main.async {
+                    self.hud.indicatorView = JGProgressHUDErrorIndicatorView()
+                    self.hud.textLabel.text = NSLocalizedString("_livephoto_save_error_", comment: "")
+                    self.hud.dismiss()
+                }
+                return self.finish()
+            }
+        })
+    }
+}

+ 9 - 9
iOSClient/Menu/NCShare+Menu.swift

@@ -33,7 +33,7 @@ extension NCShare {
             actions.append(
                 NCMenuAction(
                     title: NSLocalizedString("_share_add_sharelink_", comment: ""),
-                    icon: NCUtility.shared.loadImage(named: "shareAdd"),
+                    icon: utility.loadImage(named: "shareAdd"),
                     action: { _ in
                         self.makeNewLinkShare()
                     }
@@ -44,7 +44,7 @@ extension NCShare {
         actions.append(
             NCMenuAction(
                 title: NSLocalizedString("_details_", comment: ""),
-                icon: NCUtility.shared.loadImage(named: "pencil"),
+                icon: utility.loadImage(named: "pencil"),
                 action: { _ in
                     guard
                         let advancePermission = UIStoryboard(name: "NCShare", bundle: nil).instantiateViewController(withIdentifier: "NCShareAdvancePermission") as? NCShareAdvancePermission,
@@ -61,18 +61,18 @@ extension NCShare {
         actions.append(
             NCMenuAction(
                 title: NSLocalizedString("_share_unshare_", comment: ""),
-                icon: NCUtility.shared.loadImage(named: "trash"),
+                icon: utility.loadImage(named: "trash"),
                 action: { _ in
                     Task {
-                        if share.shareType != NCShareCommon.shared.SHARE_TYPE_LINK, let metadata = self.metadata, metadata.e2eEncrypted && NCGlobal.shared.capabilityE2EEApiVersion == NCGlobal.shared.e2eeVersionV20 {
+                        if share.shareType != NCShareCommon().SHARE_TYPE_LINK, let metadata = self.metadata, metadata.e2eEncrypted && NCGlobal.shared.capabilityE2EEApiVersion == NCGlobal.shared.e2eeVersionV20 {
                             let serverUrl = metadata.serverUrl + "/" + metadata.fileName
-                            if NCNetworkingE2EE.shared.isInUpload(account: metadata.account, serverUrl: serverUrl) {
+                            if NCNetworkingE2EE().isInUpload(account: metadata.account, serverUrl: serverUrl) {
                                 let error = NKError(errorCode: NCGlobal.shared.errorE2EEUploadInProgress, errorDescription: NSLocalizedString("_e2e_in_upload_", comment: ""))
-                                return NCContentPresenter.shared.showInfo(error: error)
+                                return NCContentPresenter().showInfo(error: error)
                             }
                             let error = await NCNetworkingE2EE().uploadMetadata(account: metadata.account, serverUrl: serverUrl, userId: metadata.userId, addUserId: nil, removeUserId: share.shareWith)
                             if error != .success {
-                                return NCContentPresenter.shared.showError(error: error)
+                                return NCContentPresenter().showError(error: error)
                             }
                         }
                         self.networking?.unShare(idShare: share.idShare)
@@ -90,7 +90,7 @@ extension NCShare {
         actions.append(
             NCMenuAction(
                 title: NSLocalizedString("_share_read_only_", comment: ""),
-                icon: NCUtility.shared.loadImage(named: "eye"),
+                icon: utility.loadImage(named: "eye"),
                 selected: tableShare.permissions == (NCGlobal.shared.permissionReadShare + NCGlobal.shared.permissionShareShare) || tableShare.permissions == NCGlobal.shared.permissionReadShare,
                 on: false,
                 action: { _ in
@@ -104,7 +104,7 @@ extension NCShare {
         actions.append(
             NCMenuAction(
                 title: isDirectory ? NSLocalizedString("_share_allow_upload_", comment: "") : NSLocalizedString("_share_editing_", comment: ""),
-                icon: NCUtility.shared.loadImage(named: "pencil"),
+                icon: utility.loadImage(named: "pencil"),
                 selected: hasUploadPermission(tableShare: tableShare),
                 on: false,
                 action: { _ in

+ 8 - 8
iOSClient/Menu/NCTrash+Menu.swift

@@ -32,14 +32,14 @@ extension NCTrash {
         [
             NCMenuAction(
                 title: NSLocalizedString("_cancel_", comment: ""),
-                icon: NCUtility.shared.loadImage(named: "xmark"),
+                icon: utility.loadImage(named: "xmark"),
                 action: { _ in
                     self.tapSelect()
                 }
             ),
             NCMenuAction(
                 title: NSLocalizedString("_select_all_", comment: ""),
-                icon: NCUtility.shared.loadImage(named: "checkmark.circle.fill"),
+                icon: utility.loadImage(named: "checkmark.circle.fill"),
                 action: { _ in
                     self.selectOcId = self.datasource.map { $0.fileId }
                     self.collectionView.reloadData()
@@ -48,7 +48,7 @@ extension NCTrash {
             NCMenuAction.seperator(),
             NCMenuAction(
                 title: NSLocalizedString("_trash_restore_selected_", comment: ""),
-                icon: NCUtility.shared.loadImage(named: "restore"),
+                icon: utility.loadImage(named: "restore"),
                 action: { _ in
                     self.selectOcId.forEach(self.restoreItem)
                     self.tapSelect()
@@ -56,7 +56,7 @@ extension NCTrash {
             ),
             NCMenuAction(
                 title: NSLocalizedString("_trash_delete_selected_", comment: ""),
-                icon: NCUtility.shared.loadImage(named: "trash"),
+                icon: utility.loadImage(named: "trash"),
                 action: { _ in
                     let alert = UIAlertController(title: NSLocalizedString("_trash_delete_selected_", comment: ""), message: "", preferredStyle: .alert)
                     alert.addAction(UIAlertAction(title: NSLocalizedString("_delete_", comment: ""), style: .destructive, handler: { _ in
@@ -77,7 +77,7 @@ extension NCTrash {
         actions.append(
             NCMenuAction(
                 title: NSLocalizedString("_trash_restore_all_", comment: ""),
-                icon: NCUtility.shared.loadImage(named: "restore"),
+                icon: utility.loadImage(named: "restore"),
                 action: { _ in
                     self.datasource.forEach({ self.restoreItem(with: $0.fileId) })
                 }
@@ -87,7 +87,7 @@ extension NCTrash {
         actions.append(
             NCMenuAction(
                 title: NSLocalizedString("_trash_delete_all_", comment: ""),
-                icon: NCUtility.shared.loadImage(named: "trash"),
+                icon: utility.loadImage(named: "trash"),
                 action: { _ in
                     let alert = UIAlertController(title: NSLocalizedString("_trash_delete_all_description_", comment: ""), message: "", preferredStyle: .alert)
                     alert.addAction(UIAlertAction(title: NSLocalizedString("_trash_delete_all_", comment: ""), style: .destructive, handler: { _ in
@@ -121,7 +121,7 @@ extension NCTrash {
         var actions: [NCMenuAction] = []
 
         var iconHeader: UIImage!
-        if let icon = UIImage(contentsOfFile: CCUtility.getDirectoryProviderStorageIconOcId(tableTrash.fileId, etag: tableTrash.fileName)) {
+        if let icon = UIImage(contentsOfFile: utilityFileSystem.getDirectoryProviderStorageIconOcId(tableTrash.fileId, etag: tableTrash.fileName)) {
             iconHeader = icon
         } else {
             if tableTrash.directory {
@@ -152,7 +152,7 @@ extension NCTrash {
         actions.append(
             NCMenuAction(
                 title: NSLocalizedString("_delete_", comment: ""),
-                icon: NCUtility.shared.loadImage(named: "trash"),
+                icon: utility.loadImage(named: "trash"),
                 action: { _ in
                     self.deleteItem(with: objectId)
                 }

+ 15 - 15
iOSClient/Menu/NCViewer+Menu.swift

@@ -44,7 +44,7 @@ extension NCViewer {
             actions.append(
                 NCMenuAction(
                     title: NSLocalizedString("_details_", comment: ""),
-                    icon: NCUtility.shared.loadImage(named: "info"),
+                    icon: utility.loadImage(named: "info"),
                     action: { _ in
                         NCActionCenter.shared.openShare(viewController: viewController, metadata: metadata, page: .activity)
                     }
@@ -59,7 +59,7 @@ extension NCViewer {
             actions.append(
                 NCMenuAction(
                     title: NSLocalizedString("_view_in_folder_", comment: ""),
-                    icon: NCUtility.shared.loadImage(named: "questionmark.folder"),
+                    icon: utility.loadImage(named: "questionmark.folder"),
                     action: { _ in
                         NCActionCenter.shared.openFileViewInFolder(serverUrl: metadata.serverUrl, fileNameBlink: metadata.fileName, fileNameOpen: nil)
                     }
@@ -75,11 +75,11 @@ extension NCViewer {
             actions.append(
                 NCMenuAction(
                     title: titleFavorite,
-                    icon: NCUtility.shared.loadImage(named: "star.fill", color: NCBrandColor.shared.yellowFavorite),
+                    icon: utility.loadImage(named: "star.fill", color: NCBrandColor.shared.yellowFavorite),
                     action: { _ in
                         NCNetworking.shared.favoriteMetadata(metadata) { error in
                             if error != .success {
-                                NCContentPresenter.shared.showError(error: error)
+                                NCContentPresenter().showError(error: error)
                             }
                         }
                     }
@@ -108,9 +108,9 @@ extension NCViewer {
             actions.append(
                 NCMenuAction(
                     title: NSLocalizedString("_print_", comment: ""),
-                    icon: NCUtility.shared.loadImage(named: "printer"),
+                    icon: utility.loadImage(named: "printer"),
                     action: { _ in
-                        if CCUtility.fileProviderStorageExists(metadata) {
+                        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])
                         } else {
                             NCNetworking.shared.download(metadata: metadata, selector: NCGlobal.shared.selectorPrint) { _, _ in }
@@ -130,7 +130,7 @@ extension NCViewer {
             actions.append(
                 NCMenuAction(
                     title: NSLocalizedString("_video_processing_", comment: ""),
-                    icon: NCUtility.shared.loadImage(named: "film"),
+                    icon: utility.loadImage(named: "film"),
                     action: { menuAction in
                         if let ncplayer = (viewController as? NCViewerMediaPage)?.currentViewController.ncplayer {
                             ncplayer.convertVideo(withAlert: false)
@@ -155,9 +155,9 @@ extension NCViewer {
             actions.append(
                 NCMenuAction(
                     title: NSLocalizedString("_save_as_scan_", comment: ""),
-                    icon: NCUtility.shared.loadImage(named: "viewfinder.circle"),
+                    icon: utility.loadImage(named: "viewfinder.circle"),
                     action: { _ in
-                        if CCUtility.fileProviderStorageExists(metadata) {
+                        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])
                         } else {
                             NCNetworking.shared.download(metadata: metadata, selector: NCGlobal.shared.selectorSaveAsScan) { _, _ in }
@@ -174,7 +174,7 @@ extension NCViewer {
             actions.append(
                 NCMenuAction(
                     title: NSLocalizedString("_rename_", comment: ""),
-                    icon: NCUtility.shared.loadImage(named: "pencil"),
+                    icon: utility.loadImage(named: "pencil"),
                     action: { _ in
 
                         if let vcRename = UIStoryboard(name: "NCRenameFile", bundle: nil).instantiateInitialViewController() as? NCRenameFile {
@@ -210,11 +210,11 @@ extension NCViewer {
         //
         // DOWNLOAD LOCALLY
         //
-        if !webView, metadata.session.isEmpty, !CCUtility.fileProviderStorageExists(metadata) {
+        if !webView, metadata.session.isEmpty, !self.utilityFileSystem.fileProviderStorageExists(metadata) {
             actions.append(
                 NCMenuAction(
                     title: NSLocalizedString("_download_locally_", comment: ""),
-                    icon: NCUtility.shared.loadImage(named: "icloud.and.arrow.down"),
+                    icon: utility.loadImage(named: "icloud.and.arrow.down"),
                     action: { _ in
                         NCNetworking.shared.download(metadata: metadata, selector: "") { _, _ in }
                     }
@@ -239,7 +239,7 @@ extension NCViewer {
             actions.append(
                 NCMenuAction(
                     title: NSLocalizedString("_go_to_page_", comment: ""),
-                    icon: NCUtility.shared.loadImage(named: "repeat"),
+                    icon: utility.loadImage(named: "repeat"),
                     action: { _ in
                         NotificationCenter.default.postOnMainThread(name: NCGlobal.shared.notificationCenterMenuGotToPageInPDF)
                     }
@@ -254,9 +254,9 @@ extension NCViewer {
             actions.append(
                 NCMenuAction(
                     title: NSLocalizedString("_modify_", comment: ""),
-                    icon: NCUtility.shared.loadImage(named: "pencil.tip.crop.circle"),
+                    icon: utility.loadImage(named: "pencil.tip.crop.circle"),
                     action: { _ in
-                        if CCUtility.fileProviderStorageExists(metadata) {
+                        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])
                         } else {
                             NCNetworking.shared.download(metadata: metadata, selector: NCGlobal.shared.selectorLoadFileQuickLook) { _, _ in }

+ 6 - 6
iOSClient/Menu/UIViewController+Menu.swift

@@ -36,7 +36,7 @@ extension UIViewController {
                 let components = URLComponents(url: url, resolvingAgainstBaseURL: false)
             else {
                 let error = NKError(errorCode: NCGlobal.shared.errorInternalError, errorDescription: "_cannot_send_mail_error_")
-                NCContentPresenter.shared.showError(error: error)
+                NCContentPresenter().showError(error: error)
                 return
             }
             sendEmail(to: components.path)
@@ -52,7 +52,7 @@ extension UIViewController {
         default:
             guard let url = action.hyperlinkUrl, UIApplication.shared.canOpenURL(url) else {
                 let error = NKError(errorCode: NCGlobal.shared.errorInternalError, errorDescription: "_open_url_error_")
-                NCContentPresenter.shared.showError(error: error)
+                NCContentPresenter().showError(error: error)
                 return
             }
             UIApplication.shared.open(url, options: [:])
@@ -69,14 +69,14 @@ extension UIViewController {
 
             let personHeader = NCMenuAction(
                 title: card.displayName,
-                icon: NCUtility.shared.loadUserImage(
+                icon: NCUtility().loadUserImage(
                     for: userId,
                        displayName: card.displayName,
                        userBaseUrl: appDelegate),
                 action: nil)
 
             let actions = card.actions.map { action -> NCMenuAction in
-                var image = NCUtility.shared.loadImage(named: "user", color: .label)
+                var image = NCUtility().loadImage(named: "user", color: .label)
                 if let url = URL(string: action.icon),
                    let svgSource = SVGKSourceURL.source(from: url),
                    let svg = SVGKImage(source: svgSource) {
@@ -96,7 +96,7 @@ extension UIViewController {
     func sendEmail(to email: String) {
         guard MFMailComposeViewController.canSendMail() else {
             let error = NKError(errorCode: NCGlobal.shared.errorInternalError, errorDescription: "_cannot_send_mail_error_")
-            NCContentPresenter.shared.showError(error: error)
+            NCContentPresenter().showError(error: error)
             return
         }
 
@@ -112,7 +112,7 @@ extension UIViewController {
         let actions = actions.sorted(by: { $0.order < $1.order })
         guard let menuViewController = NCMenu.makeNCMenu(with: actions, menuColor: menuColor, textColor: textColor) else {
             let error = NKError(errorCode: NCGlobal.shared.errorInternalError, errorDescription: "_internal_generic_error_")
-            NCContentPresenter.shared.showError(error: error)
+            NCContentPresenter().showError(error: error)
             return
         }
 

+ 15 - 0
iOSClient/More/Cells/BaseNCMoreCell.swift

@@ -5,6 +5,21 @@
 //  Created by Milen on 15.06.23.
 //  Copyright © 2023 Marino Faggiana. All rights reserved.
 //
+//  Author Marino Faggiana <marino.faggiana@nextcloud.com>
+//
+//  This program is free software: you can redistribute it and/or modify
+//  it under the terms of the GNU General Public License as published by
+//  the Free Software Foundation, either version 3 of the License, or
+//  (at your option) any later version.
+//
+//  This program is distributed in the hope that it will be useful,
+//  but WITHOUT ANY WARRANTY; without even the implied warranty of
+//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+//  GNU General Public License for more details.
+//
+//  You should have received a copy of the GNU General Public License
+//  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+//
 
 import Foundation
 

+ 15 - 0
iOSClient/More/Cells/CCCellMore.swift

@@ -5,6 +5,21 @@
 //  Created by Milen on 14.06.23.
 //  Copyright © 2023 Marino Faggiana. All rights reserved.
 //
+//  Author Marino Faggiana <marino.faggiana@nextcloud.com>
+//
+//  This program is free software: you can redistribute it and/or modify
+//  it under the terms of the GNU General Public License as published by
+//  the Free Software Foundation, either version 3 of the License, or
+//  (at your option) any later version.
+//
+//  This program is distributed in the hope that it will be useful,
+//  but WITHOUT ANY WARRANTY; without even the implied warranty of
+//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+//  GNU General Public License for more details.
+//
+//  You should have received a copy of the GNU General Public License
+//  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+//
 
 import Foundation
 

+ 17 - 7
iOSClient/More/Cells/NCMoreAppSuggestionsCell.swift

@@ -5,6 +5,21 @@
 //  Created by Milen on 14.06.23.
 //  Copyright © 2023 Marino Faggiana. All rights reserved.
 //
+//  Author Marino Faggiana <marino.faggiana@nextcloud.com>
+//
+//  This program is free software: you can redistribute it and/or modify
+//  it under the terms of the GNU General Public License as published by
+//  the Free Software Foundation, either version 3 of the License, or
+//  (at your option) any later version.
+//
+//  This program is distributed in the hope that it will be useful,
+//  but WITHOUT ANY WARRANTY; without even the implied warranty of
+//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+//  GNU General Public License for more details.
+//
+//  You should have received a copy of the GNU General Public License
+//  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+//
 
 import Foundation
 import SafariServices
@@ -16,8 +31,6 @@ class NCMoreAppSuggestionsCell: BaseNCMoreCell {
 
     static let reuseIdentifier = "NCMoreAppSuggestionsCell"
 
-    weak var delegate: NCMoreAppSuggestionsCellDelegate?
-
     static func fromNib() -> UINib {
         return UINib(nibName: "NCMoreAppSuggestionsCell", bundle: nil)
     }
@@ -54,10 +67,7 @@ class NCMoreAppSuggestionsCell: BaseNCMoreCell {
     }
 
     @objc func moreAppsTapped() {
-        delegate?.moreAppsTapped()
+        guard let url = URL(string: NCGlobal.shared.moreAppsUrl) else { return }
+        UIApplication.shared.open(url)
     }
 }
-
-protocol NCMoreAppSuggestionsCellDelegate: AnyObject {
-    func moreAppsTapped()
-}

+ 15 - 0
iOSClient/More/Cells/NCMoreUserCell.swift

@@ -5,6 +5,21 @@
 //  Created by Milen on 14.06.23.
 //  Copyright © 2023 Marino Faggiana. All rights reserved.
 //
+//  Author Marino Faggiana <marino.faggiana@nextcloud.com>
+//
+//  This program is free software: you can redistribute it and/or modify
+//  it under the terms of the GNU General Public License as published by
+//  the Free Software Foundation, either version 3 of the License, or
+//  (at your option) any later version.
+//
+//  This program is distributed in the hope that it will be useful,
+//  but WITHOUT ANY WARRANTY; without even the implied warranty of
+//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+//  GNU General Public License for more details.
+//
+//  You should have received a copy of the GNU General Public License
+//  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+//
 
 import Foundation
 import MarqueeLabel

+ 8 - 22
iOSClient/More/NCMore.swift

@@ -37,14 +37,11 @@ class NCMore: UIViewController, UITableViewDelegate, UITableViewDataSource {
     private var externalSiteMenu: [NKExternalSite] = []
     private var settingsMenu: [NKExternalSite] = []
     private var quotaMenu: [NKExternalSite] = []
-
-    // swiftlint:disable force_cast
-    private let appDelegate = UIApplication.shared.delegate as! AppDelegate
-    // swiftlint:enable force_cast
-
+    private let appDelegate = (UIApplication.shared.delegate as? AppDelegate)!
     private let applicationHandle = NCApplicationHandle()
-
     private var tabAccount: tableAccount?
+    let utilityFileSystem = NCUtilityFileSystem()
+    let utility = NCUtility()
 
     private struct Section {
         var items: [NKExternalSite]
@@ -213,10 +210,10 @@ class NCMore: UIViewController, UITableViewDelegate, UITableViewDataSource {
             case -3:
                 quota = NSLocalizedString("_quota_space_unlimited_", comment: "")
             default:
-                quota = CCUtility.transformedSize(activeAccount.quotaTotal)
+                quota = utilityFileSystem.transformedSize(activeAccount.quotaTotal)
             }
 
-            let quotaUsed: String = CCUtility.transformedSize(activeAccount.quotaUsed)
+            let quotaUsed: String = utilityFileSystem.transformedSize(activeAccount.quotaUsed)
 
             labelQuota.text = String.localizedStringWithFormat(NSLocalizedString("_quota_using_", comment: ""), quotaUsed, quota)
         }
@@ -330,7 +327,7 @@ class NCMore: UIViewController, UITableViewDelegate, UITableViewDataSource {
             cell.displayName.text = ""
 
             if let account = tabAccount {
-                cell.avatar.image = NCUtility.shared.loadUserImage(for: account.user, displayName: account.displayName, userBaseUrl: appDelegate)
+                cell.avatar.image = utility.loadUserImage(for: account.user, displayName: account.displayName, userBaseUrl: appDelegate)
 
                 if account.alias.isEmpty {
                     cell.displayName?.text = account.displayName
@@ -342,7 +339,7 @@ class NCMore: UIViewController, UITableViewDelegate, UITableViewDataSource {
             cell.accessoryType = UITableViewCell.AccessoryType.disclosureIndicator
 
             if NCGlobal.shared.capabilityUserStatusEnabled, let account = NCManageDatabase.shared.getAccount(predicate: NSPredicate(format: "account == %@", appDelegate.account)) {
-                let status = NCUtility.shared.getUserStatus(userIcon: account.userStatusIcon, userStatus: account.userStatusStatus, userMessage: account.userStatusMessage)
+                let status = utility.getUserStatus(userIcon: account.userStatusIcon, userStatus: account.userStatusStatus, userMessage: account.userStatusMessage)
                 cell.icon.image = status.onlineStatus
                 cell.status.text = status.statusMessage
                 cell.status.textColor = .label
@@ -361,15 +358,13 @@ class NCMore: UIViewController, UITableViewDelegate, UITableViewDataSource {
         } else if section.type == .moreApps {
             guard let cell = tableView.dequeueReusableCell(withIdentifier: NCMoreAppSuggestionsCell.reuseIdentifier, for: indexPath) as? NCMoreAppSuggestionsCell else { return UITableViewCell() }
 
-            cell.delegate = self
-
             return cell
         } else {
             guard let cell = tableView.dequeueReusableCell(withIdentifier: CCCellMore.reuseIdentifier, for: indexPath) as? CCCellMore else { return UITableViewCell() }
 
             let item = sections[indexPath.section].items[indexPath.row]
 
-            cell.imageIcon?.image = NCUtility.shared.loadImage(named: item.icon)
+            cell.imageIcon?.image = utility.loadImage(named: item.icon)
             cell.imageIcon?.contentMode = .scaleAspectFit
             cell.labelText?.text = NSLocalizedString(item.name, comment: "")
             cell.labelText.textColor = .label
@@ -449,12 +444,3 @@ class NCMore: UIViewController, UITableViewDelegate, UITableViewDataSource {
         }
     }
 }
-
-extension NCMore: NCMoreAppSuggestionsCellDelegate {
-    func moreAppsTapped() {
-        guard let url = URL(string: NCGlobal.shared.moreAppsUrl) else { return }
-        let safariViewController = SFSafariViewController(url: url)
-
-        present(safariViewController, animated: true, completion: nil)
-    }
-}

+ 12 - 4
iOSClient/NCGlobal.swift

@@ -61,6 +61,13 @@ class NCGlobal: NSObject {
         return result.reduce(0, { $0 + $1 }) % maximum
     }
 
+    // ENUM
+    //
+    public enum TypeFilterScanDocument: String {
+        case document = "document"
+        case original = "original"
+    }
+
     // Directory on Group
     //
     @objc let directoryProviderStorage              = "File Provider Storage"
@@ -72,7 +79,6 @@ class NCGlobal: NSObject {
 
     // Service
     //
-    @objc let serviceShareKeyChain                  = "Crypto Cloud"
     let metadataKeyedUnarchiver                     = "it.twsweb.nextcloud.metadata"
     let refreshTask                                 = "com.nextcloud.refreshTask"
     let processingTask                              = "com.nextcloud.processingTask"
@@ -97,6 +103,8 @@ class NCGlobal: NSObject {
     let nextcloudVersion25: Int                     = 25
     let nextcloudVersion26: Int                     = 26
     let nextcloudVersion27: Int                     = 27
+    let nextcloudVersion28: Int                     = 28
+    let nextcloudVersion29: Int                     = 29
 
     // Nextcloud unsupported
     //
@@ -213,6 +221,7 @@ class NCGlobal: NSObject {
     @objc let errorPreconditionFailed: Int          = 412
     @objc let errorQuota: Int                       = 507
     @objc let errorUnauthorized997: Int             = 997
+    @objc let errorExplicitlyCancelled: Int         = -999
     @objc let errorConnectionLost: Int              = -1005
     @objc let errorNetworkNotAvailable: Int         = -1009
     @objc let errorBadServerResponse: Int           = -1011
@@ -379,8 +388,6 @@ class NCGlobal: NSObject {
     let notificationCenterMenuSearchTextPDF                     = "menuSearchTextPDF"
     let notificationCenterMenuGotToPageInPDF                    = "menuGotToPageInPDF"
 
-    let notificationCenterDownloadedThumbnail                   = "DownloadedThumbnail"             // userInfo: ocId
-
     let notificationCenterOpenMediaDetail                       = "openMediaDetail"                 // userInfo: ocId
 
     let notificationCenterDismissScanDocument                   = "dismissScanDocument"
@@ -453,6 +460,7 @@ class NCGlobal: NSObject {
     @objc var capabilityE2EEEnabled: Bool                       = false
     @objc var capabilityE2EEApiVersion: String                  = ""
 
+    var capabilityRichdocumentsEnabled: Bool                    = false
     var capabilityRichdocumentsMimetypes: [String]              = []
     var capabilityActivity: [String]                            = []
     var capabilityNotification: [String]                        = []
@@ -471,7 +479,7 @@ class NCGlobal: NSObject {
     let notesSchemeUrl                                          = "nextcloudnotes://"
     let talkAppStoreUrl                                         = "https://apps.apple.com/de/app/nextcloud-talk/id1296825574"
     let notesAppStoreUrl                                        = "https://apps.apple.com/de/app/nextcloud-notes/id813973264"
-    let moreAppsUrl                                             = "https://www.apple.com/us/search/nextcloud?src=globalnav"
+    let moreAppsUrl                                             = "itms-apps://search.itunes.apple.com/WebObjects/MZSearch.woa/wa/search?media=software&term=nextcloud"
 
     // SNAPSHOT PREVIEW
     let defaultSnapshotConfiguration = "DefaultPreviewConfiguration"

+ 256 - 0
iOSClient/NCImageCache.swift

@@ -0,0 +1,256 @@
+//
+//  NCImageCache.swift
+//  Nextcloud
+//
+//  Created by Marino Faggiana on 18/10/23.
+//  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 LRUCache
+import NextcloudKit
+
+@objc class NCImageCache: NSObject {
+    @objc public static let shared: NCImageCache = {
+        let instance = NCImageCache()
+        return instance
+    }()
+
+    // MARK: -
+
+    private let limit: Int = 1000
+    private var account: String = ""
+    private var brandElementColor: UIColor?
+
+    enum ImageType {
+        case placeholder
+        case actual(_ image: UIImage)
+    }
+
+    private typealias ThumbnailLRUCache = LRUCache<String, ImageType>
+    private lazy var cache: ThumbnailLRUCache = {
+        return ThumbnailLRUCache(countLimit: limit)
+    }()
+    private var ocIdEtag: [String: String] = [:]
+    public var metadatas: [tableMetadata] = []
+    public var livePhoto: Bool = false
+
+    override private init() {}
+
+    func createMediaCache(account: String) {
+
+        guard account != self.account, !account.isEmpty else { return }
+        self.account = account
+
+        ocIdEtag.removeAll()
+        metadatas.removeAll()
+        getMediaMetadatas(account: account)
+
+        guard !metadatas.isEmpty else { return }
+        let ext = ".preview.ico"
+        let manager = FileManager.default
+        let resourceKeys = Set<URLResourceKey>([.nameKey, .pathKey, .fileSizeKey, .creationDateKey])
+        struct FileInfo {
+            var path: URL
+            var ocId: String
+            var date: Date
+        }
+        var files: [FileInfo] = []
+        let startDate = Date()
+
+        for metadata in metadatas {
+            ocIdEtag[metadata.ocId] = metadata.etag
+        }
+
+        if let enumerator = manager.enumerator(at: URL(fileURLWithPath: NCUtilityFileSystem().directoryProviderStorage), includingPropertiesForKeys: [.isRegularFileKey], options: [.skipsHiddenFiles]) {
+            for case let fileURL as URL in enumerator where fileURL.lastPathComponent.hasSuffix(ext) {
+                let fileName = fileURL.lastPathComponent
+                let ocId = fileURL.deletingLastPathComponent().lastPathComponent
+                guard let resourceValues = try? fileURL.resourceValues(forKeys: resourceKeys),
+                      let size = resourceValues.fileSize,
+                      size > 0,
+                      let date = resourceValues.creationDate,
+                      let etag = ocIdEtag[ocId],
+                      fileName == etag + ext else { continue }
+                files.append(FileInfo(path: fileURL, ocId: ocId, date: date))
+            }
+        }
+
+        files.sort(by: { $0.date > $1.date })
+        if let firstDate = files.first?.date, let lastDate = files.last?.date {
+            print("First date: \(firstDate)")
+            print("Last date: \(lastDate)")
+        }
+
+        cache.removeAllValues()
+        var counter: Int = 0
+        for file in files {
+            counter += 1
+            if counter > limit { break }
+            autoreleasepool {
+                if let image = UIImage(contentsOfFile: file.path.path) {
+                    cache.setValue(.actual(image), forKey: file.ocId)
+                }
+            }
+        }
+
+        let endDate = Date()
+        let diffDate = endDate.timeIntervalSinceReferenceDate - startDate.timeIntervalSinceReferenceDate
+        NextcloudKit.shared.nkCommonInstance.writeLog("--------- ThumbnailLRUCache image process ---------")
+        NextcloudKit.shared.nkCommonInstance.writeLog("Counter process: \(cache.count)")
+        NextcloudKit.shared.nkCommonInstance.writeLog("Time process: \(diffDate)")
+        NextcloudKit.shared.nkCommonInstance.writeLog("--------- ThumbnailLRUCache image process ---------")
+    }
+
+    func getMediaImage(ocId: String) -> ImageType? {
+        return cache.value(forKey: ocId)
+    }
+
+    func setMediaImage(ocId: String, image: ImageType) {
+        cache.setValue(image, forKey: ocId)
+    }
+
+    @objc func clearMediaCache() {
+
+        ocIdEtag.removeAll()
+        metadatas.removeAll()
+        cache.removeAllValues()
+    }
+
+    func getMediaMetadatas(account: String, predicate: NSPredicate? = nil) {
+
+        guard let account = NCManageDatabase.shared.getAccount(predicate: NSPredicate(format: "account == %@", account)) else { return }
+        let startServerUrl = NCUtilityFileSystem().getHomeServer(urlBase: account.urlBase, userId: account.userId) + account.mediaPath
+
+        let predicateDefault = NSPredicate(format: "account == %@ AND serverUrl BEGINSWITH %@ AND (classFile == %@ OR classFile == %@) AND NOT (session CONTAINS[c] 'upload')", account.account, startServerUrl, NKCommon.TypeClassFile.image.rawValue, NKCommon.TypeClassFile.video.rawValue)
+
+        livePhoto = NCKeychain().livePhoto
+
+        let newMetadatas = NCManageDatabase.shared.getMetadatasMedia(predicate: predicate ?? predicateDefault, livePhoto: livePhoto)
+        if metadatas != newMetadatas {
+            metadatas = newMetadatas
+        }
+
+        switch NCKeychain().mediaSortDate {
+        case "date":
+            metadatas = self.metadatas.sorted(by: {($0.date as Date) > ($1.date as Date)})
+        case "creationDate":
+            metadatas = self.metadatas.sorted(by: {($0.creationDate as Date) > ($1.creationDate as Date)})
+        case "uploadDate":
+            metadatas = self.metadatas.sorted(by: {($0.uploadDate as Date) > ($1.uploadDate as Date)})
+        default:
+            break
+        }
+    }
+
+    // MARK: -
+
+    struct images {
+        static var file = UIImage()
+
+        static var shared = UIImage()
+        static var canShare = UIImage()
+        static var shareByLink = UIImage()
+
+        static var favorite = UIImage()
+        static var comment = UIImage()
+        static var livePhoto = UIImage()
+        static var offlineFlag = UIImage()
+        static var local = UIImage()
+
+        static var folderEncrypted = UIImage()
+        static var folderSharedWithMe = UIImage()
+        static var folderPublic = UIImage()
+        static var folderGroup = UIImage()
+        static var folderExternal = UIImage()
+        static var folderAutomaticUpload = UIImage()
+        static var folder = UIImage()
+
+        static var checkedYes = UIImage()
+        static var checkedNo = UIImage()
+
+        static var buttonMore = UIImage()
+        static var buttonStop = UIImage()
+        static var buttonMoreLock = UIImage()
+        static var buttonRestore = UIImage()
+        static var buttonTrash = UIImage()
+
+        static var iconContacts = UIImage()
+        static var iconTalk = UIImage()
+        static var iconCalendar = UIImage()
+        static var iconDeck = UIImage()
+        static var iconMail = UIImage()
+        static var iconConfirm = UIImage()
+        static var iconPages = UIImage()
+    }
+
+    func createImagesCache() {
+
+        let yellowFavorite = NCBrandColor.shared.yellowFavorite
+        let utility = NCUtility()
+
+        images.file = UIImage(named: "file")!
+
+        images.shared = UIImage(named: "share")!.image(color: .systemGray, size: 50)
+        images.canShare = UIImage(named: "share")!.image(color: .systemGray, size: 50)
+        images.shareByLink = UIImage(named: "sharebylink")!.image(color: .systemGray, size: 50)
+
+        images.favorite = utility.loadImage(named: "star.fill", color: yellowFavorite)
+        images.comment = UIImage(named: "comment")!.image(color: .systemGray, size: 50)
+        images.livePhoto = utility.loadImage(named: "livephoto", color: .label)
+        images.offlineFlag = UIImage(named: "offlineFlag")!
+        images.local = UIImage(named: "local")!
+
+        images.checkedYes = utility.loadImage(named: "checkmark.circle.fill", color: .systemBlue)
+        images.checkedNo = utility.loadImage(named: "circle", color: .systemGray)
+
+        images.buttonMore = UIImage(named: "more")!.image(color: .systemGray, size: 50)
+        images.buttonStop = UIImage(named: "stop")!.image(color: .systemGray, size: 50)
+        images.buttonMoreLock = UIImage(named: "moreLock")!.image(color: .systemGray, size: 50)
+        images.buttonRestore = UIImage(named: "restore")!.image(color: .systemGray, size: 50)
+        images.buttonTrash = UIImage(named: "trash")!.image(color: .systemGray, size: 50)
+
+        createImagesBrandCache()
+    }
+
+    func createImagesBrandCache() {
+
+        let brandElement = NCBrandColor.shared.brandElement
+        guard brandElement != self.brandElementColor else { return }
+        self.brandElementColor = brandElement
+
+        let folderWidth: CGFloat = UIScreen.main.bounds.width / 3
+        images.folderEncrypted = UIImage(named: "folderEncrypted")!.image(color: brandElement, size: folderWidth)
+        images.folderSharedWithMe = UIImage(named: "folder_shared_with_me")!.image(color: brandElement, size: folderWidth)
+        images.folderPublic = UIImage(named: "folder_public")!.image(color: brandElement, size: folderWidth)
+        images.folderGroup = UIImage(named: "folder_group")!.image(color: brandElement, size: folderWidth)
+        images.folderExternal = UIImage(named: "folder_external")!.image(color: brandElement, size: folderWidth)
+        images.folderAutomaticUpload = UIImage(named: "folderAutomaticUpload")!.image(color: brandElement, size: folderWidth)
+        images.folder = UIImage(named: "folder")!.image(color: brandElement, size: folderWidth)
+
+        images.iconContacts = UIImage(named: "icon-contacts")!.image(color: brandElement, size: folderWidth)
+        images.iconTalk = UIImage(named: "icon-talk")!.image(color: brandElement, size: folderWidth)
+        images.iconCalendar = UIImage(named: "icon-calendar")!.image(color: brandElement, size: folderWidth)
+        images.iconDeck = UIImage(named: "icon-deck")!.image(color: brandElement, size: folderWidth)
+        images.iconMail = UIImage(named: "icon-mail")!.image(color: brandElement, size: folderWidth)
+        images.iconConfirm = UIImage(named: "icon-confirm")!.image(color: brandElement, size: folderWidth)
+        images.iconPages = UIImage(named: "icon-pages")!.image(color: brandElement, size: folderWidth)
+
+        NotificationCenter.default.postOnMainThread(name: NCGlobal.shared.notificationCenterChangeTheming)
+    }
+}

+ 1 - 1
iOSClient/Networking/E2EE/NCEndToEndEncryption.m

@@ -564,7 +564,7 @@
     NSData *initializationVectorData = [[NSData alloc] initWithBase64EncodedString:initializationVector options:0];
     NSData *authenticationTagData = [[NSData alloc] initWithBase64EncodedString:authenticationTag options:0];
 
-   return [self decryptFile:[CCUtility getDirectoryProviderStorageOcId:ocId fileNameView:fileName] fileNamePlain:[CCUtility getDirectoryProviderStorageOcId:ocId fileNameView:fileNameView] key:keyData keyLen:AES_KEY_128_LENGTH initializationVector:initializationVectorData authenticationTag:authenticationTagData];
+    return [self decryptFile:[[[NCUtilityFileSystem alloc] init] getDirectoryProviderStorageOcId:ocId fileNameView:fileName] fileNamePlain:[[[NCUtilityFileSystem alloc] init] getDirectoryProviderStorageOcId:ocId fileNameView:fileNameView] key:keyData keyLen:AES_KEY_128_LENGTH initializationVector:initializationVectorData authenticationTag:authenticationTagData];
 }
 
 // -----------------------------------------------------------------------------------------------------------------------------------------------------------------------

部分文件因为文件数量过多而无法显示