react-native-fast-image 9.1.0 → 9.2.1

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 CHANGED
@@ -387,7 +387,7 @@ onLoad={e => console.log(e.nativeEvent.width, e.nativeEvent.height)}
387
387
 
388
388
  **Type:** `(event: OnErrorEvent) => void`
389
389
 
390
- Called on an image fetching error, with a message describing it (e.g. the HTTP status code).
390
+ Called on an image fetching error, with a message describing it (e.g. the HTTP status code). On iOS and Android, a download that gets nothing from the server for 15 seconds fails too (on Android, unless the app's OkHttp client has timeouts of its own).
391
391
 
392
392
  ```jsx
393
393
  onError={e => console.log(e.nativeEvent.error)}
@@ -571,7 +571,7 @@ const { ok } = await FastImage.getCachePath({
571
571
  })
572
572
  ```
573
573
 
574
- - 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.
574
+ - 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. If a view or a preload is downloading the same source, `getCachePath` shares that download instead of downloading the image again (on Android, except with `cache: 'web'`).
575
575
  - The file belongs to the cache, which can remove it at any time: copy it to keep it.
576
576
  - 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.
577
577
  - A `file://` source is its own path. Other local images (`require()` in a release build, `content://`) have no file to give.
@@ -765,7 +765,7 @@ How the image fades in: see [`transition`](#transition).
765
765
  - `'all'`: skips it for images from the memory or disk cache too, so images that download fade in, and local files and bundled images usually only the first time (the disk cache usually keeps them too).
766
766
  - `'none'`: always fades.
767
767
 
768
- Downloads, local files and bundled images (`require()`) fade in.
768
+ Downloads, local files and bundled images (`require()`) fade in. On Android, a `cache: 'web'` image from its HTTP cache, without a request, counts as one from the memory cache (these images aren't kept there).
769
769
 
770
770
  <!-- api:types end -->
771
771
 
@@ -109,3 +109,15 @@ dependencies {
109
109
  // Animated PNGs (see FastImageApng).
110
110
  implementation 'com.github.penfeizhou.android.animation:apng:3.0.5'
111
111
  }
112
+
113
+ // Unit tests of the downloads (src/test, with Robolectric, which gives them
114
+ // Android's classes), run from an example app's android folder:
115
+ // `./gradlew :react-native-fast-image:testDebugUnitTest`. The npm package
116
+ // doesn't include src/test, so apps don't get these.
117
+ if (file('src/test').exists()) {
118
+ android.testOptions.unitTests.includeAndroidResources = true
119
+ dependencies {
120
+ testImplementation 'junit:junit:4.13.2'
121
+ testImplementation 'org.robolectric:robolectric:4.15.1'
122
+ }
123
+ }
@@ -10,7 +10,6 @@ import com.bumptech.glide.annotation.GlideModule;
10
10
  import com.bumptech.glide.load.ImageHeaderParser;
11
11
  import com.bumptech.glide.load.ImageHeaderParserUtils;
12
12
  import com.bumptech.glide.load.engine.bitmap_recycle.ArrayPool;
13
- import com.bumptech.glide.integration.okhttp3.OkHttpUrlLoader;
14
13
  import com.bumptech.glide.load.Options;
15
14
  import com.bumptech.glide.load.model.GlideUrl;
16
15
  import com.bumptech.glide.load.model.ModelLoader;
@@ -23,10 +22,8 @@ import com.facebook.react.modules.network.OkHttpClientProvider;
23
22
  import java.io.File;
24
23
  import java.io.IOException;
25
24
  import java.io.InputStream;
26
- import java.util.HashMap;
27
25
  import java.util.Locale;
28
- import java.util.Map;
29
- import java.util.WeakHashMap;
26
+ import java.util.concurrent.TimeUnit;
30
27
 
31
28
  import okhttp3.Cache;
32
29
  import okhttp3.HttpUrl;
@@ -34,20 +31,18 @@ import okhttp3.Interceptor;
34
31
  import okhttp3.JavaNetCookieJar;
35
32
  import okhttp3.MediaType;
36
33
  import okhttp3.OkHttpClient;
37
- import okhttp3.Request;
38
34
  import okhttp3.Response;
39
35
  import okhttp3.ResponseBody;
40
36
  import okio.Buffer;
41
37
  import okio.BufferedSource;
42
- import okio.ForwardingSource;
43
- import okio.Okio;
44
- import okio.Source;
45
38
 
46
39
  @GlideModule
47
40
  public class FastImageOkHttpProgressGlideModule extends LibraryGlideModule {
48
41
 
49
- private static final DispatchingProgressListener progressListener = new DispatchingProgressListener();
50
42
  private static final long WEB_CACHE_SIZE = 50 * 1024 * 1024;
43
+ // How long a download can get nothing from the server (see
44
+ // registerComponents).
45
+ private static final long TIMEOUT_SECONDS = 15;
51
46
  // The HTTP cache of `cache: 'web'` images, once Glide has set up.
52
47
  @Nullable
53
48
  private static Cache webCache;
@@ -67,11 +62,7 @@ public class FastImageOkHttpProgressGlideModule extends LibraryGlideModule {
67
62
  static void downloadToWebCache(GlideUrl url) throws IOException {
68
63
  OkHttpClient client = webClient;
69
64
  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()) {
65
+ try (Response response = client.newCall(FastImageSharedDownloads.request(url)).execute()) {
75
66
  if (!response.isSuccessful()) {
76
67
  // As Glide's HttpException reports it.
77
68
  throw new IOException(response.message() + ", status code: " + response.code());
@@ -107,7 +98,6 @@ public class FastImageOkHttpProgressGlideModule extends LibraryGlideModule {
107
98
  OkHttpClient sharedClient = OkHttpClientProvider.getOkHttpClient();
108
99
  OkHttpClient.Builder builder = sharedClient
109
100
  .newBuilder()
110
- .addInterceptor(createInterceptor(progressListener))
111
101
  // A network interceptor, so it runs before the HTTP cache of
112
102
  // `web` images stores the response (checking it reads it).
113
103
  .addNetworkInterceptor(createNonImageInterceptor(registry, glide.getArrayPool()));
@@ -118,6 +108,15 @@ public class FastImageOkHttpProgressGlideModule extends LibraryGlideModule {
118
108
  if (sharedClient.cookieJar() instanceof CookieJarContainer) {
119
109
  builder.cookieJar(new JavaNetCookieJar(new FastImageCookieHandler()));
120
110
  }
111
+ // React Native's shared client has no timeouts, so a download that
112
+ // stopped (e.g. on a connection that died) never ended, and every
113
+ // request sharing it waited. As on iOS (SDWebImage's 15 s), one that
114
+ // gets nothing for TIMEOUT_SECONDS fails: connecting, or waiting for
115
+ // the response or the next of its bytes. Timeouts the app gave the
116
+ // client stay. No call timeout: a large image on a slow link can take
117
+ // longer than any.
118
+ if (sharedClient.connectTimeoutMillis() == 0) builder.connectTimeout(TIMEOUT_SECONDS, TimeUnit.SECONDS);
119
+ if (sharedClient.readTimeoutMillis() == 0) builder.readTimeout(TIMEOUT_SECONDS, TimeUnit.SECONDS);
121
120
  OkHttpClient client = builder.build();
122
121
  // `cache: 'web'` skips Glide's caches and relies on HTTP caching, so
123
122
  // those urls get a client with an HTTP cache (#280): one of their own,
@@ -127,6 +126,7 @@ public class FastImageOkHttpProgressGlideModule extends LibraryGlideModule {
127
126
  // store them twice).
128
127
  webCache = new Cache(new File(context.getCacheDir(), "fast-image-http-cache"), WEB_CACHE_SIZE);
129
128
  webClient = client.newBuilder().cache(webCache).build();
129
+ FastImageSharedDownloads.setDirectory(new File(context.getCacheDir(), "fast-image-downloads"));
130
130
  registry.prepend(FastImageUrl.class, InputStream.class, new UrlLoaderFactory(client, webClient));
131
131
  FastImageSvg.register(registry, glide.getBitmapPool());
132
132
  FastImageAnimated.register(context, glide, registry);
@@ -138,12 +138,12 @@ public class FastImageOkHttpProgressGlideModule extends LibraryGlideModule {
138
138
  FastImageGlide.registered(glide);
139
139
  }
140
140
 
141
- // Loads FastImage's remote images (FastImageUrls): `web` ones
142
- // (FastImageWebGlideUrls) with the client with the HTTP cache, others with
143
- // the other client, where a view loading an image that's being preloaded
144
- // waits for the preload's download. FastImageUrl isn't a GlideUrl, so
145
- // Glide doesn't also give these to other GlideUrl loaders, which it would
146
- // try after a failed load (e.g. a 404, requested again).
141
+ // Loads FastImage's remote images (FastImageUrls), with one download of an
142
+ // image at a time, shared by its requests (FastImageSharedDownloads):
143
+ // `web` ones (FastImageWebGlideUrls) with the client with the HTTP cache,
144
+ // others with the other client. FastImageUrl isn't a GlideUrl, so Glide
145
+ // doesn't also give these to other GlideUrl loaders, which it would try
146
+ // after a failed load (e.g. a 404, requested again).
147
147
  private static class UrlLoaderFactory implements ModelLoaderFactory<FastImageUrl, InputStream> {
148
148
  private final OkHttpClient client;
149
149
  private final OkHttpClient webClient;
@@ -156,16 +156,11 @@ public class FastImageOkHttpProgressGlideModule extends LibraryGlideModule {
156
156
  @NonNull
157
157
  @Override
158
158
  public ModelLoader<FastImageUrl, InputStream> build(@NonNull MultiModelLoaderFactory multiFactory) {
159
- final OkHttpUrlLoader loader = new OkHttpUrlLoader(client);
160
- final OkHttpUrlLoader webLoader = new OkHttpUrlLoader(webClient);
161
159
  return new ModelLoader<FastImageUrl, InputStream>() {
162
160
  @Override
163
161
  public LoadData<InputStream> buildLoadData(@NonNull FastImageUrl model, int width, int height, @NonNull Options options) {
164
- if (model.url instanceof FastImageWebGlideUrl) {
165
- return webLoader.buildLoadData(model.url, width, height, options);
166
- }
167
- LoadData<InputStream> data = loader.buildLoadData(model.url, width, height, options);
168
- return data == null ? null : FastImageSharedDownloads.share(data, model.url, options);
162
+ return FastImageSharedDownloads.loadData(
163
+ model.url, model.url instanceof FastImageWebGlideUrl ? webClient : client);
169
164
  }
170
165
 
171
166
  @Override
@@ -227,132 +222,4 @@ public class FastImageOkHttpProgressGlideModule extends LibraryGlideModule {
227
222
  || subtype.equalsIgnoreCase("xhtml+xml")
228
223
  || subtype.equalsIgnoreCase("javascript");
229
224
  }
230
-
231
- private static Interceptor createInterceptor(final ResponseProgressListener listener) {
232
- return new Interceptor() {
233
- @Override
234
- public Response intercept(Chain chain) throws IOException {
235
- Request request = chain.request();
236
- Response response = chain.proceed(request);
237
- final String key = request.url().toString();
238
- return response
239
- .newBuilder()
240
- .body(new OkHttpProgressResponseBody(key, response.body(), listener))
241
- .build();
242
- }
243
- };
244
- }
245
-
246
- static void forget(String key) {
247
- progressListener.forget(key);
248
- }
249
-
250
- static void expect(String key, FastImageProgressListener listener) {
251
- progressListener.expect(key, listener);
252
- }
253
-
254
- private interface ResponseProgressListener {
255
- void update(String key, long bytesRead, long contentLength);
256
- }
257
-
258
- private static class DispatchingProgressListener implements ResponseProgressListener {
259
- private final Map<String, FastImageProgressListener> LISTENERS = new WeakHashMap<>();
260
- private final Map<String, Long> PROGRESSES = new HashMap<>();
261
-
262
- void forget(String key) {
263
- LISTENERS.remove(key);
264
- PROGRESSES.remove(key);
265
- }
266
-
267
- void expect(String key, FastImageProgressListener listener) {
268
- LISTENERS.put(key, listener);
269
- }
270
-
271
- @Override
272
- public void update(final String key, final long bytesRead, final long contentLength) {
273
- final FastImageProgressListener listener = LISTENERS.get(key);
274
- // Without a Content-Length the total is unknown (-1), and a
275
- // percentage can't be worked out from it, so don't send those. (It
276
- // also looked like the last update, which stopped all updates.)
277
- if (listener == null || contentLength <= 0) {
278
- return;
279
- }
280
- if (contentLength <= bytesRead) {
281
- forget(key);
282
- }
283
- if (needsDispatch(key, bytesRead, contentLength, listener.getGranularityPercentage())) {
284
- listener.onProgress(key, bytesRead, contentLength);
285
- }
286
- }
287
-
288
- private boolean needsDispatch(String key, long current, long total, float granularity) {
289
- if (granularity == 0 || current == 0 || total == current) {
290
- return true;
291
- }
292
- float percent = 100f * current / total;
293
- long currentProgress = (long) (percent / granularity);
294
- Long lastProgress = PROGRESSES.get(key);
295
- if (lastProgress == null || currentProgress != lastProgress) {
296
- PROGRESSES.put(key, currentProgress);
297
- return true;
298
- } else {
299
- return false;
300
- }
301
- }
302
- }
303
-
304
- private static class OkHttpProgressResponseBody extends ResponseBody {
305
- private final String key;
306
- private final ResponseBody responseBody;
307
- private final ResponseProgressListener progressListener;
308
- private BufferedSource bufferedSource;
309
-
310
- OkHttpProgressResponseBody(
311
- String key,
312
- ResponseBody responseBody,
313
- ResponseProgressListener progressListener
314
- ) {
315
- this.key = key;
316
- this.responseBody = responseBody;
317
- this.progressListener = progressListener;
318
- }
319
-
320
- @Override
321
- public MediaType contentType() {
322
- return responseBody.contentType();
323
- }
324
-
325
- @Override
326
- public long contentLength() {
327
- return responseBody.contentLength();
328
- }
329
-
330
- @Override
331
- public BufferedSource source() {
332
- if (bufferedSource == null) {
333
- bufferedSource = Okio.buffer(source(responseBody.source()));
334
- }
335
- return bufferedSource;
336
- }
337
-
338
- private Source source(Source source) {
339
- return new ForwardingSource(source) {
340
- long totalBytesRead = 0L;
341
-
342
- @Override
343
- public long read(Buffer sink, long byteCount) throws IOException {
344
- long bytesRead = super.read(sink, byteCount);
345
- long fullLength = responseBody.contentLength();
346
- if (bytesRead == -1) {
347
- // this source is exhausted
348
- totalBytesRead = fullLength;
349
- } else {
350
- totalBytesRead += bytesRead;
351
- }
352
- progressListener.update(key, totalBytesRead, fullLength);
353
- return bytesRead;
354
- }
355
- };
356
- }
357
- }
358
225
  }
@@ -1,5 +1,9 @@
1
1
  package com.dylanvann.fastimage;
2
2
 
3
+ // No longer used: FastImage sends progress through FastImageViewManager's
4
+ // onDownloadProgress. Kept, as a public type, so code that refers to it still
5
+ // compiles.
6
+ @Deprecated
3
7
  public interface FastImageProgressListener {
4
8
 
5
9
  void onProgress(String key, long bytesRead, long expectedLength);
@@ -16,7 +16,6 @@ public class FastImageRequestListener implements RequestListener<Drawable> {
16
16
  static final String REACT_ON_ERROR_EVENT = "onFastImageError";
17
17
  static final String REACT_ON_LOAD_EVENT = "onFastImageLoad";
18
18
  static final String REACT_ON_LOAD_END_EVENT = "onFastImageLoadEnd";
19
- private final String key;
20
19
  private final FastImageSource source;
21
20
  // Whether the request shows the previous image as a thumbnail meanwhile.
22
21
  private final boolean thumbnail;
@@ -24,8 +23,7 @@ public class FastImageRequestListener implements RequestListener<Drawable> {
24
23
  // view shows it at its old size if that fails.
25
24
  private final boolean events;
26
25
 
27
- FastImageRequestListener(String key, FastImageSource source, boolean thumbnail, boolean events) {
28
- this.key = key;
26
+ FastImageRequestListener(FastImageSource source, boolean thumbnail, boolean events) {
29
27
  this.source = source;
30
28
  this.thumbnail = thumbnail;
31
29
  this.events = events;
@@ -39,7 +37,6 @@ public class FastImageRequestListener implements RequestListener<Drawable> {
39
37
  }
40
38
  return false;
41
39
  }
42
- FastImageOkHttpProgressGlideModule.forget(key);
43
40
  if (!(target instanceof ImageViewTarget)) {
44
41
  return false;
45
42
  }