You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

133 lines
5.4 KiB

2 years ago
  1. // The MIT License (MIT)
  2. //
  3. // Copyright (c) 2015-2021 Alexander Grebenyuk (github.com/kean).
  4. import Foundation
  5. /// Tries to load processed image data from disk and if not available, starts
  6. /// `ProcessedImageTask` and subscribes to it.
  7. final class TaskLoadImage: ImagePipelineTask<ImageResponse> {
  8. override func start() {
  9. if let image = pipeline.cachedImage(for: request) {
  10. let response = ImageResponse(container: image)
  11. if image.isPreview {
  12. send(value: response)
  13. } else {
  14. return send(value: response, isCompleted: true)
  15. }
  16. }
  17. guard let dataCache = pipeline.configuration.dataCache, pipeline.configuration.dataCacheOptions.storedItems.contains(.finalImage), request.cachePolicy != .reloadIgnoringCachedData else {
  18. return loadDecompressedImage()
  19. }
  20. // Load processed image from data cache and decompress it.
  21. operation = pipeline.configuration.dataCachingQueue.add { [weak self] in
  22. self?.getCachedData(dataCache: dataCache)
  23. }
  24. }
  25. private func getCachedData(dataCache: DataCaching) {
  26. let key = request.makeCacheKeyForFinalImageData()
  27. let data = signpost(log, "ReadCachedProcessedImageData") {
  28. dataCache.cachedData(for: key)
  29. }
  30. async {
  31. if let data = data {
  32. self.decodeProcessedImageData(data)
  33. } else {
  34. self.loadDecompressedImage()
  35. }
  36. }
  37. }
  38. private func decodeProcessedImageData(_ data: Data) {
  39. guard !isDisposed else { return }
  40. let decoderContext = ImageDecodingContext(request: request, data: data, isCompleted: true, urlResponse: nil)
  41. guard let decoder = pipeline.configuration.makeImageDecoder(decoderContext) else {
  42. // This shouldn't happen in practice unless encoder/decoder pair
  43. // for data cache is misconfigured.
  44. return loadDecompressedImage()
  45. }
  46. operation = pipeline.configuration.imageDecodingQueue.add { [weak self] in
  47. guard let self = self else { return }
  48. let response = signpost(log, "DecodeCachedProcessedImageData") {
  49. decoder.decode(data, urlResponse: nil, isCompleted: true)
  50. }
  51. self.async {
  52. if let response = response {
  53. self.decompressProcessedImage(response, isCompleted: true)
  54. } else {
  55. self.loadDecompressedImage()
  56. }
  57. }
  58. }
  59. }
  60. private func loadDecompressedImage() {
  61. dependency = pipeline.makeTaskProcessImage(for: request).subscribe(self) { [weak self] in
  62. self?.storeImageInDataCache($0)
  63. self?.decompressProcessedImage($0, isCompleted: $1)
  64. }
  65. }
  66. #if os(macOS)
  67. private func decompressProcessedImage(_ response: ImageResponse, isCompleted: Bool) {
  68. pipeline.storeResponse(response.container, for: request)
  69. send(value: response, isCompleted: isCompleted) // There is no decompression on macOS
  70. }
  71. #else
  72. private func decompressProcessedImage(_ response: ImageResponse, isCompleted: Bool) {
  73. guard isDecompressionNeeded(for: response) else {
  74. pipeline.storeResponse(response.container, for: request)
  75. send(value: response, isCompleted: isCompleted)
  76. return
  77. }
  78. if isCompleted {
  79. operation?.cancel() // Cancel any potential pending progressive decompression tasks
  80. } else if operation != nil {
  81. return // Back-pressure: we are receiving data too fast
  82. }
  83. guard !isDisposed else { return }
  84. operation = pipeline.configuration.imageDecompressingQueue.add { [weak self] in
  85. guard let self = self else { return }
  86. let response = signpost(log, "DecompressImage", isCompleted ? "FinalImage" : "ProgressiveImage") {
  87. response.map { $0.map(ImageDecompression.decompress(image:)) } ?? response
  88. }
  89. self.async {
  90. self.pipeline.storeResponse(response.container, for: self.request)
  91. self.send(value: response, isCompleted: isCompleted)
  92. }
  93. }
  94. }
  95. private func isDecompressionNeeded(for response: ImageResponse) -> Bool {
  96. return pipeline.configuration.isDecompressionEnabled &&
  97. ImageDecompression.isDecompressionNeeded(for: response.image) ?? false &&
  98. !(ImagePipeline.Configuration._isAnimatedImageDataEnabled && response.image._animatedImageData != nil)
  99. }
  100. #endif
  101. private func storeImageInDataCache(_ response: ImageResponse) {
  102. guard let dataCache = pipeline.configuration.dataCache, pipeline.configuration.dataCacheOptions.storedItems.contains(.finalImage), !response.container.isPreview else {
  103. return
  104. }
  105. let context = ImageEncodingContext(request: request, image: response.image, urlResponse: response.urlResponse)
  106. let encoder = pipeline.configuration.makeImageEncoder(context)
  107. pipeline.configuration.imageEncodingQueue.addOperation { [request, log] in
  108. let encodedData = signpost(log, "EncodeImage") {
  109. encoder.encode(response.container, context: context)
  110. }
  111. guard let data = encodedData else { return }
  112. let key = request.makeCacheKeyForFinalImageData()
  113. dataCache.storeData(data, for: key) // This is instant
  114. }
  115. }
  116. }