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

// The MIT License (MIT)
//
// Copyright (c) 2015-2021 Alexander Grebenyuk (github.com/kean).
import Foundation
/// Tries to load processed image data from disk and if not available, starts
/// `ProcessedImageTask` and subscribes to it.
final class TaskLoadImage: ImagePipelineTask<ImageResponse> {
override func start() {
if let image = pipeline.cachedImage(for: request) {
let response = ImageResponse(container: image)
if image.isPreview {
send(value: response)
} else {
return send(value: response, isCompleted: true)
}
}
guard let dataCache = pipeline.configuration.dataCache, pipeline.configuration.dataCacheOptions.storedItems.contains(.finalImage), request.cachePolicy != .reloadIgnoringCachedData else {
return loadDecompressedImage()
}
// Load processed image from data cache and decompress it.
operation = pipeline.configuration.dataCachingQueue.add { [weak self] in
self?.getCachedData(dataCache: dataCache)
}
}
private func getCachedData(dataCache: DataCaching) {
let key = request.makeCacheKeyForFinalImageData()
let data = signpost(log, "ReadCachedProcessedImageData") {
dataCache.cachedData(for: key)
}
async {
if let data = data {
self.decodeProcessedImageData(data)
} else {
self.loadDecompressedImage()
}
}
}
private func decodeProcessedImageData(_ data: Data) {
guard !isDisposed else { return }
let decoderContext = ImageDecodingContext(request: request, data: data, isCompleted: true, urlResponse: nil)
guard let decoder = pipeline.configuration.makeImageDecoder(decoderContext) else {
// This shouldn't happen in practice unless encoder/decoder pair
// for data cache is misconfigured.
return loadDecompressedImage()
}
operation = pipeline.configuration.imageDecodingQueue.add { [weak self] in
guard let self = self else { return }
let response = signpost(log, "DecodeCachedProcessedImageData") {
decoder.decode(data, urlResponse: nil, isCompleted: true)
}
self.async {
if let response = response {
self.decompressProcessedImage(response, isCompleted: true)
} else {
self.loadDecompressedImage()
}
}
}
}
private func loadDecompressedImage() {
dependency = pipeline.makeTaskProcessImage(for: request).subscribe(self) { [weak self] in
self?.storeImageInDataCache($0)
self?.decompressProcessedImage($0, isCompleted: $1)
}
}
#if os(macOS)
private func decompressProcessedImage(_ response: ImageResponse, isCompleted: Bool) {
pipeline.storeResponse(response.container, for: request)
send(value: response, isCompleted: isCompleted) // There is no decompression on macOS
}
#else
private func decompressProcessedImage(_ response: ImageResponse, isCompleted: Bool) {
guard isDecompressionNeeded(for: response) else {
pipeline.storeResponse(response.container, for: request)
send(value: response, isCompleted: isCompleted)
return
}
if isCompleted {
operation?.cancel() // Cancel any potential pending progressive decompression tasks
} else if operation != nil {
return // Back-pressure: we are receiving data too fast
}
guard !isDisposed else { return }
operation = pipeline.configuration.imageDecompressingQueue.add { [weak self] in
guard let self = self else { return }
let response = signpost(log, "DecompressImage", isCompleted ? "FinalImage" : "ProgressiveImage") {
response.map { $0.map(ImageDecompression.decompress(image:)) } ?? response
}
self.async {
self.pipeline.storeResponse(response.container, for: self.request)
self.send(value: response, isCompleted: isCompleted)
}
}
}
private func isDecompressionNeeded(for response: ImageResponse) -> Bool {
return pipeline.configuration.isDecompressionEnabled &&
ImageDecompression.isDecompressionNeeded(for: response.image) ?? false &&
!(ImagePipeline.Configuration._isAnimatedImageDataEnabled && response.image._animatedImageData != nil)
}
#endif
private func storeImageInDataCache(_ response: ImageResponse) {
guard let dataCache = pipeline.configuration.dataCache, pipeline.configuration.dataCacheOptions.storedItems.contains(.finalImage), !response.container.isPreview else {
return
}
let context = ImageEncodingContext(request: request, image: response.image, urlResponse: response.urlResponse)
let encoder = pipeline.configuration.makeImageEncoder(context)
pipeline.configuration.imageEncodingQueue.addOperation { [request, log] in
let encodedData = signpost(log, "EncodeImage") {
encoder.encode(response.container, context: context)
}
guard let data = encodedData else { return }
let key = request.makeCacheKeyForFinalImageData()
dataCache.storeData(data, for: key) // This is instant
}
}
}