Skip to content

Commit 843748f

Browse files
rafecafacebook-github-bot
authored andcommitted
Add support for storing/retrieving binary data in metro-cache
Reviewed By: mjesun Differential Revision: D10868923 fbshipit-source-id: 9e849b483ade5d97a4194155615f05cd70559593
1 parent db3ec38 commit 843748f

File tree

4 files changed

+104
-6
lines changed

4 files changed

+104
-6
lines changed

packages/metro-cache/src/stores/FileStore.js

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@ const mkdirp = require('mkdirp');
1515
const path = require('path');
1616
const rimraf = require('rimraf');
1717

18+
const NULL_BYTE = 0x00;
19+
const NULL_BYTE_BUFFER = new Buffer([NULL_BYTE]);
20+
1821
export type Options = {|
1922
root: string,
2023
|};
@@ -29,7 +32,13 @@ class FileStore<T> {
2932

3033
get(key: Buffer): ?T {
3134
try {
32-
return JSON.parse(fs.readFileSync(this._getFilePath(key), 'utf8'));
35+
const data = fs.readFileSync(this._getFilePath(key));
36+
37+
if (data[0] === NULL_BYTE) {
38+
return (data.slice(1): any);
39+
} else {
40+
return JSON.parse(data.toString('utf8'));
41+
}
3342
} catch (err) {
3443
if (err.code === 'ENOENT') {
3544
return null;
@@ -40,7 +49,16 @@ class FileStore<T> {
4049
}
4150

4251
set(key: Buffer, value: T): void {
43-
fs.writeFileSync(this._getFilePath(key), JSON.stringify(value));
52+
if (value instanceof Buffer) {
53+
const fd = fs.openSync(this._getFilePath(key), 'w');
54+
55+
fs.writeSync(fd, NULL_BYTE_BUFFER);
56+
fs.writeSync(fd, value);
57+
58+
fs.closeSync(fd);
59+
} else {
60+
fs.writeFileSync(this._getFilePath(key), JSON.stringify(value));
61+
}
4462
}
4563

4664
clear() {

packages/metro-cache/src/stores/HttpStore.js

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@ const ZLIB_OPTIONS = {
2828
level: 9,
2929
};
3030

31+
const NULL_BYTE = 0x00;
32+
const NULL_BYTE_BUFFER = new Buffer([NULL_BYTE]);
33+
3134
class HttpStore<T> {
3235
static HttpError = HttpError;
3336
static NetworkError = NetworkError;
@@ -82,7 +85,7 @@ class HttpStore<T> {
8285

8386
const req = this._module.request(options, res => {
8487
const code = res.statusCode;
85-
let data = '';
88+
const data = [];
8689

8790
if (code === 404) {
8891
res.resume();
@@ -99,7 +102,7 @@ class HttpStore<T> {
99102
const gunzipped = res.pipe(zlib.createGunzip());
100103

101104
gunzipped.on('data', chunk => {
102-
data += chunk.toString();
105+
data.push(chunk);
103106
});
104107

105108
gunzipped.on('error', err => {
@@ -108,7 +111,13 @@ class HttpStore<T> {
108111

109112
gunzipped.on('end', () => {
110113
try {
111-
resolve(JSON.parse(data));
114+
const buffer = Buffer.concat(data);
115+
116+
if (buffer.length > 0 && buffer[0] === NULL_BYTE) {
117+
resolve((buffer.slice(1): any));
118+
} else {
119+
resolve(JSON.parse(buffer.toString('utf8')));
120+
}
112121
} catch (err) {
113122
reject(err);
114123
}
@@ -152,7 +161,13 @@ class HttpStore<T> {
152161
});
153162

154163
gzip.pipe(req);
155-
gzip.end(JSON.stringify(value) || 'null');
164+
165+
if (value instanceof Buffer) {
166+
gzip.write(NULL_BYTE_BUFFER);
167+
gzip.end(value);
168+
} else {
169+
gzip.end(JSON.stringify(value) || 'null');
170+
}
156171
});
157172
}
158173

packages/metro-cache/src/stores/__tests__/FileStore-test.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,4 +40,13 @@ describe('FileStore', () => {
4040

4141
expect(fileStore.get(cache)).toEqual(null);
4242
});
43+
44+
it('reads and writes binary data', () => {
45+
const fileStore = new FileStore({root: '/root'});
46+
const cache = Buffer.from([0xfa, 0xce, 0xb0, 0x0c]);
47+
const data = Buffer.from([0xca, 0xc4, 0xe5]);
48+
49+
fileStore.set(cache, data);
50+
expect(fileStore.get(cache)).toEqual(data);
51+
});
4352
});

packages/metro-cache/src/stores/__tests__/HttpStore-test.js

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,4 +187,60 @@ describe('HttpStore', () => {
187187
done();
188188
});
189189
});
190+
191+
it('gets the same value that was set', async () => {
192+
const store = new HttpStore({endpoint: 'http://www.example.com/endpoint'});
193+
const chunks = [];
194+
let storedValue;
195+
196+
httpPassThrough.on('data', chunk => {
197+
chunks.push(chunk);
198+
});
199+
200+
httpPassThrough.on('end', () => {
201+
storedValue = zlib.gunzipSync(Buffer.concat(chunks));
202+
203+
const callbackSet = require('http').request.mock.calls[0][1];
204+
205+
callbackSet(responseHttpOk(''));
206+
});
207+
208+
await store.set(Buffer.from('key-set'), {foo: 42});
209+
210+
const promiseGet = store.get(Buffer.from('key-set'));
211+
const callbackGet = require('http').request.mock.calls[1][1];
212+
213+
callbackGet(responseHttpOk(storedValue));
214+
215+
expect(await promiseGet).toEqual({foo: 42});
216+
});
217+
218+
it('gets the same value that was set when storing buffers', async () => {
219+
const store = new HttpStore({endpoint: 'http://www.example.com/endpoint'});
220+
const chunks = [];
221+
let storedValue;
222+
223+
httpPassThrough.on('data', chunk => {
224+
chunks.push(chunk);
225+
});
226+
227+
httpPassThrough.on('end', () => {
228+
storedValue = zlib.gunzipSync(Buffer.concat(chunks));
229+
230+
const callbackSet = require('http').request.mock.calls[0][1];
231+
232+
callbackSet(responseHttpOk(''));
233+
});
234+
235+
const bufferValue = new Buffer([0xfb, 0xca, 0xc4]);
236+
237+
await store.set(Buffer.from('key-set'), bufferValue);
238+
239+
const promiseGet = store.get(Buffer.from('key-set'));
240+
const callbackGet = require('http').request.mock.calls[1][1];
241+
242+
callbackGet(responseHttpOk(storedValue));
243+
244+
expect(await promiseGet).toEqual(bufferValue);
245+
});
190246
});

0 commit comments

Comments
 (0)