NCImageCache.swift 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248
  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 UIKit
  24. import LRUCache
  25. import NextcloudKit
  26. import RealmSwift
  27. @objc class NCImageCache: NSObject {
  28. @objc public static let shared: NCImageCache = {
  29. let instance = NCImageCache()
  30. return instance
  31. }()
  32. // MARK: -
  33. private let limit: Int = 1000
  34. private var account: String = ""
  35. private var brandElementColor: UIColor?
  36. enum ImageType {
  37. case placeholder
  38. case actual(_ image: UIImage)
  39. }
  40. struct metadataInfo {
  41. var etag: String
  42. var date: NSDate
  43. }
  44. private typealias ThumbnailLRUCache = LRUCache<String, ImageType>
  45. private lazy var cache: ThumbnailLRUCache = {
  46. return ThumbnailLRUCache(countLimit: limit)
  47. }()
  48. private var metadatasInfo: [String: metadataInfo] = [:]
  49. private var metadatas: ThreadSafeArray<tableMetadata>?
  50. let showAllPredicateMediaString = "account == %@ AND serverUrl BEGINSWITH %@ AND (classFile == '\(NKCommon.TypeClassFile.image.rawValue)' OR classFile == '\(NKCommon.TypeClassFile.video.rawValue)') AND NOT (session CONTAINS[c] 'upload')"
  51. let showBothPredicateMediaString = "account == %@ AND serverUrl BEGINSWITH %@ AND (classFile == '\(NKCommon.TypeClassFile.image.rawValue)' OR classFile == '\(NKCommon.TypeClassFile.video.rawValue)') AND NOT (session CONTAINS[c] 'upload') AND NOT (livePhotoFile != '' AND classFile == '\(NKCommon.TypeClassFile.video.rawValue)')"
  52. let showOnlyPredicateMediaString = "account == %@ AND serverUrl BEGINSWITH %@ AND classFile == %@ AND NOT (session CONTAINS[c] 'upload') AND NOT (livePhotoFile != '' AND classFile == '\(NKCommon.TypeClassFile.video.rawValue)')"
  53. override private init() {}
  54. func createMediaCache(account: String) {
  55. guard account != self.account, !account.isEmpty else { return }
  56. self.account = account
  57. self.metadatasInfo.removeAll()
  58. self.metadatas = nil
  59. self.metadatas = getMediaMetadatas(account: account)
  60. guard let metadatas = self.metadatas, !metadatas.isEmpty else { return }
  61. let ext = ".preview.ico"
  62. let manager = FileManager.default
  63. let resourceKeys = Set<URLResourceKey>([.nameKey, .pathKey, .fileSizeKey, .creationDateKey])
  64. struct FileInfo {
  65. var path: URL
  66. var ocIdEtag: String
  67. var date: Date
  68. }
  69. var files: [FileInfo] = []
  70. let startDate = Date()
  71. metadatas.forEach { metadata in
  72. metadatasInfo[metadata.ocId] = metadataInfo(etag: metadata.etag, date: metadata.date)
  73. }
  74. if let enumerator = manager.enumerator(at: URL(fileURLWithPath: NCUtilityFileSystem().directoryProviderStorage), includingPropertiesForKeys: [.isRegularFileKey], options: [.skipsHiddenFiles]) {
  75. for case let fileURL as URL in enumerator where fileURL.lastPathComponent.hasSuffix(ext) {
  76. let fileName = fileURL.lastPathComponent
  77. let ocId = fileURL.deletingLastPathComponent().lastPathComponent
  78. guard let resourceValues = try? fileURL.resourceValues(forKeys: resourceKeys),
  79. let size = resourceValues.fileSize,
  80. size > 0,
  81. let date = metadatasInfo[ocId]?.date,
  82. let etag = metadatasInfo[ocId]?.etag,
  83. fileName == etag + ext else { continue }
  84. files.append(FileInfo(path: fileURL, ocIdEtag: ocId + etag, date: date as Date))
  85. }
  86. }
  87. files.sort(by: { $0.date > $1.date })
  88. if let firstDate = files.first?.date, let lastDate = files.last?.date {
  89. print("First date: \(firstDate)")
  90. print("Last date: \(lastDate)")
  91. }
  92. cache.removeAllValues()
  93. var counter: Int = 0
  94. for file in files {
  95. counter += 1
  96. if counter > (limit - 100) { break }
  97. autoreleasepool {
  98. if let image = UIImage(contentsOfFile: file.path.path) {
  99. cache.setValue(.actual(image), forKey: file.ocIdEtag)
  100. }
  101. }
  102. }
  103. let diffDate = Date().timeIntervalSinceReferenceDate - startDate.timeIntervalSinceReferenceDate
  104. NextcloudKit.shared.nkCommonInstance.writeLog("--------- ThumbnailLRUCache image process ---------")
  105. NextcloudKit.shared.nkCommonInstance.writeLog("Counter process: \(cache.count)")
  106. NextcloudKit.shared.nkCommonInstance.writeLog("Time process: \(diffDate)")
  107. NextcloudKit.shared.nkCommonInstance.writeLog("--------- ThumbnailLRUCache image process ---------")
  108. }
  109. func initialMetadatas() -> ThreadSafeArray<tableMetadata>? {
  110. defer { self.metadatas = nil }
  111. return self.metadatas
  112. }
  113. func getMediaImage(ocId: String, etag: String) -> ImageType? {
  114. return cache.value(forKey: ocId + etag)
  115. }
  116. func setMediaImage(ocId: String, etag: String, image: ImageType) {
  117. cache.setValue(image, forKey: ocId + etag)
  118. }
  119. @objc func clearMediaCache() {
  120. self.metadatasInfo.removeAll()
  121. self.metadatas = nil
  122. cache.removeAllValues()
  123. }
  124. func getMediaMetadatas(account: String, predicate: NSPredicate? = nil) -> ThreadSafeArray<tableMetadata>? {
  125. guard let account = NCManageDatabase.shared.getAccount(predicate: NSPredicate(format: "account == %@", account)) else { return nil }
  126. let startServerUrl = NCUtilityFileSystem().getHomeServer(urlBase: account.urlBase, userId: account.userId) + account.mediaPath
  127. let predicateBoth = NSPredicate(format: showBothPredicateMediaString, account.account, startServerUrl)
  128. return NCManageDatabase.shared.getMediaMetadatas(predicate: predicate ?? predicateBoth)
  129. }
  130. // MARK: -
  131. struct images {
  132. static var file = UIImage()
  133. static var shared = UIImage()
  134. static var canShare = UIImage()
  135. static var shareByLink = UIImage()
  136. static var favorite = UIImage()
  137. static var comment = UIImage()
  138. static var livePhoto = UIImage()
  139. static var offlineFlag = UIImage()
  140. static var local = UIImage()
  141. static var folderEncrypted = UIImage()
  142. static var folderSharedWithMe = UIImage()
  143. static var folderPublic = UIImage()
  144. static var folderGroup = UIImage()
  145. static var folderExternal = UIImage()
  146. static var folderAutomaticUpload = UIImage()
  147. static var folder = UIImage()
  148. static var checkedYes = UIImage()
  149. static var checkedNo = UIImage()
  150. static var buttonMore = UIImage()
  151. static var buttonStop = UIImage()
  152. static var buttonMoreLock = UIImage()
  153. static var buttonRestore = UIImage()
  154. static var buttonTrash = UIImage()
  155. static var iconContacts = UIImage()
  156. static var iconTalk = UIImage()
  157. static var iconCalendar = UIImage()
  158. static var iconDeck = UIImage()
  159. static var iconMail = UIImage()
  160. static var iconConfirm = UIImage()
  161. static var iconPages = UIImage()
  162. }
  163. func createImagesCache() {
  164. let yellowFavorite = NCBrandColor.shared.yellowFavorite
  165. let utility = NCUtility()
  166. images.file = UIImage(named: "file")!
  167. images.shared = UIImage(named: "share")!.image(color: .systemGray, size: 50)
  168. images.canShare = UIImage(named: "share")!.image(color: .systemGray, size: 50)
  169. images.shareByLink = UIImage(named: "sharebylink")!.image(color: .systemGray, size: 50)
  170. images.favorite = utility.loadImage(named: "star.fill", color: yellowFavorite)
  171. images.comment = UIImage(named: "comment")!.image(color: .systemGray, size: 50)
  172. images.livePhoto = utility.loadImage(named: "livephoto", color: .label)
  173. images.offlineFlag = UIImage(named: "offlineFlag")!
  174. images.local = UIImage(named: "local")!
  175. images.checkedYes = utility.loadImage(named: "checkmark.circle.fill", color: .systemBlue)
  176. images.checkedNo = utility.loadImage(named: "circle", color: .systemGray)
  177. images.buttonMore = UIImage(named: "more")!.image(color: .systemGray, size: 50)
  178. images.buttonStop = UIImage(named: "stop")!.image(color: .systemGray, size: 50)
  179. images.buttonMoreLock = UIImage(named: "moreLock")!.image(color: .systemGray, size: 50)
  180. images.buttonRestore = UIImage(named: "restore")!.image(color: .systemGray, size: 50)
  181. images.buttonTrash = UIImage(named: "trash")!.image(color: .systemGray, size: 50)
  182. createImagesBrandCache()
  183. }
  184. func createImagesBrandCache() {
  185. let brandElement = NCBrandColor.shared.brandElement
  186. guard brandElement != self.brandElementColor else { return }
  187. self.brandElementColor = brandElement
  188. let folderWidth: CGFloat = UIScreen.main.bounds.width / 3
  189. images.folderEncrypted = UIImage(named: "folderEncrypted")!.image(color: brandElement, size: folderWidth)
  190. images.folderSharedWithMe = UIImage(named: "folder_shared_with_me")!.image(color: brandElement, size: folderWidth)
  191. images.folderPublic = UIImage(named: "folder_public")!.image(color: brandElement, size: folderWidth)
  192. images.folderGroup = UIImage(named: "folder_group")!.image(color: brandElement, size: folderWidth)
  193. images.folderExternal = UIImage(named: "folder_external")!.image(color: brandElement, size: folderWidth)
  194. images.folderAutomaticUpload = UIImage(named: "folderAutomaticUpload")!.image(color: brandElement, size: folderWidth)
  195. images.folder = UIImage(named: "folder")!.image(color: brandElement, size: folderWidth)
  196. images.iconContacts = UIImage(named: "icon-contacts")!.image(color: brandElement, size: folderWidth)
  197. images.iconTalk = UIImage(named: "icon-talk")!.image(color: brandElement, size: folderWidth)
  198. images.iconCalendar = UIImage(named: "icon-calendar")!.image(color: brandElement, size: folderWidth)
  199. images.iconDeck = UIImage(named: "icon-deck")!.image(color: brandElement, size: folderWidth)
  200. images.iconMail = UIImage(named: "icon-mail")!.image(color: brandElement, size: folderWidth)
  201. images.iconConfirm = UIImage(named: "icon-confirm")!.image(color: brandElement, size: folderWidth)
  202. images.iconPages = UIImage(named: "icon-pages")!.image(color: brandElement, size: folderWidth)
  203. NotificationCenter.default.postOnMainThread(name: NCGlobal.shared.notificationCenterChangeTheming)
  204. }
  205. }