NCImageCache.swift 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256
  1. //
  2. // NCImageCache.swift
  3. // Nextcloud
  4. //
  5. // Created by Marino Faggiana on 18/10/23.
  6. // Copyright © 2021 Marino Faggiana. All rights reserved.
  7. //
  8. // Author Marino Faggiana <marino.faggiana@nextcloud.com>
  9. //
  10. // This program is free software: you can redistribute it and/or modify
  11. // it under the terms of the GNU General Public License as published by
  12. // the Free Software Foundation, either version 3 of the License, or
  13. // (at your option) any later version.
  14. //
  15. // This program is distributed in the hope that it will be useful,
  16. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  17. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  18. // GNU General Public License for more details.
  19. //
  20. // You should have received a copy of the GNU General Public License
  21. // along with this program. If not, see <http://www.gnu.org/licenses/>.
  22. //
  23. import Foundation
  24. import UIKit
  25. import LRUCache
  26. import NextcloudKit
  27. import RealmSwift
  28. class NCImageCache: NSObject {
  29. static let shared = NCImageCache()
  30. private let utility = NCUtility()
  31. private let global = NCGlobal.shared
  32. private let allowExtensions = [NCGlobal.shared.previewExt256]
  33. private var brandElementColor: UIColor?
  34. public var countLimit: Int = 0
  35. lazy var cache: LRUCache<String, UIImage> = {
  36. return LRUCache<String, UIImage>(countLimit: countLimit)
  37. }()
  38. public var isLoadingCache: Bool = false
  39. override init() {
  40. super.init()
  41. countLimit = calculateMaxImages(percentage: 5.0, imageSizeKB: 30.0) // 5% of cache = 20
  42. NextcloudKit.shared.nkCommonInstance.writeLog("Counter cache image: \(countLimit)")
  43. NotificationCenter.default.addObserver(forName: LRUCacheMemoryWarningNotification, object: nil, queue: nil) { _ in
  44. self.cache.removeAllValues()
  45. self.countLimit = self.countLimit - 500
  46. if self.countLimit <= 0 { self.countLimit = 100 }
  47. self.cache = LRUCache<String, UIImage>(countLimit: self.countLimit)
  48. #if DEBUG
  49. NCContentPresenter().messageNotification("Cache image memory warning \(self.countLimit)", error: .success, delay: NCGlobal.shared.dismissAfterSecond, type: NCContentPresenter.messageType.error, priority: .max)
  50. #endif
  51. }
  52. NotificationCenter.default.addObserver(forName: UIApplication.didEnterBackgroundNotification, object: nil, queue: nil) { _ in
  53. autoreleasepool {
  54. self.cache.removeAllValues()
  55. }
  56. }
  57. NotificationCenter.default.addObserver(forName: UIApplication.willEnterForegroundNotification, object: nil, queue: nil) { _ in
  58. #if !EXTENSION
  59. var files: [NCFiles] = []
  60. var cost: Int = 0
  61. if let activeTableAccount = NCManageDatabase.shared.getActiveTableAccount(),
  62. NCImageCache.shared.cache.count == 0 {
  63. let session = NCSession.shared.getSession(account: activeTableAccount.account)
  64. for mainTabBarController in SceneManager.shared.getControllers() {
  65. if let currentVC = mainTabBarController.selectedViewController as? UINavigationController,
  66. let file = currentVC.visibleViewController as? NCFiles {
  67. files.append(file)
  68. }
  69. }
  70. /// MEDIA
  71. if let metadatas = NCManageDatabase.shared.getResultsMetadatas(predicate: self.getMediaPredicate(filterLivePhotoFile: true, session: session, showOnlyImages: false, showOnlyVideos: false), sortedByKeyPath: "date", freeze: true)?.prefix(self.countLimit) {
  72. self.cache.removeAllValues()
  73. self.isLoadingCache = true
  74. metadatas.forEach { metadata in
  75. if let image = self.utility.getImage(ocId: metadata.ocId, etag: metadata.etag, ext: NCGlobal.shared.previewExt256) {
  76. self.addImageCache(ocId: metadata.ocId, etag: metadata.etag, image: image, ext: NCGlobal.shared.previewExt256, cost: cost)
  77. cost += 1
  78. }
  79. }
  80. self.isLoadingCache = false
  81. }
  82. /// FILE
  83. for file in files where !file.serverUrl.isEmpty {
  84. NotificationCenter.default.postOnMainThread(name: NCGlobal.shared.notificationCenterReloadDataSource, userInfo: ["serverUrl": file.serverUrl])
  85. }
  86. }
  87. #endif
  88. }
  89. }
  90. deinit {
  91. NotificationCenter.default.removeObserver(self, name: LRUCacheMemoryWarningNotification, object: nil)
  92. }
  93. func calculateMaxImages(percentage: Double, imageSizeKB: Double) -> Int {
  94. let totalRamBytes = Double(ProcessInfo.processInfo.physicalMemory)
  95. let cacheSizeBytes = totalRamBytes * (percentage / 100.0)
  96. let imageSizeBytes = imageSizeKB * 1024
  97. let maxImages = Int(cacheSizeBytes / imageSizeBytes)
  98. return maxImages
  99. }
  100. func allowExtensions(ext: String) -> Bool {
  101. return allowExtensions.contains(ext)
  102. }
  103. func addImageCache(ocId: String, etag: String, data: Data, ext: String, cost: Int) {
  104. guard allowExtensions.contains(ext),
  105. let image = UIImage(data: data) else { return }
  106. cache.setValue(image, forKey: ocId + etag + ext, cost: cost)
  107. }
  108. func addImageCache(ocId: String, etag: String, image: UIImage, ext: String, cost: Int) {
  109. guard allowExtensions.contains(ext) else { return }
  110. cache.setValue(image, forKey: ocId + etag + ext, cost: cost)
  111. }
  112. func getImageCache(ocId: String, etag: String, ext: String) -> UIImage? {
  113. return cache.value(forKey: ocId + etag + ext)
  114. }
  115. func removeImageCache(ocIdPlusEtag: String) {
  116. for i in 0..<allowExtensions.count {
  117. cache.removeValue(forKey: ocIdPlusEtag + allowExtensions[i])
  118. }
  119. }
  120. func removeAll() {
  121. cache.removeAllValues()
  122. }
  123. // MARK: - MEDIA -
  124. func getMediaPredicate(filterLivePhotoFile: Bool, session: NCSession.Session, showOnlyImages: Bool, showOnlyVideos: Bool) -> NSPredicate {
  125. guard let tableAccount = NCManageDatabase.shared.getTableAccount(predicate: NSPredicate(format: "account == %@", session.account)) else { return NSPredicate() }
  126. var predicate = NSPredicate()
  127. let startServerUrl = NCUtilityFileSystem().getHomeServer(session: session) + tableAccount.mediaPath
  128. var showBothPredicateMediaString = "account == %@ AND serverUrl BEGINSWITH %@ AND hasPreview == true AND (classFile == '\(NKCommon.TypeClassFile.image.rawValue)' OR classFile == '\(NKCommon.TypeClassFile.video.rawValue)') AND NOT (status IN %@)"
  129. var showOnlyPredicateMediaString = "account == %@ AND serverUrl BEGINSWITH %@ AND hasPreview == true AND classFile == %@ AND NOT (status IN %@)"
  130. if filterLivePhotoFile {
  131. showBothPredicateMediaString = showBothPredicateMediaString + " AND NOT (livePhotoFile != '' AND classFile == '\(NKCommon.TypeClassFile.video.rawValue)')"
  132. showOnlyPredicateMediaString = showOnlyPredicateMediaString + " AND NOT (livePhotoFile != '' AND classFile == '\(NKCommon.TypeClassFile.video.rawValue)')"
  133. }
  134. if showOnlyImages {
  135. predicate = NSPredicate(format: showOnlyPredicateMediaString, session.account, startServerUrl, NKCommon.TypeClassFile.image.rawValue, global.metadataStatusHideInView)
  136. } else if showOnlyVideos {
  137. predicate = NSPredicate(format: showOnlyPredicateMediaString, session.account, startServerUrl, NKCommon.TypeClassFile.video.rawValue, global.metadataStatusHideInView)
  138. } else {
  139. predicate = NSPredicate(format: showBothPredicateMediaString, session.account, startServerUrl, global.metadataStatusHideInView)
  140. }
  141. return predicate
  142. }
  143. // MARK: -
  144. func getImageFile() -> UIImage {
  145. return utility.loadImage(named: "doc", colors: [NCBrandColor.shared.iconImageColor2])
  146. }
  147. func getImageShared() -> UIImage {
  148. return utility.loadImage(named: "person.fill.badge.plus", colors: NCBrandColor.shared.iconImageMultiColors)
  149. }
  150. func getImageCanShare() -> UIImage {
  151. return utility.loadImage(named: "person.fill.badge.plus", colors: NCBrandColor.shared.iconImageMultiColors)
  152. }
  153. func getImageShareByLink() -> UIImage {
  154. return utility.loadImage(named: "link", colors: [NCBrandColor.shared.iconImageColor])
  155. }
  156. func getImageFavorite() -> UIImage {
  157. return utility.loadImage(named: "star.fill", colors: [NCBrandColor.shared.yellowFavorite])
  158. }
  159. func getImageOfflineFlag() -> UIImage {
  160. return utility.loadImage(named: "arrow.down.circle.fill", colors: [.systemGreen])
  161. }
  162. func getImageLocal() -> UIImage {
  163. return utility.loadImage(named: "checkmark.circle.fill", colors: [.systemGreen])
  164. }
  165. func getImageCheckedYes() -> UIImage {
  166. return utility.loadImage(named: "checkmark.circle.fill", colors: [NCBrandColor.shared.iconImageColor2])
  167. }
  168. func getImageCheckedNo() -> UIImage {
  169. return utility.loadImage(named: "circle", colors: [NCBrandColor.shared.iconImageColor])
  170. }
  171. func getImageButtonMore() -> UIImage {
  172. return utility.loadImage(named: "ellipsis", colors: [NCBrandColor.shared.iconImageColor])
  173. }
  174. func getImageButtonStop() -> UIImage {
  175. return utility.loadImage(named: "stop.circle", colors: [NCBrandColor.shared.iconImageColor])
  176. }
  177. func getImageButtonMoreLock() -> UIImage {
  178. return utility.loadImage(named: "lock.fill", colors: [NCBrandColor.shared.iconImageColor])
  179. }
  180. func getFolder(account: String) -> UIImage {
  181. return UIImage(named: "folder")!.image(color: NCBrandColor.shared.getElement(account: account))
  182. }
  183. func getFolderEncrypted(account: String) -> UIImage {
  184. return UIImage(named: "folderEncrypted")!.image(color: NCBrandColor.shared.getElement(account: account))
  185. }
  186. func getFolderSharedWithMe(account: String) -> UIImage {
  187. return UIImage(named: "folder_shared_with_me")!.image(color: NCBrandColor.shared.getElement(account: account))
  188. }
  189. func getFolderPublic(account: String) -> UIImage {
  190. return UIImage(named: "folder_public")!.image(color: NCBrandColor.shared.getElement(account: account))
  191. }
  192. func getFolderGroup(account: String) -> UIImage {
  193. return UIImage(named: "folder_group")!.image(color: NCBrandColor.shared.getElement(account: account))
  194. }
  195. func getFolderExternal(account: String) -> UIImage {
  196. return UIImage(named: "folder_external")!.image(color: NCBrandColor.shared.getElement(account: account))
  197. }
  198. func getFolderAutomaticUpload(account: String) -> UIImage {
  199. return UIImage(named: "folderAutomaticUpload")!.image(color: NCBrandColor.shared.getElement(account: account))
  200. }
  201. }