react-native-fast-image 8.17.2 → 8.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +28 -0
- package/android/src/main/java/com/dylanvann/fastimage/FastImageOkHttpProgressGlideModule.java +42 -1
- package/android/src/main/java/com/dylanvann/fastimage/FastImageViewConverter.java +1 -1
- package/android/src/main/java/com/dylanvann/fastimage/FastImageViewModule.java +159 -0
- package/dist/index.cjs.js +1 -0
- package/dist/index.cjs.js.flow +5 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.js +1 -0
- package/dist/index.js.flow +5 -0
- package/ios/FastImage/FFFastImageViewManager.m +98 -19
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -371,6 +371,34 @@ Clear all images from memory cache.
|
|
|
371
371
|
|
|
372
372
|
Clear all images from disk cache.
|
|
373
373
|
|
|
374
|
+
### `FastImage.getCachePath: (source: Source) => Promise<CachePathResult>`
|
|
375
|
+
|
|
376
|
+
The path of the source's downloaded file in the disk cache, e.g. to share, save or upload an image without downloading it again. If the file isn't there, it's downloaded first, without decoding the image or keeping it in memory. Resolves with `{ ok: true, path }`, or `{ ok: false, error }` if it can't be downloaded. Never rejects.
|
|
377
|
+
|
|
378
|
+
```js
|
|
379
|
+
const result = await FastImage.getCachePath({ uri: photo.url })
|
|
380
|
+
if (result.ok) {
|
|
381
|
+
await shareFile(`file://${result.path}`) // e.g. with react-native-share
|
|
382
|
+
}
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
With `cache: 'cacheOnly'` it doesn't download: use it to check whether an image is cached.
|
|
386
|
+
|
|
387
|
+
```js
|
|
388
|
+
const { ok } = await FastImage.getCachePath({
|
|
389
|
+
uri: photo.url,
|
|
390
|
+
cache: FastImage.cacheControl.cacheOnly,
|
|
391
|
+
})
|
|
392
|
+
```
|
|
393
|
+
|
|
394
|
+
- The source's `headers`, `cacheKey` and `priority` apply, as for a view. Downloads wait in the same queue as `preload`'s, so they don't hold up the images on screen.
|
|
395
|
+
- The file belongs to the cache, which can remove it at any time: copy it to keep it.
|
|
396
|
+
- Its name may not have an image extension (on Android it ends in `.0` or `.1`): copy it with one for APIs that need it.
|
|
397
|
+
- A `file://` source is its own path. Other local images (`require()` in a release build, `content://`) have no file to give.
|
|
398
|
+
- With `cache: 'web'`, Android only keeps the file if the server allows caching it (in an HTTP cache), and returns `ok: false` otherwise. If the server compressed the image with `Content-Encoding` (rare for images), that file is compressed.
|
|
399
|
+
|
|
400
|
+
There's no way to remove a single image from the cache. To load an image again after it changed on the server, change its [`cacheKey`](#sourcecachekey-string).
|
|
401
|
+
|
|
374
402
|
## Troubleshooting
|
|
375
403
|
|
|
376
404
|
If you have any problems using this library try the steps in [troubleshooting](docs/troubleshooting.md) and see if they fix it.
|
package/android/src/main/java/com/dylanvann/fastimage/FastImageOkHttpProgressGlideModule.java
CHANGED
|
@@ -29,6 +29,7 @@ import java.util.Map;
|
|
|
29
29
|
import java.util.WeakHashMap;
|
|
30
30
|
|
|
31
31
|
import okhttp3.Cache;
|
|
32
|
+
import okhttp3.HttpUrl;
|
|
32
33
|
import okhttp3.Interceptor;
|
|
33
34
|
import okhttp3.JavaNetCookieJar;
|
|
34
35
|
import okhttp3.MediaType;
|
|
@@ -50,6 +51,9 @@ public class FastImageOkHttpProgressGlideModule extends LibraryGlideModule {
|
|
|
50
51
|
// The HTTP cache of `cache: 'web'` images, once Glide has set up.
|
|
51
52
|
@Nullable
|
|
52
53
|
private static Cache webCache;
|
|
54
|
+
// The client that loads them, with that cache.
|
|
55
|
+
@Nullable
|
|
56
|
+
private static OkHttpClient webClient;
|
|
53
57
|
|
|
54
58
|
// Empties the HTTP cache of `cache: 'web'` images (clearDiskCache).
|
|
55
59
|
static void clearWebCache() throws IOException {
|
|
@@ -57,6 +61,43 @@ public class FastImageOkHttpProgressGlideModule extends LibraryGlideModule {
|
|
|
57
61
|
if (cache != null) cache.evictAll();
|
|
58
62
|
}
|
|
59
63
|
|
|
64
|
+
// Downloads a `web` image (with its headers) through the client with the
|
|
65
|
+
// HTTP cache, which stores it if the server allows it (getCachePath).
|
|
66
|
+
// Blocking. Glide must be set up.
|
|
67
|
+
static void downloadToWebCache(GlideUrl url) throws IOException {
|
|
68
|
+
OkHttpClient client = webClient;
|
|
69
|
+
if (client == null) throw new IOException("Glide isn't set up");
|
|
70
|
+
Request.Builder request = new Request.Builder().url(url.toStringUrl());
|
|
71
|
+
for (Map.Entry<String, String> header : url.getHeaders().entrySet()) {
|
|
72
|
+
request.addHeader(header.getKey(), header.getValue());
|
|
73
|
+
}
|
|
74
|
+
try (Response response = client.newCall(request.build()).execute()) {
|
|
75
|
+
if (!response.isSuccessful()) {
|
|
76
|
+
// As Glide's HttpException reports it.
|
|
77
|
+
throw new IOException(response.message() + ", status code: " + response.code());
|
|
78
|
+
}
|
|
79
|
+
ResponseBody body = response.body();
|
|
80
|
+
if (body == null) return;
|
|
81
|
+
// Read to the end, so the cache stores it.
|
|
82
|
+
BufferedSource source = body.source();
|
|
83
|
+
Buffer buffer = new Buffer();
|
|
84
|
+
while (source.read(buffer, 8192) != -1) buffer.clear();
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// The file with the body of url's response in the HTTP cache of
|
|
89
|
+
// `cache: 'web'` images (getCachePath), or null. OkHttp has no API for it:
|
|
90
|
+
// its cache stores each response as `<Cache.key(url)>.0` (headers) and
|
|
91
|
+
// `.1` (body, as the server sent it) once it's complete.
|
|
92
|
+
@Nullable
|
|
93
|
+
static File webCacheFile(String url) {
|
|
94
|
+
Cache cache = webCache;
|
|
95
|
+
HttpUrl httpUrl = HttpUrl.parse(url);
|
|
96
|
+
if (cache == null || httpUrl == null) return null;
|
|
97
|
+
File file = new File(cache.directory(), Cache.key(httpUrl) + ".1");
|
|
98
|
+
return file.isFile() ? file : null;
|
|
99
|
+
}
|
|
100
|
+
|
|
60
101
|
@Override
|
|
61
102
|
public void registerComponents(
|
|
62
103
|
@NonNull Context context,
|
|
@@ -87,7 +128,7 @@ public class FastImageOkHttpProgressGlideModule extends LibraryGlideModule {
|
|
|
87
128
|
// Other urls are cached by Glide, so they don't use it (that would
|
|
88
129
|
// store them twice).
|
|
89
130
|
webCache = new Cache(new File(context.getCacheDir(), "fast-image-http-cache"), WEB_CACHE_SIZE);
|
|
90
|
-
|
|
131
|
+
webClient = client.newBuilder().cache(webCache).build();
|
|
91
132
|
registry.prepend(FastImageWebGlideUrl.class, InputStream.class, new WebUrlLoaderFactory(webClient));
|
|
92
133
|
}
|
|
93
134
|
|
|
@@ -138,7 +138,7 @@ class FastImageViewConverter {
|
|
|
138
138
|
return options;
|
|
139
139
|
}
|
|
140
140
|
|
|
141
|
-
|
|
141
|
+
static FastImageCacheControl getCacheControl(ReadableMap source) {
|
|
142
142
|
return getValueFromSource("cache", "immutable", FAST_IMAGE_CACHE_CONTROL_MAP, source);
|
|
143
143
|
}
|
|
144
144
|
|
|
@@ -256,6 +256,165 @@ class FastImageViewModule extends ReactContextBaseJavaModule {
|
|
|
256
256
|
return result;
|
|
257
257
|
}
|
|
258
258
|
|
|
259
|
+
// Where getCachePath looks in the caches and downloads `web` images
|
|
260
|
+
// (blocking calls, off the UI and modules threads).
|
|
261
|
+
private static final Executor cachePathExecutor = Executors.newSingleThreadExecutor();
|
|
262
|
+
|
|
263
|
+
private static WritableMap pathResult(File file) {
|
|
264
|
+
WritableMap result = Arguments.createMap();
|
|
265
|
+
result.putBoolean("ok", true);
|
|
266
|
+
result.putString("path", file.getAbsolutePath());
|
|
267
|
+
return result;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// The source's downloaded file in the disk cache, downloading it first if
|
|
271
|
+
// it isn't there (without decoding it or keeping it in memory, and in the
|
|
272
|
+
// preloads' queue): { ok, path } or { ok: false, error }. Never rejects.
|
|
273
|
+
// With `cacheOnly` it doesn't download. A local file is its own path.
|
|
274
|
+
@ReactMethod
|
|
275
|
+
public void getCachePath(final ReadableMap source, final Promise promise) {
|
|
276
|
+
final ReactApplicationContext context = getReactApplicationContext();
|
|
277
|
+
if (!FastImageViewConverter.hasUri(source)) {
|
|
278
|
+
promise.resolve(failure("Invalid source: no uri"));
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
final FastImageSource imageSource = FastImageViewConverter.getImageSource(context, source);
|
|
282
|
+
if (imageSource.getUri().toString().isEmpty()) {
|
|
283
|
+
promise.resolve(failure("Invalid source: can't resolve " + source.getString("uri")));
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
if ("file".equalsIgnoreCase(imageSource.getUri().getScheme())) {
|
|
287
|
+
File file = new File(imageSource.getUri().getPath());
|
|
288
|
+
promise.resolve(file.isFile() ? pathResult(file) : failure("No file at this uri"));
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
if (!imageSource.isRemote()) {
|
|
292
|
+
// content://, a resource or a data uri: there's no file to give.
|
|
293
|
+
promise.resolve(failure("Not a remote image"));
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
final boolean cacheOnly = FastImageViewConverter.getCacheControl(source) == FastImageCacheControl.CACHE_ONLY;
|
|
297
|
+
final RequestOptions options = FastImageViewConverter.getOptions(context, imageSource, source);
|
|
298
|
+
final RequestOptions preloadOptions = source.hasKey("priority") && !source.isNull("priority")
|
|
299
|
+
? options
|
|
300
|
+
: options.priority(Priority.LOW);
|
|
301
|
+
final String url = imageSource.getUri().toString();
|
|
302
|
+
cachePathExecutor.execute(new Runnable() {
|
|
303
|
+
@Override
|
|
304
|
+
public void run() {
|
|
305
|
+
// On disk already: in Glide's cache (not for `web` images,
|
|
306
|
+
// which it doesn't cache), or in the HTTP cache of `web`
|
|
307
|
+
// images (set up by Glide, which is started first).
|
|
308
|
+
File file = null;
|
|
309
|
+
Glide.get(context);
|
|
310
|
+
if (!imageSource.isWebCache()) {
|
|
311
|
+
try {
|
|
312
|
+
file = Glide.with(context)
|
|
313
|
+
.asFile()
|
|
314
|
+
.load(imageSource.getSourceForLoad())
|
|
315
|
+
.apply(options)
|
|
316
|
+
.onlyRetrieveFromCache(true)
|
|
317
|
+
.submit()
|
|
318
|
+
.get();
|
|
319
|
+
} catch (Exception e) {
|
|
320
|
+
// Not in Glide's cache.
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
if (file == null) file = FastImageOkHttpProgressGlideModule.webCacheFile(url);
|
|
324
|
+
if (file != null) {
|
|
325
|
+
promise.resolve(pathResult(file));
|
|
326
|
+
return;
|
|
327
|
+
}
|
|
328
|
+
if (cacheOnly) {
|
|
329
|
+
promise.resolve(failure("Not in the disk cache"));
|
|
330
|
+
return;
|
|
331
|
+
}
|
|
332
|
+
UiThreadUtil.runOnUiThread(new Runnable() {
|
|
333
|
+
@Override
|
|
334
|
+
public void run() {
|
|
335
|
+
pendingPreloads.add(imageSource.isWebCache()
|
|
336
|
+
? downloadToWebCache(imageSource, url, promise)
|
|
337
|
+
: downloadToDiskCache(context, imageSource, preloadOptions, promise));
|
|
338
|
+
startPendingPreloads();
|
|
339
|
+
}
|
|
340
|
+
});
|
|
341
|
+
}
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
// Frees a preload slot (on the UI thread) and resolves.
|
|
346
|
+
private static void finishDownload(final Promise promise, final WritableMap result) {
|
|
347
|
+
UiThreadUtil.runOnUiThread(new Runnable() {
|
|
348
|
+
@Override
|
|
349
|
+
public void run() {
|
|
350
|
+
preloadsInFlight--;
|
|
351
|
+
startPendingPreloads();
|
|
352
|
+
promise.resolve(result);
|
|
353
|
+
}
|
|
354
|
+
});
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// Downloads the image into Glide's disk cache (as a disk-only preload).
|
|
358
|
+
private static Runnable downloadToDiskCache(
|
|
359
|
+
final Context context,
|
|
360
|
+
final FastImageSource imageSource,
|
|
361
|
+
final RequestOptions options,
|
|
362
|
+
final Promise promise) {
|
|
363
|
+
return new Runnable() {
|
|
364
|
+
@Override
|
|
365
|
+
public void run() {
|
|
366
|
+
Glide
|
|
367
|
+
.with(context)
|
|
368
|
+
.asFile()
|
|
369
|
+
.load(imageSource.getSourceForLoad())
|
|
370
|
+
.apply(options)
|
|
371
|
+
.listener(new RequestListener<File>() {
|
|
372
|
+
@Override
|
|
373
|
+
public boolean onLoadFailed(@Nullable GlideException e, Object model, Target<File> target, boolean isFirstResource) {
|
|
374
|
+
finishDownload(promise, failure(FastImageRequestListener.errorMessage(e)));
|
|
375
|
+
return false;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
@Override
|
|
379
|
+
public boolean onResourceReady(File file, Object model, Target<File> target, DataSource dataSource, boolean isFirstResource) {
|
|
380
|
+
finishDownload(promise, pathResult(file));
|
|
381
|
+
return false;
|
|
382
|
+
}
|
|
383
|
+
})
|
|
384
|
+
.preload();
|
|
385
|
+
}
|
|
386
|
+
};
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
// Downloads a `web` image into its HTTP cache, which keeps it only if the
|
|
390
|
+
// server allows caching it.
|
|
391
|
+
private static Runnable downloadToWebCache(
|
|
392
|
+
final FastImageSource imageSource,
|
|
393
|
+
final String url,
|
|
394
|
+
final Promise promise) {
|
|
395
|
+
return new Runnable() {
|
|
396
|
+
@Override
|
|
397
|
+
public void run() {
|
|
398
|
+
cachePathExecutor.execute(new Runnable() {
|
|
399
|
+
@Override
|
|
400
|
+
public void run() {
|
|
401
|
+
WritableMap result;
|
|
402
|
+
try {
|
|
403
|
+
FastImageOkHttpProgressGlideModule.downloadToWebCache(imageSource.getGlideUrl());
|
|
404
|
+
File file = FastImageOkHttpProgressGlideModule.webCacheFile(url);
|
|
405
|
+
result = file != null
|
|
406
|
+
? pathResult(file)
|
|
407
|
+
: failure("Not stored in the disk cache (the server doesn't allow caching it)");
|
|
408
|
+
} catch (IOException e) {
|
|
409
|
+
result = failure(e.getMessage() != null ? e.getMessage() : e.toString());
|
|
410
|
+
}
|
|
411
|
+
finishDownload(promise, result);
|
|
412
|
+
}
|
|
413
|
+
});
|
|
414
|
+
}
|
|
415
|
+
};
|
|
416
|
+
}
|
|
417
|
+
|
|
259
418
|
@ReactMethod
|
|
260
419
|
public void clearMemoryCache(final Promise promise) {
|
|
261
420
|
final Activity activity = getCurrentActivity();
|
package/dist/index.cjs.js
CHANGED
|
@@ -195,6 +195,7 @@ FastImage.preload = (sources) => Promise.resolve(react_native.NativeModules.Fast
|
|
|
195
195
|
}));
|
|
196
196
|
FastImage.clearMemoryCache = () => react_native.NativeModules.FastImageView.clearMemoryCache();
|
|
197
197
|
FastImage.clearDiskCache = () => react_native.NativeModules.FastImageView.clearDiskCache();
|
|
198
|
+
FastImage.getCachePath = (source) => Promise.resolve(react_native.NativeModules.FastImageView.getCachePath(source || {}));
|
|
198
199
|
const styles = react_native.StyleSheet.create({
|
|
199
200
|
fallbackImage: {
|
|
200
201
|
position: "absolute",
|
package/dist/index.cjs.js.flow
CHANGED
|
@@ -53,6 +53,10 @@ export type LoadResult =
|
|
|
53
53
|
| {| ok: true, width: number, height: number |}
|
|
54
54
|
| {| ok: false, error: string |}
|
|
55
55
|
|
|
56
|
+
export type CachePathResult =
|
|
57
|
+
| {| ok: true, path: string |}
|
|
58
|
+
| {| ok: false, error: string |}
|
|
59
|
+
|
|
56
60
|
export type PreloadSuccess = {|
|
|
57
61
|
uri: string,
|
|
58
62
|
ok: true,
|
|
@@ -118,4 +122,5 @@ declare export default class FastImage extends React$Component<FastImageProps> {
|
|
|
118
122
|
static preload: PreloadFn;
|
|
119
123
|
static clearMemoryCache: () => Promise<void>;
|
|
120
124
|
static clearDiskCache: () => Promise<void>;
|
|
125
|
+
static getCachePath: (source: FastImageSource) => Promise<CachePathResult>;
|
|
121
126
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -62,6 +62,13 @@ export type LoadResult = {
|
|
|
62
62
|
ok: false;
|
|
63
63
|
error: string;
|
|
64
64
|
};
|
|
65
|
+
export type CachePathResult = {
|
|
66
|
+
ok: true;
|
|
67
|
+
path: string;
|
|
68
|
+
} | {
|
|
69
|
+
ok: false;
|
|
70
|
+
error: string;
|
|
71
|
+
};
|
|
65
72
|
export interface OnProgressEvent {
|
|
66
73
|
nativeEvent: {
|
|
67
74
|
loaded: number;
|
|
@@ -179,6 +186,12 @@ export interface FastImageStaticProperties {
|
|
|
179
186
|
preload: (sources: Source[]) => Promise<PreloadResult[]>;
|
|
180
187
|
clearMemoryCache: () => Promise<void>;
|
|
181
188
|
clearDiskCache: () => Promise<void>;
|
|
189
|
+
/**
|
|
190
|
+
* The path of the source's downloaded file in the disk cache, downloading
|
|
191
|
+
* it first if it isn't there (without decoding it). With
|
|
192
|
+
* `cache: 'cacheOnly'` it doesn't download. Never rejects.
|
|
193
|
+
*/
|
|
194
|
+
getCachePath: (source: Source) => Promise<CachePathResult>;
|
|
182
195
|
}
|
|
183
196
|
declare const FastImage: React.ComponentType<FastImageProps> & FastImageStaticProperties;
|
|
184
197
|
export interface FastImageBackgroundProps extends Omit<FastImageProps, 'style' | 'children'> {
|
package/dist/index.js
CHANGED
|
@@ -168,6 +168,7 @@ FastImage.preload = (sources) => Promise.resolve(NativeModules.FastImageView.pre
|
|
|
168
168
|
}));
|
|
169
169
|
FastImage.clearMemoryCache = () => NativeModules.FastImageView.clearMemoryCache();
|
|
170
170
|
FastImage.clearDiskCache = () => NativeModules.FastImageView.clearDiskCache();
|
|
171
|
+
FastImage.getCachePath = (source) => Promise.resolve(NativeModules.FastImageView.getCachePath(source || {}));
|
|
171
172
|
const styles = StyleSheet.create({
|
|
172
173
|
fallbackImage: {
|
|
173
174
|
position: "absolute",
|
package/dist/index.js.flow
CHANGED
|
@@ -53,6 +53,10 @@ export type LoadResult =
|
|
|
53
53
|
| {| ok: true, width: number, height: number |}
|
|
54
54
|
| {| ok: false, error: string |}
|
|
55
55
|
|
|
56
|
+
export type CachePathResult =
|
|
57
|
+
| {| ok: true, path: string |}
|
|
58
|
+
| {| ok: false, error: string |}
|
|
59
|
+
|
|
56
60
|
export type PreloadSuccess = {|
|
|
57
61
|
uri: string,
|
|
58
62
|
ok: true,
|
|
@@ -118,4 +122,5 @@ declare export default class FastImage extends React$Component<FastImageProps> {
|
|
|
118
122
|
static preload: PreloadFn;
|
|
119
123
|
static clearMemoryCache: () => Promise<void>;
|
|
120
124
|
static clearDiskCache: () => Promise<void>;
|
|
125
|
+
static getCachePath: (source: FastImageSource) => Promise<CachePathResult>;
|
|
121
126
|
}
|
|
@@ -47,6 +47,27 @@ static SDWebImageMutableContext *FFFPreloadContext(FFFastImageSource *source)
|
|
|
47
47
|
return context;
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
+
// A preload's options: the prefetcher's (low priority), with the source's own
|
|
51
|
+
// priority instead if it has one (as on Android), and its `cache` (#406). One
|
|
52
|
+
// source at a time, not SDWebImagePrefetcher, to get each source's result and
|
|
53
|
+
// to send its headers with its own request only. Failed urls are tried again,
|
|
54
|
+
// as views do (SDWebImage otherwise fails a url that failed before, e.g. with
|
|
55
|
+
// data that isn't an image, without a request until the app is relaunched,
|
|
56
|
+
// #394).
|
|
57
|
+
static SDWebImageOptions FFFPreloadOptions(FFFastImageSource *source)
|
|
58
|
+
{
|
|
59
|
+
SDWebImageOptions options = [SDWebImagePrefetcher sharedImagePrefetcher].options | SDWebImageRetryFailed;
|
|
60
|
+
if (source.hasPriority) {
|
|
61
|
+
options &= ~(SDWebImageLowPriority | SDWebImageHighPriority);
|
|
62
|
+
if (source.priority == FFFPriorityLow) {
|
|
63
|
+
options |= SDWebImageLowPriority;
|
|
64
|
+
} else if (source.priority == FFFPriorityHigh) {
|
|
65
|
+
options |= SDWebImageHighPriority;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return options | [source cacheOptions];
|
|
69
|
+
}
|
|
70
|
+
|
|
50
71
|
static NSUInteger FFFPreloadLimit(void)
|
|
51
72
|
{
|
|
52
73
|
NSUInteger limit = [SDWebImagePrefetcher sharedImagePrefetcher].maxConcurrentPrefetchCount;
|
|
@@ -91,11 +112,6 @@ RCT_EXPORT_METHOD(preload:(nonnull NSArray<FFFastImageSource *> *)sources
|
|
|
91
112
|
resolve(results);
|
|
92
113
|
}
|
|
93
114
|
};
|
|
94
|
-
// With the prefetcher's options (low priority), but one source at a
|
|
95
|
-
// time (not SDWebImagePrefetcher), to get each source's result, to
|
|
96
|
-
// send its headers with its own request only, and to follow its
|
|
97
|
-
// `cache` (#406).
|
|
98
|
-
SDWebImageOptions prefetcherOptions = [SDWebImagePrefetcher sharedImagePrefetcher].options;
|
|
99
115
|
|
|
100
116
|
[sources enumerateObjectsUsingBlock:^(FFFastImageSource * _Nonnull source, NSUInteger idx, BOOL * _Nonnull stop) {
|
|
101
117
|
if (!source.url) {
|
|
@@ -106,20 +122,7 @@ RCT_EXPORT_METHOD(preload:(nonnull NSArray<FFFastImageSource *> *)sources
|
|
|
106
122
|
return;
|
|
107
123
|
}
|
|
108
124
|
[results addObject:[NSNull null]];
|
|
109
|
-
|
|
110
|
-
// Failed urls are tried again, as views do (SDWebImage otherwise
|
|
111
|
-
// fails a url that failed before, e.g. with data that isn't an
|
|
112
|
-
// image, without a request until the app is relaunched, #394).
|
|
113
|
-
SDWebImageOptions options = prefetcherOptions | SDWebImageRetryFailed;
|
|
114
|
-
if (source.hasPriority) {
|
|
115
|
-
options &= ~(SDWebImageLowPriority | SDWebImageHighPriority);
|
|
116
|
-
if (source.priority == FFFPriorityLow) {
|
|
117
|
-
options |= SDWebImageLowPriority;
|
|
118
|
-
} else if (source.priority == FFFPriorityHigh) {
|
|
119
|
-
options |= SDWebImageHighPriority;
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
options |= [source cacheOptions];
|
|
125
|
+
SDWebImageOptions options = FFFPreloadOptions(source);
|
|
123
126
|
SDWebImageMutableContext *context = FFFPreloadContext(source);
|
|
124
127
|
if (!source.memoryCache) {
|
|
125
128
|
// Not decoded (only its header is read, for the size), and not
|
|
@@ -161,6 +164,82 @@ RCT_EXPORT_METHOD(preload:(nonnull NSArray<FFFastImageSource *> *)sources
|
|
|
161
164
|
});
|
|
162
165
|
}
|
|
163
166
|
|
|
167
|
+
// The source's downloaded file in the disk cache, downloading it first if it
|
|
168
|
+
// isn't there (without decoding it or keeping it in memory, and in the
|
|
169
|
+
// preloads' queue): { ok, path } or { ok: false, error }. Never rejects. With
|
|
170
|
+
// `cacheOnly` it doesn't download. A local file is its own path.
|
|
171
|
+
RCT_EXPORT_METHOD(getCachePath:(FFFastImageSource *)source
|
|
172
|
+
resolve:(RCTPromiseResolveBlock)resolve
|
|
173
|
+
reject:(__unused RCTPromiseRejectBlock)reject)
|
|
174
|
+
{
|
|
175
|
+
if (!source.url) {
|
|
176
|
+
resolve(@{@"ok": @NO, @"error": @"Invalid source: no uri"});
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
if (source.url.isFileURL) {
|
|
180
|
+
BOOL exists = [NSFileManager.defaultManager fileExistsAtPath:source.url.path];
|
|
181
|
+
resolve(exists
|
|
182
|
+
? @{@"ok": @YES, @"path": source.url.path}
|
|
183
|
+
: @{@"ok": @NO, @"error": @"No file at this uri"});
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
SDWebImageMutableContext *context = FFFPreloadContext(source);
|
|
187
|
+
// The key SDWebImage stores it under (the cacheKey, or the converted url).
|
|
188
|
+
NSString *key = [SDWebImageManager.sharedManager cacheKeyForURL:source.url context:context];
|
|
189
|
+
SDImageCache *cache = SDImageCache.sharedImageCache;
|
|
190
|
+
void (^resolveFromDisk)(NSString *) = ^(NSString *error) {
|
|
191
|
+
[cache diskImageExistsWithKey:key completion:^(BOOL exists) {
|
|
192
|
+
resolve(exists
|
|
193
|
+
? @{@"ok": @YES, @"path": [cache cachePathForKey:key]}
|
|
194
|
+
: @{@"ok": @NO, @"error": error});
|
|
195
|
+
}];
|
|
196
|
+
};
|
|
197
|
+
[cache diskImageExistsWithKey:key completion:^(BOOL exists) {
|
|
198
|
+
// On the main queue, where the preloads' queue is used.
|
|
199
|
+
if (exists) {
|
|
200
|
+
resolve(@{@"ok": @YES, @"path": [cache cachePathForKey:key]});
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
if (source.cacheControl == FFFCacheControlCacheOnly) {
|
|
204
|
+
resolve(@{@"ok": @NO, @"error": @"Not in the disk cache"});
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
if (!FFFPendingPreloads) {
|
|
208
|
+
FFFPendingPreloads = [NSMutableArray array];
|
|
209
|
+
}
|
|
210
|
+
// As a disk-only preload: not decoded, and not looked up in or kept in
|
|
211
|
+
// memory (an image in memory may not be on disk). It resolves once the
|
|
212
|
+
// file is written.
|
|
213
|
+
SDWebImageOptions options = FFFPreloadOptions(source) | SDWebImageAvoidDecodeImage | SDWebImageWaitStoreCache;
|
|
214
|
+
context[SDWebImageContextQueryCacheType] = @(SDImageCacheTypeDisk);
|
|
215
|
+
context[SDWebImageContextStoreCacheType] = @(SDImageCacheTypeDisk);
|
|
216
|
+
[FFFPendingPreloads addObject:[^{
|
|
217
|
+
__block BOOL done = NO;
|
|
218
|
+
[[SDWebImageManager sharedManager] loadImageWithURL:source.url
|
|
219
|
+
options:options
|
|
220
|
+
context:[context copy]
|
|
221
|
+
progress:nil
|
|
222
|
+
completed:^(UIImage *image, NSData *data, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
|
|
223
|
+
if (!finished || done) {
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
done = YES;
|
|
227
|
+
FFFPreloadsInFlight--;
|
|
228
|
+
FFFStartPendingPreloads();
|
|
229
|
+
if (error) {
|
|
230
|
+
[source forgetResponseAfterError:error];
|
|
231
|
+
}
|
|
232
|
+
if (!image) {
|
|
233
|
+
resolve(@{@"ok": @NO, @"error": FFFErrorMessage(error)});
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
resolveFromDisk(@"Not stored in the disk cache");
|
|
237
|
+
}];
|
|
238
|
+
} copy]];
|
|
239
|
+
FFFStartPendingPreloads();
|
|
240
|
+
}];
|
|
241
|
+
}
|
|
242
|
+
|
|
164
243
|
RCT_EXPORT_METHOD(clearMemoryCache:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject)
|
|
165
244
|
{
|
|
166
245
|
[SDImageCache.sharedImageCache clearMemory];
|