Skip to content

Commit d5ed0c6

Browse files
author
AJ ONeal
committed
moved FileReader to own repo
1 parent 3cfb300 commit d5ed0c6

File tree

3 files changed

+412
-1
lines changed

3 files changed

+412
-1
lines changed

FileReader.js

Lines changed: 300 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,300 @@
1+
//
2+
// FileReader
3+
//
4+
// http://www.w3.org/TR/FileAPI/#dfn-filereader
5+
// https://developer.mozilla.org/en/DOM/FileReader
6+
(function () {
7+
"use strict";
8+
9+
var fs = require("fs")
10+
, EventEmitter = require("events").EventEmitter
11+
;
12+
13+
function doop(fn, args, context) {
14+
if ('function' === typeof fn) {
15+
fn.apply(context, args);
16+
}
17+
}
18+
19+
function toDataUrl(data, type) {
20+
// var data = self.result;
21+
var dataUrl = 'data:';
22+
23+
if (type) {
24+
dataUrl += type + ';';
25+
}
26+
27+
if (/text/i.test(type)) {
28+
dataUrl += 'charset=utf-8,';
29+
dataUrl += data.toString('utf8');
30+
} else {
31+
dataUrl += 'base64,';
32+
dataUrl += data.toString('base64');
33+
}
34+
35+
return dataUrl;
36+
}
37+
38+
function mapDataToFormat(file, data, format, encoding) {
39+
// var data = self.result;
40+
41+
switch(format) {
42+
case 'buffer':
43+
return data;
44+
break;
45+
case 'binary':
46+
return data.toString('binary');
47+
break;
48+
case 'dataUrl':
49+
return toDataUrl(data, file.type);
50+
break;
51+
case 'text':
52+
return data.toString(encoding || 'utf8');
53+
break;
54+
}
55+
}
56+
57+
function FileReader() {
58+
var self = this,
59+
emitter = new EventEmitter,
60+
file;
61+
62+
self.addEventListener = function (on, callback) {
63+
emitter.on(on, callback);
64+
};
65+
self.removeEventListener = function (callback) {
66+
emitter.removeListener(callback);
67+
}
68+
self.dispatchEvent = function (on) {
69+
emitter.emit(on);
70+
}
71+
72+
self.EMPTY = 0;
73+
self.LOADING = 1;
74+
self.DONE = 2;
75+
76+
self.error = undefined; // Read only
77+
self.readyState = self.EMPTY; // Read only
78+
self.result = undefined; // Road only
79+
80+
// non-standard
81+
self.on = function () {
82+
emitter.on.apply(emitter, arguments);
83+
}
84+
self.nodeChunkedEncoding = false;
85+
self.setNodeChunkedEncoding = function (val) {
86+
self.nodeChunkedEncoding = val;
87+
};
88+
// end non-standard
89+
90+
91+
92+
// Whatever the file object is, turn it into a Node.JS File.Stream
93+
function createFileStream() {
94+
var stream = new EventEmitter(),
95+
chunked = self.nodeChunkedEncoding;
96+
97+
// attempt to make the length computable
98+
if (!file.size && chunked && file.path) {
99+
fs.stat(file.path, function (err, stat) {
100+
file.size = stat.size;
101+
file.lastModifiedDate = stat.mtime;
102+
});
103+
}
104+
105+
106+
// The stream exists, do nothing more
107+
if (file.stream) {
108+
return;
109+
}
110+
111+
112+
// Create a read stream from a buffer
113+
if (file.buffer) {
114+
process.nextTick(function () {
115+
stream.emit('data', file.buffer);
116+
stream.emit('end');
117+
});
118+
file.stream = stream;
119+
return;
120+
}
121+
122+
123+
// Create a read stream from a file
124+
if (file.path) {
125+
// TODO url
126+
if (!chunked) {
127+
fs.readFile(file.path, function (err, data) {
128+
if (err) {
129+
stream.emit('error', err);
130+
}
131+
if (data) {
132+
stream.emit('data', data);
133+
stream.emit('end');
134+
}
135+
});
136+
137+
file.stream = stream;
138+
return;
139+
}
140+
141+
// TODO don't duplicate this code here,
142+
// expose a method in File instead
143+
file.stream = fs.createReadStream(file.path);
144+
}
145+
}
146+
147+
148+
149+
// before any other listeners are added
150+
emitter.on('abort', function () {
151+
self.readyState = self.DONE;
152+
});
153+
154+
155+
156+
// Map `error`, `progress`, `load`, and `loadend`
157+
function mapStreamToEmitter(format, encoding) {
158+
var stream = file.stream,
159+
buffers = [],
160+
chunked = self.nodeChunkedEncoding;
161+
162+
buffers.dataLength = 0;
163+
164+
stream.on('error', function (err) {
165+
if (self.DONE === self.readyState) {
166+
return;
167+
}
168+
169+
self.readyState = self.DONE;
170+
self.error = err;
171+
emitter.emit('error', err);
172+
});
173+
174+
stream.on('data', function (data) {
175+
if (self.DONE === self.readyState) {
176+
return;
177+
}
178+
179+
buffers.dataLength += data.length;
180+
buffers.push(data);
181+
182+
emitter.emit('progress', {
183+
// fs.stat will probably complete before this
184+
// but possibly it will not, hence the check
185+
lengthComputable: (!isNaN(file.size)) ? true : false,
186+
loaded: buffers.dataLength,
187+
total: file.size
188+
});
189+
190+
emitter.emit('data', data);
191+
});
192+
193+
stream.on('end', function () {
194+
if (self.DONE === self.readyState) {
195+
return;
196+
}
197+
198+
var data;
199+
200+
if (buffers.length > 1 ) {
201+
data = Buffer.concat(buffers);
202+
} else {
203+
data = buffers[0];
204+
}
205+
206+
self.readyState = self.DONE;
207+
self.result = mapDataToFormat(file, data, format, encoding);
208+
emitter.emit('load', {
209+
target: {
210+
// non-standard
211+
nodeBufferResult: data,
212+
result: self.result
213+
}
214+
});
215+
216+
emitter.emit('loadend');
217+
});
218+
}
219+
220+
221+
// Abort is overwritten by readAsXyz
222+
self.abort = function () {
223+
if (self.readState == self.DONE) {
224+
return;
225+
}
226+
self.readyState = self.DONE;
227+
emitter.emit('abort');
228+
};
229+
230+
231+
232+
//
233+
function mapUserEvents() {
234+
emitter.on('start', function () {
235+
doop(self.onloadstart, arguments);
236+
});
237+
emitter.on('progress', function () {
238+
doop(self.onprogress, arguments);
239+
});
240+
emitter.on('error', function (err) {
241+
// TODO translate to FileError
242+
if (self.onerror) {
243+
self.onerror(err);
244+
} else {
245+
if (!emitter.listeners.error || !emitter.listeners.error.length) {
246+
throw err;
247+
}
248+
}
249+
});
250+
emitter.on('load', function () {
251+
doop(self.onload, arguments);
252+
});
253+
emitter.on('end', function () {
254+
doop(self.onloadend, arguments);
255+
});
256+
emitter.on('abort', function () {
257+
doop(self.onabort, arguments);
258+
});
259+
}
260+
261+
262+
263+
function readFile(_file, format, encoding) {
264+
file = _file;
265+
if (!file || !file.name || !(file.path || file.stream || file.buffer)) {
266+
throw new Error("cannot read as File: " + JSON.stringify(file));
267+
}
268+
if (0 !== self.readyState) {
269+
console.log("already loading, request to change format ignored");
270+
return;
271+
}
272+
273+
// 'process.nextTick' does not ensure order, (i.e. an fs.stat queued later may return faster)
274+
// but `onloadstart` must come before the first `data` event and must be asynchronous.
275+
// Hence we waste a single tick waiting
276+
process.nextTick(function () {
277+
self.readyState = self.LOADING;
278+
emitter.emit('loadstart');
279+
createFileStream();
280+
mapStreamToEmitter(format, encoding);
281+
mapUserEvents();
282+
});
283+
}
284+
285+
self.readAsArrayBuffer = function (file) {
286+
readFile(file, 'buffer');
287+
};
288+
self.readAsBinaryString = function (file) {
289+
readFile(file, 'binary');
290+
};
291+
self.readAsDataURL = function (file) {
292+
readFile(file, 'dataUrl');
293+
};
294+
self.readAsText = function (file, encoding) {
295+
readFile(file, 'text', encoding);
296+
};
297+
}
298+
299+
module.exports = FileReader;
300+
}());

0 commit comments

Comments
 (0)