1 package com.bumptech.glide.load.engine; 2 3 import android.util.Log; 4 5 import com.bumptech.glide.Priority; 6 import com.bumptech.glide.load.Encoder; 7 import com.bumptech.glide.load.Key; 8 import com.bumptech.glide.load.Transformation; 9 import com.bumptech.glide.load.data.DataFetcher; 10 import com.bumptech.glide.load.engine.cache.DiskCache; 11 import com.bumptech.glide.load.resource.transcode.ResourceTranscoder; 12 import com.bumptech.glide.provider.DataLoadProvider; 13 import com.bumptech.glide.util.LogTime; 14 15 import java.io.BufferedOutputStream; 16 import java.io.File; 17 import java.io.FileNotFoundException; 18 import java.io.FileOutputStream; 19 import java.io.IOException; 20 import java.io.OutputStream; 21 22 /** 23 * A class responsible for decoding resources either from cached data or from the original source and applying 24 * transformations and transcodes. 25 * 26 * @param <A> The type of the source data the resource can be decoded from. 27 * @param <T> The type of resource that will be decoded. 28 * @param <Z> The type of resource that will be transcoded from the decoded and transformed resource. 29 */ 30 class DecodeJob<A, T, Z> { 31 private static final String TAG = "DecodeJob"; 32 private static final FileOpener DEFAULT_FILE_OPENER = new FileOpener(); 33 34 private final EngineKey resultKey; 35 private final int width; 36 private final int height; 37 private final DataFetcher<A> fetcher; 38 private final DataLoadProvider<A, T> loadProvider; 39 private final Transformation<T> transformation; 40 private final ResourceTranscoder<T, Z> transcoder; 41 private final DiskCacheStrategy diskCacheStrategy; 42 private final DiskCache diskCache; 43 private final Priority priority; 44 private final FileOpener fileOpener; 45 46 private volatile boolean isCancelled; 47 DecodeJob(EngineKey resultKey, int width, int height, DataFetcher<A> fetcher, DataLoadProvider<A, T> loadProvider, Transformation<T> transformation, ResourceTranscoder<T, Z> transcoder, DiskCache diskCache, DiskCacheStrategy diskCacheStrategy, Priority priority)48 public DecodeJob(EngineKey resultKey, int width, int height, DataFetcher<A> fetcher, 49 DataLoadProvider<A, T> loadProvider, Transformation<T> transformation, ResourceTranscoder<T, Z> transcoder, 50 DiskCache diskCache, DiskCacheStrategy diskCacheStrategy, Priority priority) { 51 this(resultKey, width, height, fetcher, loadProvider, transformation, transcoder, diskCache, diskCacheStrategy, 52 priority, DEFAULT_FILE_OPENER); 53 } 54 55 // Visible for testing. DecodeJob(EngineKey resultKey, int width, int height, DataFetcher<A> fetcher, DataLoadProvider<A, T> loadProvider, Transformation<T> transformation, ResourceTranscoder<T, Z> transcoder, DiskCache diskCache, DiskCacheStrategy diskCacheStrategy, Priority priority, FileOpener fileOpener)56 DecodeJob(EngineKey resultKey, int width, int height, DataFetcher<A> fetcher, 57 DataLoadProvider<A, T> loadProvider, Transformation<T> transformation, ResourceTranscoder<T, Z> transcoder, 58 DiskCache diskCache, DiskCacheStrategy diskCacheStrategy, Priority priority, FileOpener fileOpener) { 59 this.resultKey = resultKey; 60 this.width = width; 61 this.height = height; 62 this.fetcher = fetcher; 63 this.loadProvider = loadProvider; 64 this.transformation = transformation; 65 this.transcoder = transcoder; 66 this.diskCacheStrategy = diskCacheStrategy; 67 this.diskCache = diskCache; 68 this.priority = priority; 69 this.fileOpener = fileOpener; 70 } 71 72 /** 73 * Returns a transcoded resource decoded from transformed resource data in the disk cache, or null if no such 74 * resource exists. 75 * 76 * @throws Exception 77 */ decodeResultFromCache()78 public Resource<Z> decodeResultFromCache() throws Exception { 79 if (!diskCacheStrategy.cacheResult()) { 80 return null; 81 } 82 83 long startTime = LogTime.getLogTime(); 84 Resource<T> transformed = loadFromCache(resultKey); 85 if (Log.isLoggable(TAG, Log.VERBOSE)) { 86 logWithTimeAndKey("Decoded transformed from cache", startTime); 87 } 88 startTime = LogTime.getLogTime(); 89 Resource<Z> result = transcode(transformed); 90 if (Log.isLoggable(TAG, Log.VERBOSE)) { 91 logWithTimeAndKey("Transcoded transformed from cache", startTime); 92 } 93 return result; 94 } 95 96 /** 97 * Returns a transformed and transcoded resource decoded from source data in the disk cache, or null if no such 98 * resource exists. 99 * 100 * @throws Exception 101 */ decodeSourceFromCache()102 public Resource<Z> decodeSourceFromCache() throws Exception { 103 if (!diskCacheStrategy.cacheSource()) { 104 return null; 105 } 106 107 long startTime = LogTime.getLogTime(); 108 Resource<T> decoded = loadFromCache(resultKey.getOriginalKey()); 109 if (Log.isLoggable(TAG, Log.VERBOSE)) { 110 logWithTimeAndKey("Decoded source from cache", startTime); 111 } 112 return transformEncodeAndTranscode(decoded); 113 } 114 115 /** 116 * Returns a transformed and transcoded resource decoded from source data, or null if no source data could be 117 * obtained or no resource could be decoded. 118 * 119 * <p> 120 * Depending on the {@link com.bumptech.glide.load.engine.DiskCacheStrategy} used, source data is either decoded 121 * directly or first written to the disk cache and then decoded from the disk cache. 122 * </p> 123 * 124 * @throws Exception 125 */ decodeFromSource()126 public Resource<Z> decodeFromSource() throws Exception { 127 Resource<T> decoded = decodeSource(); 128 return transformEncodeAndTranscode(decoded); 129 } 130 cancel()131 public void cancel() { 132 fetcher.cancel(); 133 isCancelled = true; 134 } 135 transformEncodeAndTranscode(Resource<T> decoded)136 private Resource<Z> transformEncodeAndTranscode(Resource<T> decoded) { 137 long startTime = LogTime.getLogTime(); 138 Resource<T> transformed = transform(decoded); 139 if (Log.isLoggable(TAG, Log.VERBOSE)) { 140 logWithTimeAndKey("Transformed resource from source", startTime); 141 } 142 143 writeTransformedToCache(transformed); 144 145 startTime = LogTime.getLogTime(); 146 Resource<Z> result = transcode(transformed); 147 if (Log.isLoggable(TAG, Log.VERBOSE)) { 148 logWithTimeAndKey("Transcoded transformed from source", startTime); 149 } 150 return result; 151 } 152 writeTransformedToCache(Resource<T> transformed)153 private void writeTransformedToCache(Resource<T> transformed) { 154 if (transformed == null || !diskCacheStrategy.cacheResult()) { 155 return; 156 } 157 long startTime = LogTime.getLogTime(); 158 SourceWriter<Resource<T>> writer = new SourceWriter<Resource<T>>(loadProvider.getEncoder(), transformed); 159 diskCache.put(resultKey, writer); 160 if (Log.isLoggable(TAG, Log.VERBOSE)) { 161 logWithTimeAndKey("Wrote transformed from source to cache", startTime); 162 } 163 } 164 decodeSource()165 private Resource<T> decodeSource() throws Exception { 166 Resource<T> decoded = null; 167 try { 168 long startTime = LogTime.getLogTime(); 169 final A data = fetcher.loadData(priority); 170 if (Log.isLoggable(TAG, Log.VERBOSE)) { 171 logWithTimeAndKey("Fetched data", startTime); 172 } 173 if (isCancelled) { 174 return null; 175 } 176 decoded = decodeFromSourceData(data); 177 } finally { 178 fetcher.cleanup(); 179 } 180 return decoded; 181 } 182 decodeFromSourceData(A data)183 private Resource<T> decodeFromSourceData(A data) throws IOException { 184 final Resource<T> decoded; 185 if (diskCacheStrategy.cacheSource()) { 186 decoded = cacheAndDecodeSourceData(data); 187 } else { 188 long startTime = LogTime.getLogTime(); 189 decoded = loadProvider.getSourceDecoder().decode(data, width, height); 190 if (Log.isLoggable(TAG, Log.VERBOSE)) { 191 logWithTimeAndKey("Decoded from source", startTime); 192 } 193 } 194 return decoded; 195 } 196 cacheAndDecodeSourceData(A data)197 private Resource<T> cacheAndDecodeSourceData(A data) throws IOException { 198 long startTime = LogTime.getLogTime(); 199 SourceWriter<A> writer = new SourceWriter<A>(loadProvider.getSourceEncoder(), data); 200 diskCache.put(resultKey.getOriginalKey(), writer); 201 if (Log.isLoggable(TAG, Log.VERBOSE)) { 202 logWithTimeAndKey("Wrote source to cache", startTime); 203 } 204 205 startTime = LogTime.getLogTime(); 206 Resource<T> result = loadFromCache(resultKey.getOriginalKey()); 207 if (Log.isLoggable(TAG, Log.VERBOSE) && result != null) { 208 logWithTimeAndKey("Decoded source from cache", startTime); 209 } 210 return result; 211 } 212 loadFromCache(Key key)213 private Resource<T> loadFromCache(Key key) throws IOException { 214 File cacheFile = diskCache.get(key); 215 if (cacheFile == null) { 216 return null; 217 } 218 219 Resource<T> result = null; 220 try { 221 result = loadProvider.getCacheDecoder().decode(cacheFile, width, height); 222 } finally { 223 if (result == null) { 224 diskCache.delete(key); 225 } 226 } 227 return result; 228 } 229 transform(Resource<T> decoded)230 private Resource<T> transform(Resource<T> decoded) { 231 if (decoded == null) { 232 return null; 233 } 234 235 Resource<T> transformed = transformation.transform(decoded, width, height); 236 if (!decoded.equals(transformed)) { 237 decoded.recycle(); 238 } 239 return transformed; 240 } 241 transcode(Resource<T> transformed)242 private Resource<Z> transcode(Resource<T> transformed) { 243 if (transformed == null) { 244 return null; 245 } 246 return transcoder.transcode(transformed); 247 } 248 logWithTimeAndKey(String message, long startTime)249 private void logWithTimeAndKey(String message, long startTime) { 250 Log.v(TAG, message + " in " + LogTime.getElapsedMillis(startTime) + resultKey); 251 } 252 253 class SourceWriter<DataType> implements DiskCache.Writer { 254 255 private final Encoder<DataType> encoder; 256 private final DataType data; 257 SourceWriter(Encoder<DataType> encoder, DataType data)258 public SourceWriter(Encoder<DataType> encoder, DataType data) { 259 this.encoder = encoder; 260 this.data = data; 261 } 262 263 @Override write(File file)264 public boolean write(File file) { 265 boolean success = false; 266 OutputStream os = null; 267 try { 268 os = fileOpener.open(file); 269 success = encoder.encode(data, os); 270 } catch (FileNotFoundException e) { 271 if (Log.isLoggable(TAG, Log.DEBUG)) { 272 Log.d(TAG, "Failed to find file to write to disk cache", e); 273 } 274 } finally { 275 if (os != null) { 276 try { 277 os.close(); 278 } catch (IOException e) { 279 // Do nothing. 280 } 281 } 282 } 283 return success; 284 } 285 } 286 287 static class FileOpener { open(File file)288 public OutputStream open(File file) throws FileNotFoundException { 289 return new BufferedOutputStream(new FileOutputStream(file)); 290 } 291 } 292 } 293