Skip to content
This repository was archived by the owner on Apr 15, 2025. It is now read-only.

Commit 51df311

Browse files
committed
Replace rn-fetch-blob with react-native-fs based on CompanyCam#1 rn-fetch-blob does odd things like writing 404 responses to file which if for some reason aren't caught and deleted will cause the next request to return the cached 404 response, react-native-fs still returns a success response with 404 status but doesn't write the response to the destination.
1 parent d9a7c21 commit 51df311

File tree

9 files changed

+223
-193
lines changed

9 files changed

+223
-193
lines changed

README.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,15 +25,15 @@ Or
2525
$ yarn add react-native-image-cache-hoc
2626
```
2727

28-
Then, because this package has a depedency on [rn-fetch-blob](https://github.com/joltup/rn-fetch-blob) you will need to link this native package by running:
28+
Then, because this package has a depedency on [react-native-fs](https://github.com/itinance/react-native-fs) you will need to link this native package by running:
2929

3030
```bash
31-
$ react-native link rn-fetch-blob
31+
$ react-native link react-native-fs
3232
```
3333

34-
Linking rn-fetch-blob **should only be done once**, reinstalling node_modules with npm or yarn does not require running the above command again.
34+
Linking react-native-fs **should only be done once**, reinstalling node_modules with npm or yarn does not require running the above command again.
3535

36-
To troubleshoot linking, refer to [the rn-fetch-blob installation instructions](https://github.com/joltup/rn-fetch-blob#user-content-installation).
36+
To troubleshoot linking, refer to [the react-native-fs installation instructions](https://github.com/itinance/react-native-fs#usage-ios).
3737

3838
## Usage
3939

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,6 @@
7070
"validator": "^9.0.0"
7171
},
7272
"peerDependencies": {
73-
"rn-fetch-blob": "^0.12.0"
73+
"react-native-fs": "^2.14.1"
7474
}
7575
}

src/FileSystem.js

Lines changed: 45 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,23 @@
99

1010
import { Platform } from 'react-native';
1111
import pathLib from 'path';
12-
import RNFetchBlob from 'rn-fetch-blob';
12+
import RNFS from 'react-native-fs';
1313
import sha1 from 'crypto-js/sha1';
1414
import URL from 'url-parse';
1515

16+
/**
17+
* Resolves if 'unlink' resolves or if the file doesn't exist.
18+
*
19+
* @param {string} filename
20+
*/
21+
const RNFSUnlinkIfExists = (filename) =>
22+
RNFS.exists(filename).then((exists) => {
23+
if (exists) {
24+
return RNFS.unlink(filename);
25+
}
26+
return Promise.resolve();
27+
});
28+
1629
export class FileSystem {
1730
/**
1831
* All FileSystem instances will reference the cacheLock singleton "dictionary" to provide cache file locking in order to prevent concurrency race condition bugs.
@@ -85,8 +98,7 @@ export class FileSystem {
8598
* @private
8699
*/
87100
_setBaseFilePath(fileDirName = null) {
88-
let baseFilePath =
89-
this.os == 'ios' ? RNFetchBlob.fs.dirs.CacheDir : RNFetchBlob.fs.dirs.DocumentDir;
101+
let baseFilePath = this.os == 'ios' ? RNFS.CachesDirectoryPath : RNFS.DocumentDirectoryPath;
90102
baseFilePath += '/' + fileDirName + '/';
91103
return baseFilePath;
92104
}
@@ -124,7 +136,7 @@ export class FileSystem {
124136
*/
125137
exists(path) {
126138
this._validatePath(path);
127-
return RNFetchBlob.fs.exists(pathLib.resolve(this.baseFilePath + path));
139+
return RNFS.exists(pathLib.resolve(this.baseFilePath + path));
128140
}
129141

130142
/**
@@ -192,7 +204,7 @@ export class FileSystem {
192204
*/
193205
async fetchFile(url, permanent = false, fileName = null, clobber = false) {
194206
fileName = fileName || (await this.getFileNameFromUrl(url));
195-
let path = this.baseFilePath + (permanent ? 'permanent' : 'cache') + '/' + fileName;
207+
let path = this.baseFilePath + (permanent ? 'permanent/' : 'cache/') + fileName;
196208
this._validatePath(path, true);
197209

198210
// Clobber logic
@@ -207,22 +219,30 @@ export class FileSystem {
207219
}
208220

209221
// Hit network and download file to local disk.
210-
let result = null;
211222
try {
212-
result = await RNFetchBlob.config({
213-
path: path,
214-
}).fetch('GET', url);
223+
const cacheDirExists = await this.exists(permanent ? 'permanent' : 'cache');
224+
if (!cacheDirExists) {
225+
await RNFS.mkdir(`${this.baseFilePath}${permanent ? 'permanent' : 'cache'}`);
226+
}
227+
228+
const { promise } = RNFS.downloadFile({
229+
fromUrl: url,
230+
toFile: path,
231+
});
232+
const response = await promise;
233+
if (response.statusCode !== 200) {
234+
throw response;
235+
}
215236
} catch (error) {
216-
// File must be manually removed on download error https://github.com/wkh237/react-native-fetch-blob/issues/331
217-
await RNFetchBlob.fs.unlink(path);
237+
await RNFSUnlinkIfExists(path);
218238
return {
219239
path: null,
220240
fileName: pathLib.basename(path),
221241
};
222242
}
223243

224244
return {
225-
path: result.path(),
245+
path,
226246
fileName: pathLib.basename(path),
227247
};
228248
}
@@ -240,15 +260,15 @@ export class FileSystem {
240260
return;
241261
}
242262

243-
// Get blobStatObjects.
244-
let lstat = await RNFetchBlob.fs.lstat(this.baseFilePath + 'cache');
263+
// Get directory contents
264+
let dirContents = await RNFS.readDir(this.baseFilePath + 'cache');
245265

246-
// Sort blobStatObjects in order of oldest to newest file.
247-
lstat.sort((a, b) => {
248-
return a.lastModified - b.lastModified;
266+
// Sort dirContents in order of oldest to newest file.
267+
dirContents.sort((a, b) => {
268+
return a.mtime - b.mtime;
249269
});
250270

251-
let currentCacheSize = lstat.reduce((cacheSize, blobStatObject) => {
271+
let currentCacheSize = dirContents.reduce((cacheSize, blobStatObject) => {
252272
return cacheSize + parseInt(blobStatObject.size);
253273
}, 0);
254274

@@ -258,16 +278,16 @@ export class FileSystem {
258278

259279
// Keep deleting cached files so long as the current cache size is larger than the size required to trigger cache pruning, or until
260280
// all cache files have been evaluated.
261-
while (overflowSize > 0 && lstat.length) {
262-
let blobStatObject = lstat.shift();
281+
while (overflowSize > 0 && dirContents.length) {
282+
let contentFile = dirContents.shift();
263283

264284
// Only prune unlocked files from cache
265285
if (
266-
!FileSystem.cacheLock[blobStatObject.filename] &&
267-
this._validatePath('cache/' + blobStatObject.filename)
286+
!FileSystem.cacheLock[contentFile.name] &&
287+
this._validatePath('cache/' + contentFile.name)
268288
) {
269-
overflowSize = overflowSize - parseInt(blobStatObject.size);
270-
RNFetchBlob.fs.unlink(this.baseFilePath + 'cache/' + blobStatObject.filename);
289+
overflowSize -= parseInt(contentFile.size);
290+
RNFSUnlinkIfExists(this.baseFilePath + 'cache/' + contentFile.name);
271291
}
272292
}
273293
}
@@ -283,7 +303,7 @@ export class FileSystem {
283303
this._validatePath(path);
284304

285305
try {
286-
await RNFetchBlob.fs.unlink(pathLib.resolve(this.baseFilePath + path));
306+
await RNFSUnlinkIfExists(pathLib.resolve(this.baseFilePath + path));
287307
return true;
288308
} catch (error) {
289309
return false;

tests/CacheableImage.test.js

Lines changed: 24 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import imageCacheHoc from '../src/imageCacheHoc';
88
import { Image } from 'react-native';
99
import sinon from 'sinon';
1010
import 'should-sinon';
11-
import RNFetchBlob from 'rn-fetch-blob';
11+
import RNFS from 'react-native-fs';
1212
import { shallow } from 'enzyme';
1313
import React from 'react';
1414

@@ -98,13 +98,12 @@ describe('CacheableImage', function () {
9898

9999
it('#cacheFile static method should work as expected for cache dir files.', () => {
100100
// Mock that file does not exist on local fs.
101-
RNFetchBlob.fs.exists.mockResolvedValue(false);
101+
RNFS.exists.mockResolvedValue(false);
102102

103103
// Mock fetch result
104-
RNFetchBlob.fetch.mockResolvedValue({
105-
path: () => {
106-
return '/this/is/path/to/file.jpg';
107-
},
104+
105+
RNFS.downloadFile.mockReturnValue({
106+
promise: Promise.resolve({ statusCode: 200 }),
108107
});
109108

110109
const CacheableImage = imageCacheHoc(Image);
@@ -113,20 +112,19 @@ describe('CacheableImage', function () {
113112
result.should.deepEqual({
114113
url: 'https://i.redd.it/rc29s4bz61uz.png',
115114
cacheType: 'cache',
116-
localFilePath: '/this/is/path/to/file.jpg',
115+
localFilePath:
116+
'/base/file/path/react-native-image-cache-hoc/cache/d3b74e9fa8248a5805e2dcf17a8577acd28c089b.png',
117117
});
118118
});
119119
});
120120

121121
it('#cacheFile static method should work as expected for permanent dir files.', () => {
122122
// Mock that file does not exist on local fs.
123-
RNFetchBlob.fs.exists.mockResolvedValue(false);
123+
RNFS.exists.mockResolvedValue(false);
124124

125125
// Mock fetch result
126-
RNFetchBlob.fetch.mockResolvedValue({
127-
path: () => {
128-
return '/this/is/path/to/file.jpg';
129-
},
126+
RNFS.downloadFile.mockReturnValue({
127+
promise: Promise.resolve({ statusCode: 200 }),
130128
});
131129

132130
const CacheableImage = imageCacheHoc(Image);
@@ -135,14 +133,15 @@ describe('CacheableImage', function () {
135133
result.should.deepEqual({
136134
url: 'https://i.redd.it/rc29s4bz61uz.png',
137135
cacheType: 'permanent',
138-
localFilePath: '/this/is/path/to/file.jpg',
136+
localFilePath:
137+
'/base/file/path/react-native-image-cache-hoc/permanent/d3b74e9fa8248a5805e2dcf17a8577acd28c089b.png',
139138
});
140139
});
141140
});
142141

143142
it('#flush static method should work as expected.', () => {
144143
// Mock unlink to always be true.
145-
RNFetchBlob.fs.unlink.mockResolvedValue(true);
144+
RNFS.unlink.mockResolvedValue(true);
146145

147146
const CacheableImage = imageCacheHoc(Image);
148147

@@ -319,16 +318,12 @@ describe('CacheableImage', function () {
319318
});
320319

321320
it('componentDidUpdate should not throw any uncaught errors.', (done) => {
322-
RNFetchBlob.fetch
323-
.mockResolvedValueOnce({
324-
path: () => {
325-
return 'A.jpg';
326-
},
321+
RNFS.downloadFile
322+
.mockReturnValueOnce({
323+
promise: Promise.resolve({ statusCode: 200 }),
327324
})
328-
.mockResolvedValueOnce({
329-
path: () => {
330-
return 'B.jpg';
331-
},
325+
.mockReturnValueOnce({
326+
promise: Promise.resolve({ statusCode: 200 }),
332327
});
333328

334329
const CacheableImage = imageCacheHoc(Image);
@@ -337,14 +332,16 @@ describe('CacheableImage', function () {
337332

338333
setImmediate(() => {
339334
expect(wrapper.prop('source')).toStrictEqual({
340-
uri: 'A.jpg',
335+
uri:
336+
'/base/file/path/react-native-image-cache-hoc/cache/d3b74e9fa8248a5805e2dcf17a8577acd28c089b.png',
341337
});
342338

343339
wrapper.setProps({ source: { uri: 'https://example.com/B.jpg' } });
344340

345341
setImmediate(() => {
346342
expect(wrapper.prop('source')).toStrictEqual({
347-
uri: 'B.jpg',
343+
uri:
344+
'/base/file/path/react-native-image-cache-hoc/cache/a940ee9ea388fcea7628d9a64dfac6a698aa0228.jpg',
348345
});
349346

350347
done();
@@ -359,7 +356,8 @@ describe('CacheableImage', function () {
359356

360357
setImmediate(() => {
361358
expect(wrapper.prop('source')).toStrictEqual({
362-
uri: '/this/is/path/to/file.jpg',
359+
uri:
360+
'/base/file/path/react-native-image-cache-hoc/cache/d3b74e9fa8248a5805e2dcf17a8577acd28c089b.png',
363361
});
364362

365363
wrapper.setState({ localFilePath: './test.jpg' });

0 commit comments

Comments
 (0)