• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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