NCImageCache.swift 14 KB

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