Home | History | Annotate | Download | only in engine
      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 
     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.
     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      */
     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      */
    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      */
    126     public Resource<Z> decodeFromSource() throws Exception {
    127         Resource<T> decoded = decodeSource();
    128         return transformEncodeAndTranscode(decoded);
    129     }
    130 
    131     public void cancel() {
    132         fetcher.cancel();
    133         isCancelled = true;
    134     }
    135 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    242     private Resource<Z> transcode(Resource<T> transformed) {
    243         if (transformed == null) {
    244             return null;
    245         }
    246         return transcoder.transcode(transformed);
    247     }
    248 
    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 
    258         public SourceWriter(Encoder<DataType> encoder, DataType data) {
    259             this.encoder = encoder;
    260             this.data = data;
    261         }
    262 
    263         @Override
    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 {
    288         public OutputStream open(File file) throws FileNotFoundException {
    289             return new BufferedOutputStream(new FileOutputStream(file));
    290         }
    291     }
    292 }
    293