react-native-fast-image 8.17.0 → 8.17.2
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/android/src/main/java/com/dylanvann/fastimage/FastImageOkHttpProgressGlideModule.java +87 -3
- package/ios/FastImage/FFFastImageSource.h +6 -0
- package/ios/FastImage/FFFastImageSource.m +12 -0
- package/ios/FastImage/FFFastImageView.m +6 -0
- package/ios/FastImage/FFFastImageViewManager.m +7 -1
- package/package.json +1 -1
package/android/src/main/java/com/dylanvann/fastimage/FastImageOkHttpProgressGlideModule.java
CHANGED
|
@@ -7,6 +7,9 @@ import androidx.annotation.Nullable;
|
|
|
7
7
|
import com.bumptech.glide.Glide;
|
|
8
8
|
import com.bumptech.glide.Registry;
|
|
9
9
|
import com.bumptech.glide.annotation.GlideModule;
|
|
10
|
+
import com.bumptech.glide.load.ImageHeaderParser;
|
|
11
|
+
import com.bumptech.glide.load.ImageHeaderParserUtils;
|
|
12
|
+
import com.bumptech.glide.load.engine.bitmap_recycle.ArrayPool;
|
|
10
13
|
import com.bumptech.glide.integration.okhttp3.OkHttpUrlLoader;
|
|
11
14
|
import com.bumptech.glide.load.Options;
|
|
12
15
|
import com.bumptech.glide.load.model.GlideUrl;
|
|
@@ -21,6 +24,7 @@ import java.io.File;
|
|
|
21
24
|
import java.io.IOException;
|
|
22
25
|
import java.io.InputStream;
|
|
23
26
|
import java.util.HashMap;
|
|
27
|
+
import java.util.Locale;
|
|
24
28
|
import java.util.Map;
|
|
25
29
|
import java.util.WeakHashMap;
|
|
26
30
|
|
|
@@ -62,7 +66,10 @@ public class FastImageOkHttpProgressGlideModule extends LibraryGlideModule {
|
|
|
62
66
|
OkHttpClient sharedClient = OkHttpClientProvider.getOkHttpClient();
|
|
63
67
|
OkHttpClient.Builder builder = sharedClient
|
|
64
68
|
.newBuilder()
|
|
65
|
-
.addInterceptor(createInterceptor(progressListener))
|
|
69
|
+
.addInterceptor(createInterceptor(progressListener))
|
|
70
|
+
// A network interceptor, so it runs before the HTTP cache of
|
|
71
|
+
// `web` images stores the response (checking it reads it).
|
|
72
|
+
.addNetworkInterceptor(createNonImageInterceptor(registry, glide.getArrayPool()));
|
|
66
73
|
// React Native's shared client comes with an empty cookie jar (React
|
|
67
74
|
// Native only fills it in for its networking and Image clients), so
|
|
68
75
|
// images were loaded without the app's cookies, unlike on iOS. Use the
|
|
@@ -71,8 +78,7 @@ public class FastImageOkHttpProgressGlideModule extends LibraryGlideModule {
|
|
|
71
78
|
builder.cookieJar(new JavaNetCookieJar(new FastImageCookieHandler(context)));
|
|
72
79
|
}
|
|
73
80
|
OkHttpClient client = builder.build();
|
|
74
|
-
|
|
75
|
-
registry.replace(GlideUrl.class, InputStream.class, factory);
|
|
81
|
+
registry.replace(GlideUrl.class, InputStream.class, new UrlLoaderFactory(client));
|
|
76
82
|
|
|
77
83
|
// `cache: 'web'` skips Glide's caches and relies on HTTP caching, so
|
|
78
84
|
// those urls get a client with an HTTP cache (#280): one of their own,
|
|
@@ -85,6 +91,40 @@ public class FastImageOkHttpProgressGlideModule extends LibraryGlideModule {
|
|
|
85
91
|
registry.prepend(FastImageWebGlideUrl.class, InputStream.class, new WebUrlLoaderFactory(webClient));
|
|
86
92
|
}
|
|
87
93
|
|
|
94
|
+
// Loads GlideUrls with the given client, except `web` ones
|
|
95
|
+
// (FastImageWebGlideUrls): Glide gives a model to the loaders of its
|
|
96
|
+
// superclasses too, and tries the next one when a load fails, so a `web`
|
|
97
|
+
// image that failed (e.g. a 404) was requested again with this client,
|
|
98
|
+
// without the HTTP cache, which also hid the failure.
|
|
99
|
+
private static class UrlLoaderFactory implements ModelLoaderFactory<GlideUrl, InputStream> {
|
|
100
|
+
private final OkHttpClient client;
|
|
101
|
+
|
|
102
|
+
UrlLoaderFactory(OkHttpClient client) {
|
|
103
|
+
this.client = client;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
@NonNull
|
|
107
|
+
@Override
|
|
108
|
+
public ModelLoader<GlideUrl, InputStream> build(@NonNull MultiModelLoaderFactory multiFactory) {
|
|
109
|
+
final OkHttpUrlLoader loader = new OkHttpUrlLoader(client);
|
|
110
|
+
return new ModelLoader<GlideUrl, InputStream>() {
|
|
111
|
+
@Override
|
|
112
|
+
public LoadData<InputStream> buildLoadData(@NonNull GlideUrl model, int width, int height, @NonNull Options options) {
|
|
113
|
+
return loader.buildLoadData(model, width, height, options);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
@Override
|
|
117
|
+
public boolean handles(@NonNull GlideUrl model) {
|
|
118
|
+
return !(model instanceof FastImageWebGlideUrl);
|
|
119
|
+
}
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
@Override
|
|
124
|
+
public void teardown() {
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
88
128
|
// Loads FastImageWebGlideUrls with the given client.
|
|
89
129
|
private static class WebUrlLoaderFactory implements ModelLoaderFactory<FastImageWebGlideUrl, InputStream> {
|
|
90
130
|
private final OkHttpClient client;
|
|
@@ -115,6 +155,50 @@ public class FastImageOkHttpProgressGlideModule extends LibraryGlideModule {
|
|
|
115
155
|
}
|
|
116
156
|
}
|
|
117
157
|
|
|
158
|
+
// Fails a successful response that's clearly not an image: its type isn't
|
|
159
|
+
// an image's (text, JSON, XML other than SVG) and its first bytes aren't a
|
|
160
|
+
// format Glide recognizes, e.g. a captive portal's or a proxy's HTML page
|
|
161
|
+
// sent with status 200. Glide keeps the bytes it downloads in its disk
|
|
162
|
+
// cache before decoding them (and doesn't remove them when that fails), as
|
|
163
|
+
// does the HTTP cache of `web` images, so every later load of the url
|
|
164
|
+
// would fail too. Neither caches a failed response. Images sent with the
|
|
165
|
+
// wrong type still load.
|
|
166
|
+
private static Interceptor createNonImageInterceptor(final Registry registry, final ArrayPool arrayPool) {
|
|
167
|
+
return new Interceptor() {
|
|
168
|
+
@NonNull
|
|
169
|
+
@Override
|
|
170
|
+
public Response intercept(@NonNull Chain chain) throws IOException {
|
|
171
|
+
Response response = chain.proceed(chain.request());
|
|
172
|
+
ResponseBody body = response.body();
|
|
173
|
+
MediaType type = body == null ? null : body.contentType();
|
|
174
|
+
if (!response.isSuccessful() || type == null || !isNotImageType(type)) {
|
|
175
|
+
return response;
|
|
176
|
+
}
|
|
177
|
+
ImageHeaderParser.ImageType imageType;
|
|
178
|
+
try (InputStream stream = response.peekBody(64 * 1024).byteStream()) {
|
|
179
|
+
imageType = ImageHeaderParserUtils.getType(
|
|
180
|
+
registry.getImageHeaderParsers(), stream, arrayPool);
|
|
181
|
+
}
|
|
182
|
+
if (imageType != ImageHeaderParser.ImageType.UNKNOWN) {
|
|
183
|
+
return response;
|
|
184
|
+
}
|
|
185
|
+
response.close();
|
|
186
|
+
throw new IOException("Not an image (Content-Type: " + type + ")");
|
|
187
|
+
}
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
private static boolean isNotImageType(MediaType type) {
|
|
192
|
+
String subtype = type.subtype();
|
|
193
|
+
if ("text".equalsIgnoreCase(type.type())) return true;
|
|
194
|
+
if (!"application".equalsIgnoreCase(type.type())) return false;
|
|
195
|
+
return subtype.equalsIgnoreCase("json")
|
|
196
|
+
|| subtype.toLowerCase(Locale.ROOT).endsWith("+json")
|
|
197
|
+
|| subtype.equalsIgnoreCase("xml")
|
|
198
|
+
|| subtype.equalsIgnoreCase("xhtml+xml")
|
|
199
|
+
|| subtype.equalsIgnoreCase("javascript");
|
|
200
|
+
}
|
|
201
|
+
|
|
118
202
|
private static Interceptor createInterceptor(final ResponseProgressListener listener) {
|
|
119
203
|
return new Interceptor() {
|
|
120
204
|
@Override
|
|
@@ -61,4 +61,10 @@ typedef NS_ENUM(NSInteger, FFFCacheControl) {
|
|
|
61
61
|
// responses. 50 MB, as on Android.
|
|
62
62
|
+ (NSURLCache *)webURLCache;
|
|
63
63
|
|
|
64
|
+
// After a load of this source failed: for a `cache: 'web'` image whose data
|
|
65
|
+
// isn't an image (e.g. a captive portal's HTML page), removes the response
|
|
66
|
+
// from the HTTP cache, which stored it as it downloaded. Otherwise the next
|
|
67
|
+
// loads would get it from there until it expires, and fail again.
|
|
68
|
+
- (void)forgetResponseAfterError:(NSError *)error;
|
|
69
|
+
|
|
64
70
|
@end
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
#import <SDWebImage/SDWebImageDownloader.h>
|
|
3
3
|
#import <SDWebImage/SDWebImageDownloaderResponseModifier.h>
|
|
4
4
|
#import <SDWebImage/SDWebImageDownloaderDecryptor.h>
|
|
5
|
+
#import <SDWebImage/SDWebImageError.h>
|
|
5
6
|
|
|
6
7
|
static NSUInteger const FFFWebCacheSize = 50 * 1024 * 1024;
|
|
7
8
|
|
|
@@ -79,6 +80,17 @@ static NSUInteger const FFFWebCacheSize = 50 * 1024 * 1024;
|
|
|
79
80
|
return downloader;
|
|
80
81
|
}
|
|
81
82
|
|
|
83
|
+
- (void)forgetResponseAfterError:(NSError *)error
|
|
84
|
+
{
|
|
85
|
+
if (_cacheControl != FFFCacheControlWeb || !_url) {
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
if (![error.domain isEqualToString:SDWebImageErrorDomain] || error.code != SDWebImageErrorBadImageData) {
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
[[FFFastImageSource webURLCache] removeCachedResponseForRequest:[NSURLRequest requestWithURL:_url]];
|
|
92
|
+
}
|
|
93
|
+
|
|
82
94
|
+ (NSURLCache *)webURLCache
|
|
83
95
|
{
|
|
84
96
|
static NSURLCache *cache;
|
|
@@ -579,6 +579,9 @@ NSString *FFFErrorMessage(NSError *error)
|
|
|
579
579
|
setImageBlock: setImage
|
|
580
580
|
progress: nil
|
|
581
581
|
completed: ^(UIImage* _Nullable image, NSData* _Nullable data, NSError* _Nullable error, SDImageCacheType cacheType, BOOL finished, NSURL* _Nullable imageURL) {
|
|
582
|
+
if (error) {
|
|
583
|
+
[source forgetResponseAfterError: error];
|
|
584
|
+
}
|
|
582
585
|
if (error && weakSelf.loadCount == load) {
|
|
583
586
|
// Keeps the image it has, without trying again on every
|
|
584
587
|
// layout (e.g. while offline).
|
|
@@ -608,6 +611,9 @@ NSString *FFFErrorMessage(NSError *error)
|
|
|
608
611
|
SDImageCacheType cacheType,
|
|
609
612
|
BOOL finished,
|
|
610
613
|
NSURL* _Nullable imageURL) {
|
|
614
|
+
if (error) {
|
|
615
|
+
[source forgetResponseAfterError: error];
|
|
616
|
+
}
|
|
611
617
|
// Replaced by another load (a new source, or the same one
|
|
612
618
|
// restarted for a new size), which sends the events. This
|
|
613
619
|
// one was cancelled, which SDWebImage reports as an error.
|
|
@@ -107,7 +107,10 @@ RCT_EXPORT_METHOD(preload:(nonnull NSArray<FFFastImageSource *> *)sources
|
|
|
107
107
|
}
|
|
108
108
|
[results addObject:[NSNull null]];
|
|
109
109
|
// A source's own priority replaces the prefetcher's, as on Android.
|
|
110
|
-
|
|
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;
|
|
111
114
|
if (source.hasPriority) {
|
|
112
115
|
options &= ~(SDWebImageLowPriority | SDWebImageHighPriority);
|
|
113
116
|
if (source.priority == FFFPriorityLow) {
|
|
@@ -137,6 +140,9 @@ RCT_EXPORT_METHOD(preload:(nonnull NSArray<FFFastImageSource *> *)sources
|
|
|
137
140
|
return;
|
|
138
141
|
}
|
|
139
142
|
done = YES;
|
|
143
|
+
if (error) {
|
|
144
|
+
[source forgetResponseAfterError:error];
|
|
145
|
+
}
|
|
140
146
|
results[idx] = image
|
|
141
147
|
? @{@"ok": @YES, @"width": @(image.size.width), @"height": @(image.size.height)}
|
|
142
148
|
: @{@"ok": @NO, @"error": FFFErrorMessage(error)};
|