package com.bumptech.glide.load.engine; import android.util.Log; import com.bumptech.glide.Priority; import com.bumptech.glide.load.Encoder; import com.bumptech.glide.load.Key; import com.bumptech.glide.load.Transformation; import com.bumptech.glide.load.data.DataFetcher; import com.bumptech.glide.load.engine.cache.DiskCache; import com.bumptech.glide.load.resource.transcode.ResourceTranscoder; import com.bumptech.glide.provider.DataLoadProvider; import com.bumptech.glide.util.LogTime; import java.io.BufferedOutputStream; import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; /** * A class responsible for decoding resources either from cached data or from the original source and applying * transformations and transcodes. * * @param The type of the source data the resource can be decoded from. * @param The type of resource that will be decoded. * @param The type of resource that will be transcoded from the decoded and transformed resource. */ class DecodeJob { private static final String TAG = "DecodeJob"; private static final FileOpener DEFAULT_FILE_OPENER = new FileOpener(); private final EngineKey resultKey; private final int width; private final int height; private final DataFetcher fetcher; private final DataLoadProvider loadProvider; private final Transformation transformation; private final ResourceTranscoder transcoder; private final DiskCacheStrategy diskCacheStrategy; private final DiskCache diskCache; private final Priority priority; private final FileOpener fileOpener; private volatile boolean isCancelled; public DecodeJob(EngineKey resultKey, int width, int height, DataFetcher fetcher, DataLoadProvider loadProvider, Transformation transformation, ResourceTranscoder transcoder, DiskCache diskCache, DiskCacheStrategy diskCacheStrategy, Priority priority) { this(resultKey, width, height, fetcher, loadProvider, transformation, transcoder, diskCache, diskCacheStrategy, priority, DEFAULT_FILE_OPENER); } // Visible for testing. DecodeJob(EngineKey resultKey, int width, int height, DataFetcher fetcher, DataLoadProvider loadProvider, Transformation transformation, ResourceTranscoder transcoder, DiskCache diskCache, DiskCacheStrategy diskCacheStrategy, Priority priority, FileOpener fileOpener) { this.resultKey = resultKey; this.width = width; this.height = height; this.fetcher = fetcher; this.loadProvider = loadProvider; this.transformation = transformation; this.transcoder = transcoder; this.diskCacheStrategy = diskCacheStrategy; this.diskCache = diskCache; this.priority = priority; this.fileOpener = fileOpener; } /** * Returns a transcoded resource decoded from transformed resource data in the disk cache, or null if no such * resource exists. * * @throws Exception */ public Resource decodeResultFromCache() throws Exception { if (!diskCacheStrategy.cacheResult()) { return null; } long startTime = LogTime.getLogTime(); Resource transformed = loadFromCache(resultKey); if (Log.isLoggable(TAG, Log.VERBOSE)) { logWithTimeAndKey("Decoded transformed from cache", startTime); } startTime = LogTime.getLogTime(); Resource result = transcode(transformed); if (Log.isLoggable(TAG, Log.VERBOSE)) { logWithTimeAndKey("Transcoded transformed from cache", startTime); } return result; } /** * Returns a transformed and transcoded resource decoded from source data in the disk cache, or null if no such * resource exists. * * @throws Exception */ public Resource decodeSourceFromCache() throws Exception { if (!diskCacheStrategy.cacheSource()) { return null; } long startTime = LogTime.getLogTime(); Resource decoded = loadFromCache(resultKey.getOriginalKey()); if (Log.isLoggable(TAG, Log.VERBOSE)) { logWithTimeAndKey("Decoded source from cache", startTime); } return transformEncodeAndTranscode(decoded); } /** * Returns a transformed and transcoded resource decoded from source data, or null if no source data could be * obtained or no resource could be decoded. * *

* Depending on the {@link com.bumptech.glide.load.engine.DiskCacheStrategy} used, source data is either decoded * directly or first written to the disk cache and then decoded from the disk cache. *

* * @throws Exception */ public Resource decodeFromSource() throws Exception { Resource decoded = decodeSource(); return transformEncodeAndTranscode(decoded); } public void cancel() { fetcher.cancel(); isCancelled = true; } private Resource transformEncodeAndTranscode(Resource decoded) { long startTime = LogTime.getLogTime(); Resource transformed = transform(decoded); if (Log.isLoggable(TAG, Log.VERBOSE)) { logWithTimeAndKey("Transformed resource from source", startTime); } writeTransformedToCache(transformed); startTime = LogTime.getLogTime(); Resource result = transcode(transformed); if (Log.isLoggable(TAG, Log.VERBOSE)) { logWithTimeAndKey("Transcoded transformed from source", startTime); } return result; } private void writeTransformedToCache(Resource transformed) { if (transformed == null || !diskCacheStrategy.cacheResult()) { return; } long startTime = LogTime.getLogTime(); SourceWriter> writer = new SourceWriter>(loadProvider.getEncoder(), transformed); diskCache.put(resultKey, writer); if (Log.isLoggable(TAG, Log.VERBOSE)) { logWithTimeAndKey("Wrote transformed from source to cache", startTime); } } private Resource decodeSource() throws Exception { Resource decoded = null; try { long startTime = LogTime.getLogTime(); final A data = fetcher.loadData(priority); if (Log.isLoggable(TAG, Log.VERBOSE)) { logWithTimeAndKey("Fetched data", startTime); } if (isCancelled) { return null; } decoded = decodeFromSourceData(data); } finally { fetcher.cleanup(); } return decoded; } private Resource decodeFromSourceData(A data) throws IOException { final Resource decoded; if (diskCacheStrategy.cacheSource()) { decoded = cacheAndDecodeSourceData(data); } else { long startTime = LogTime.getLogTime(); decoded = loadProvider.getSourceDecoder().decode(data, width, height); if (Log.isLoggable(TAG, Log.VERBOSE)) { logWithTimeAndKey("Decoded from source", startTime); } } return decoded; } private Resource cacheAndDecodeSourceData(A data) throws IOException { long startTime = LogTime.getLogTime(); SourceWriter
writer = new SourceWriter(loadProvider.getSourceEncoder(), data); diskCache.put(resultKey.getOriginalKey(), writer); if (Log.isLoggable(TAG, Log.VERBOSE)) { logWithTimeAndKey("Wrote source to cache", startTime); } startTime = LogTime.getLogTime(); Resource result = loadFromCache(resultKey.getOriginalKey()); if (Log.isLoggable(TAG, Log.VERBOSE) && result != null) { logWithTimeAndKey("Decoded source from cache", startTime); } return result; } private Resource loadFromCache(Key key) throws IOException { File cacheFile = diskCache.get(key); if (cacheFile == null) { return null; } Resource result = null; try { result = loadProvider.getCacheDecoder().decode(cacheFile, width, height); } finally { if (result == null) { diskCache.delete(key); } } return result; } private Resource transform(Resource decoded) { if (decoded == null) { return null; } Resource transformed = transformation.transform(decoded, width, height); if (!decoded.equals(transformed)) { decoded.recycle(); } return transformed; } private Resource transcode(Resource transformed) { if (transformed == null) { return null; } return transcoder.transcode(transformed); } private void logWithTimeAndKey(String message, long startTime) { Log.v(TAG, message + " in " + LogTime.getElapsedMillis(startTime) + resultKey); } class SourceWriter implements DiskCache.Writer { private final Encoder encoder; private final DataType data; public SourceWriter(Encoder encoder, DataType data) { this.encoder = encoder; this.data = data; } @Override public boolean write(File file) { boolean success = false; OutputStream os = null; try { os = fileOpener.open(file); success = encoder.encode(data, os); } catch (FileNotFoundException e) { if (Log.isLoggable(TAG, Log.DEBUG)) { Log.d(TAG, "Failed to find file to write to disk cache", e); } } finally { if (os != null) { try { os.close(); } catch (IOException e) { // Do nothing. } } } return success; } } static class FileOpener { public OutputStream open(File file) throws FileNotFoundException { return new BufferedOutputStream(new FileOutputStream(file)); } } }