1 | "use strict";
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 |
|
9 |
|
10 |
|
11 |
|
12 |
|
13 |
|
14 |
|
15 |
|
16 |
|
17 |
|
18 |
|
19 | var url_module = require('url');
|
20 | var fs_module = require('fs');
|
21 | var https_module = require('https');
|
22 | var http_module = require('http');
|
23 |
|
24 |
|
25 |
|
26 |
|
27 |
|
28 |
|
29 |
|
30 | var FileStreamer = module.exports = function FileStreamer(file_path, upload_endpoint, progress_callback) {
|
31 | if (!file_path) {
|
32 | throw new Error('You must provide a file path');
|
33 | }
|
34 |
|
35 | if (!upload_endpoint) {
|
36 | throw new Error('You must provide an upload endpoint');
|
37 | }
|
38 |
|
39 | this._endpoint = url_module.parse(upload_endpoint);
|
40 | this._path = file_path;
|
41 | this.progress_callback = progress_callback;
|
42 | }
|
43 |
|
44 | FileStreamer.prototype._endpoint = null;
|
45 | FileStreamer.prototype._path = null;
|
46 | FileStreamer.prototype._fd = null;
|
47 | FileStreamer.prototype._file_size = 0;
|
48 | FileStreamer.prototype.percentage = 0;
|
49 | FileStreamer.prototype.sequential = true;
|
50 | FileStreamer.prototype.progress_callback = null;
|
51 |
|
52 |
|
53 |
|
54 |
|
55 | FileStreamer.prototype._user_ready = function () {
|
56 | this.ready = function (fn) {
|
57 | fn();
|
58 | }
|
59 | };
|
60 |
|
61 |
|
62 |
|
63 |
|
64 |
|
65 |
|
66 |
|
67 | FileStreamer.prototype._ready = function () {
|
68 | var _self = this;
|
69 |
|
70 |
|
71 | this._getNewStart(function (err, start) {
|
72 | if (err) {
|
73 |
|
74 | _self._closeFile();
|
75 | return _self._error(err);
|
76 | }
|
77 |
|
78 | if (start >= _self._file_size) {
|
79 |
|
80 | _self._closeFile()
|
81 | _self._user_ready();
|
82 | } else {
|
83 |
|
84 | _self._streamChunk(start);
|
85 | }
|
86 | });
|
87 | };
|
88 |
|
89 |
|
90 |
|
91 |
|
92 |
|
93 |
|
94 | FileStreamer.prototype.ready = function (fn) {
|
95 | this._user_ready = fn;
|
96 | };
|
97 |
|
98 |
|
99 |
|
100 |
|
101 |
|
102 |
|
103 |
|
104 | FileStreamer.prototype._error = function (error) {
|
105 | this.error = function (fn) {
|
106 | fn(error);
|
107 | }
|
108 | };
|
109 |
|
110 |
|
111 |
|
112 |
|
113 |
|
114 |
|
115 | FileStreamer.prototype.error = function (fn) {
|
116 | this._error = fn;
|
117 | };
|
118 |
|
119 |
|
120 |
|
121 |
|
122 | FileStreamer.prototype.upload = function () {
|
123 | var _self = this;
|
124 |
|
125 | fs_module.stat(_self._path, function (stat_err, stats) {
|
126 | if (stat_err) {
|
127 | return _self._error(stat_err);
|
128 | }
|
129 |
|
130 | _self._file_size = stats.size;
|
131 |
|
132 | fs_module.open(_self._path, 'r', function(open_err, fd) {
|
133 | if (open_err) {
|
134 | return this._error(open_err);
|
135 | }
|
136 |
|
137 | _self._fd = fd;
|
138 | _self._streamChunk(0);
|
139 | });
|
140 | });
|
141 | };
|
142 |
|
143 |
|
144 |
|
145 |
|
146 |
|
147 |
|
148 | FileStreamer.prototype._streamChunk = function (start) {
|
149 | var _self = this;
|
150 | _self._putFile(start, function (put_err, code, headers) {
|
151 |
|
152 |
|
153 | if (put_err && code) {
|
154 | _self._closeFile();
|
155 | return _self._error(put_err);
|
156 | }
|
157 |
|
158 | _self._ready();
|
159 | });
|
160 | };
|
161 |
|
162 |
|
163 |
|
164 |
|
165 |
|
166 |
|
167 |
|
168 | FileStreamer.prototype._putFile = function (start, callback) {
|
169 | var _self = this;
|
170 |
|
171 | var file = fs_module.createReadStream(_self._path, {
|
172 | start : start
|
173 | });
|
174 |
|
175 | file.on('error', function (err) {
|
176 | callback(err);
|
177 | });
|
178 |
|
179 | var uploaded_size = start || 0;
|
180 |
|
181 | file.on('data', function(chunk) {
|
182 | uploaded_size += chunk.length || 0;
|
183 | if (_self.progress_callback) {
|
184 | _self.progress_callback(uploaded_size, _self._file_size);
|
185 | }
|
186 | });
|
187 |
|
188 | var headers = {
|
189 | 'Content-Length' : _self._file_size,
|
190 | 'Content-Type' : 'video/mp4'
|
191 | };
|
192 |
|
193 | headers['Content-Range'] = 'bytes ' + start + '-' + _self._file_size + '/' + _self._file_size;
|
194 |
|
195 | var req = _self._upload_endpoint_request({
|
196 | method : 'PUT',
|
197 | headers : headers
|
198 | }, callback);
|
199 |
|
200 | file.pipe(req);
|
201 | };
|
202 |
|
203 |
|
204 |
|
205 |
|
206 | FileStreamer.prototype._closeFile = function () {
|
207 | if (this._fd) {
|
208 | fs_module.close(this._fd, function (close_err) {
|
209 | if (close_err) {
|
210 | this._error(close_err);
|
211 | }
|
212 | });
|
213 | this._fd = null;
|
214 | }
|
215 | };
|
216 |
|
217 |
|
218 |
|
219 |
|
220 |
|
221 | FileStreamer.prototype._getNewStart = function (next) {
|
222 | var _self = this;
|
223 |
|
224 | this._upload_endpoint_request({
|
225 | method : 'PUT',
|
226 | headers : {
|
227 | 'Content-Range' : 'bytes */*',
|
228 | 'Content-Type' : 'application/octet-stream'
|
229 | }
|
230 | }, function (err, status, headers) {
|
231 | if (err) {
|
232 | return next(err);
|
233 | }
|
234 |
|
235 | if (status === 308) {
|
236 | return next(null, parseInt(headers.range.split('-')[1]));
|
237 | } else {
|
238 | return next(new Error('Invalid http status returned from range query: [' + status + ']'));
|
239 | }
|
240 | }).end();
|
241 | };
|
242 |
|
243 |
|
244 |
|
245 |
|
246 |
|
247 |
|
248 |
|
249 | FileStreamer.prototype._upload_endpoint_request = function (options, callback) {
|
250 | var request_options = {
|
251 | protocol : this._endpoint.protocol,
|
252 | host : this._endpoint.hostname,
|
253 | port : this._endpoint.port,
|
254 | query : this._endpoint.query,
|
255 | headers : options.headers,
|
256 | path : this._endpoint.path,
|
257 | method : options.method
|
258 | };
|
259 |
|
260 | var client = request_options.protocol === 'https:' ? https_module : http_module;
|
261 | var req = client.request(request_options);
|
262 |
|
263 | req.on('response', function (res) {
|
264 | res.setEncoding('utf8');
|
265 |
|
266 | var buffer = '';
|
267 | res.on('readable', function () {
|
268 | buffer += res.read();
|
269 | });
|
270 |
|
271 | if (res.statusCode > 399) {
|
272 |
|
273 | res.on('end', function () {
|
274 | callback(new Error('[' + buffer + ']'), res.statusCode, res.headers);
|
275 | });
|
276 | } else {
|
277 |
|
278 | res.on('end', function () {
|
279 | callback(null, res.statusCode, res.headers);
|
280 | });
|
281 | }
|
282 | });
|
283 |
|
284 |
|
285 | req.on('error', function(e) {
|
286 | callback(e);
|
287 | });
|
288 |
|
289 | return req;
|
290 | };
|