Skip to content

Commit d17d6c5

Browse files
+create[Read,Write]Stream -duplexified File
1 parent a396eb6 commit d17d6c5

6 files changed

Lines changed: 310 additions & 428 deletions

File tree

lib/storage/bucket.js

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -98,14 +98,12 @@ Bucket.prototype.delete = function(callback) {
9898
* the different use cases you may have.
9999
*
100100
* @param {string} name - The name of the file in this bucket.
101-
* @param {object=} metadata - Metadata to set when writing to this file.
102-
* @return {module:storage/file}
103101
*
104102
* @example
105103
* var file = bucket.file('my-existing-file.png');
106104
*/
107-
Bucket.prototype.file = function(name, metadata) {
108-
return new File(this, name, metadata);
105+
Bucket.prototype.file = function(name) {
106+
return new File(this, name);
109107
};
110108

111109
/**
@@ -291,16 +289,18 @@ Bucket.prototype.upload = function(localPath, destination, metadata, callback) {
291289
name = path.basename(localPath);
292290
break;
293291
}
292+
metadata = metadata || {};
293+
callback = callback || util.noop;
294294
if (util.is(destination, 'string')) {
295295
name = destination;
296296
}
297297
if (destination instanceof File) {
298298
name = destination.name;
299299
newFile = destination;
300300
}
301-
newFile = newFile || this.file(name, metadata);
301+
newFile = newFile || this.file(name);
302302
fs.createReadStream(localPath)
303-
.pipe(newFile)
303+
.pipe(newFile.createWriteStream(metadata))
304304
.on('error', callback)
305305
.on('complete', function() {
306306
callback(null, newFile);

lib/storage/file.js

Lines changed: 118 additions & 164 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,8 @@
2121
'use strict';
2222

2323
var crypto = require('crypto');
24-
var Duplexify = require('duplexify');
24+
var duplexify = require('duplexify');
2525
var extend = require('extend');
26-
var nodeutil = require('util');
27-
var through = require('through2');
2826
var uuid = require('node-uuid');
2927

3028
/**
@@ -49,60 +47,15 @@ var STORAGE_UPLOAD_BASE_URL = 'https://www.googleapis.com/upload/storage/v1/b';
4947
* extra call to `setMetadata`.
5048
*/
5149
/**
52-
* A File object is created from your Bucket object, using
53-
* {module:storage/bucket#file}. File objects are duplex streams, which means
54-
* they can be read from and written to. No API requests are made until you
55-
* attempt to read or write from the file, by attaching a Readable or Writable
56-
* stream. This means you can create a file on the fly, but you will get an
57-
* error if you try to read from a file in your bucket that does not exist.
58-
*
59-
* For more help with using and understanding Node.js streams, see the
60-
* [Stream documentation]{@link http://nodejs.org/api/stream.html}.
50+
* A File object is created from your Bucket object using
51+
* {module:storage/bucket#file}.
6152
*
6253
* @alias module:storage/file
6354
* @constructor
6455
*
6556
* @example
66-
* //-
67-
* // <h4>Downloading a File</h4>
68-
* //
69-
* // The example below demonstrates how we can reference a remote file, then
70-
* // pipe its contents to a local file. This is effectively creating a local
71-
* // backup of your remote data.
72-
* //-
73-
* var fs = require('fs');
74-
* myBucket.file('image.png')
75-
* .pipe(fs.createWriteStream('/Users/stephen/Photos/image.png'))
76-
* .on('error', function(err) {});
77-
*
78-
* //-
79-
* // <h4>Uploading a File</h4>
80-
* //
81-
* // Now, consider a case where we want to upload a file to your bucket. You
82-
* // have the option of using {module:storage/bucket#upload}, but that is just
83-
* // a convenience method which will do the following.
84-
* //-
85-
* var fs = require('fs');
86-
* fs.createReadStream('/Users/stephen/Photos/birthday-at-the-zoo/panda.jpg')
87-
* .pipe(myBucket.file('panda.jpg'))
88-
* .on('error', function(err) {});
89-
*
90-
* //-
91-
* // <h4>Uploading a File with Metadata</h4>
92-
* //
93-
* // One last case you may run into is when you want to upload a file to your
94-
* // bucket and set its metadata at the same time. Like above, you can use
95-
* // {module:storage/bucket#upload} to do this, which again, is just a wrapper
96-
* // around the following.
97-
* //-
98-
* var fs = require('fs');
99-
* fs.createReadStream('/Users/stephen/Photos/birthday-at-the-zoo/panda.jpg')
100-
* .pipe(myBucket.file('panda.jpg', { contentType: 'image/jpeg' }))
101-
* .on('error', function(err) {});
10257
*/
10358
function File(bucket, name, metadata) {
104-
Duplexify.call(this);
105-
10659
if (!name) {
10760
throw Error('A file name must be specified.');
10861
}
@@ -114,16 +67,8 @@ function File(bucket, name, metadata) {
11467
enumerable: true,
11568
value: name
11669
});
117-
118-
this.events_ = {
119-
_read: this._read,
120-
_write: this._write
121-
};
122-
this.bindEvents_();
12370
}
12471

125-
nodeutil.inherits(File, Duplexify);
126-
12772
/**
12873
* Copy this file to another file. By default, this will copy the file to the
12974
* same bucket, but you can choose to copy it to another Bucket by providing
@@ -133,7 +78,6 @@ nodeutil.inherits(File, Duplexify);
13378
*
13479
* @param {string|{module:storage/bucket}|{module:storage/file}} destination -
13580
* Destination file.
136-
* @param {object=} metadata - Destination file metadata object.
13781
* @param {function=} callback - The callback function.
13882
*
13983
* @example
@@ -230,6 +174,115 @@ File.prototype.copy = function(destination, callback) {
230174
});
231175
};
232176

177+
178+
/**
179+
* Create a readable stream to read the contents of the remote file. It can be
180+
* piped to a writable stream or listened to for 'data' events to read a file's
181+
* contents.
182+
*
183+
* @example
184+
* //-
185+
* // <h4>Downloading a File</h4>
186+
* //
187+
* // The example below demonstrates how we can reference a remote file, then
188+
* // pipe its contents to a local file. This is effectively creating a local
189+
* // backup of your remote data.
190+
* //-
191+
* var fs = require('fs');
192+
* var image = myBucket.file('image.png');
193+
*
194+
* image.createReadStream()
195+
* .pipe(fs.createWriteStream('/Users/stephen/Photos/image.png'))
196+
* .on('error', function(err) {});
197+
*/
198+
File.prototype.createReadStream = function() {
199+
var bucket = this.bucket;
200+
var dup = duplexify();
201+
function createAuthorizedReq(uri) {
202+
bucket.connection_.createAuthorizedReq({ uri: uri }, function(err, req) {
203+
if (err) {
204+
dup.emit('error', err);
205+
return;
206+
}
207+
dup.setReadable(bucket.connection_.requester(req));
208+
});
209+
}
210+
if (this.metadata.mediaLink) {
211+
createAuthorizedReq(this.metadata.mediaLink);
212+
} else {
213+
this.getMetadata(function(err, metadata) {
214+
if (err) {
215+
dup.emit('error', err);
216+
return;
217+
}
218+
createAuthorizedReq(metadata.mediaLink);
219+
});
220+
}
221+
return dup;
222+
};
223+
224+
/**
225+
* Create a writable stream to overwrite the contents of the file in your
226+
* bucket.
227+
*
228+
* A File object can also be used to create files for the first time.
229+
*
230+
* @param {object=} metadata - Set the metadata for this file.
231+
*
232+
* @example
233+
* //-
234+
* // <h4>Uploading a File</h4>
235+
* //
236+
* // Now, consider a case where we want to upload a file to your bucket. You
237+
* // have the option of using {module:storage/bucket#upload}, but that is just
238+
* // a convenience method which will do the following.
239+
* //-
240+
* var fs = require('fs');
241+
* var image = myBucket.file('image.png');
242+
*
243+
* fs.createReadStream('/Users/stephen/Photos/birthday-at-the-zoo/panda.jpg')
244+
* .pipe(image.createWriteStream())
245+
* .on('error', function(err) {});
246+
*
247+
* //-
248+
* // <h4>Uploading a File with Metadata</h4>
249+
* //
250+
* // One last case you may run into is when you want to upload a file to your
251+
* // bucket and set its metadata at the same time. Like above, you can use
252+
* // {module:storage/bucket#upload} to do this, which is just a wrapper around
253+
* // the following.
254+
* //-
255+
* var fs = require('fs');
256+
* var image = myBucket.file('image.png');
257+
*
258+
* fs.createReadStream('/Users/stephen/Photos/birthday-at-the-zoo/panda.jpg')
259+
* .pipe(image.createWriteStream({ contentType: 'image/jpeg' }))
260+
* .on('error', function(err) {});
261+
*/
262+
File.prototype.createWriteStream = function(metadata) {
263+
var that = this;
264+
var dup = duplexify();
265+
this.getWritableStream_(metadata, function(err, writable) {
266+
if (err) {
267+
dup.emit('error', err);
268+
return;
269+
}
270+
writable.on('complete', function(res) {
271+
util.handleResp(null, res, res.body, function(err, data) {
272+
if (err) {
273+
dup.emit('error', err);
274+
return;
275+
}
276+
that.metadata = data;
277+
dup.emit('complete', data);
278+
});
279+
});
280+
dup.setWritable(writable);
281+
dup.pipe(writable);
282+
});
283+
return dup;
284+
};
285+
233286
/**
234287
* Delete the file.
235288
*
@@ -245,7 +298,6 @@ File.prototype.delete = function(callback) {
245298
callback(err);
246299
return;
247300
}
248-
this.removeAllListeners();
249301
callback();
250302
}.bind(this));
251303
};
@@ -378,61 +430,19 @@ File.prototype.setMetadata = function(metadata, callback) {
378430
* out.
379431
*/
380432

381-
/**
382-
* Set up and tear down listeners for a new stream. First, overwrite the _read
383-
* and _write methods for the first time a stream is accessed. This causes us to
384-
* fetch an authorized connection before opening the pipe for more data. The
385-
* data is not buffered into memory, rather, it ceases pulling until we say so.
386-
*
387-
* This method is called after a complete event, when we also destroy the
388-
* Duplexify stream and re-apply its constructor to our file instance.
389-
*
390-
* @private
391-
*/
392-
File.prototype.bindEvents_ = function() {
393-
var that = this;
394-
395-
var _read = this.events_._read;
396-
this._read = function() {
397-
that._read = _read.bind(that);
398-
that.setReadableStream_();
399-
_read.apply(that, util.toArray(arguments));
400-
};
401-
402-
var _write = this.events_._write;
403-
this._write = function() {
404-
that._write = _write.bind(that);
405-
that.setWritableStream_();
406-
_write.apply(that, util.toArray(arguments));
407-
};
408-
409-
this.on('error', function() {
410-
that.emit('end');
411-
});
412-
413-
this.on('complete', function() {
414-
that.emit('end');
415-
});
416-
417-
this.on('end', function() {
418-
that.removeAllListeners();
419-
that.destroy();
420-
Duplexify.call(that);
421-
that.bindEvents_();
422-
});
423-
};
424-
425433
/**
426434
* Get a remote stream to begin piping a readable stream to.
427435
*
428436
* @private
429437
*/
430-
File.prototype.getWritableStream_ = function(callback) {
438+
File.prototype.getWritableStream_ = function(metadata, callback) {
439+
if (!callback) {
440+
callback = metadata;
441+
metadata = {};
442+
}
431443
var that = this;
432444
var boundary = uuid.v4();
433-
var metadata = extend({
434-
contentType: 'text/plain'
435-
}, this.metadata);
445+
metadata = extend({ contentType: 'text/plain' }, metadata);
436446
this.bucket.connection_.createAuthorizedReq({
437447
method: 'POST',
438448
uri: util.format('{base}/{bucket}/o', {
@@ -469,60 +479,4 @@ File.prototype.getWritableStream_ = function(callback) {
469479
});
470480
};
471481

472-
/**
473-
* Set the readable stream for Duplexify after making an authorized connection.
474-
*
475-
* @private
476-
*/
477-
File.prototype.setReadableStream_ = function() {
478-
var that = this;
479-
var bucket = this.bucket;
480-
function createAuthorizedReq(uri) {
481-
bucket.connection_.createAuthorizedReq({ uri: uri }, function(err, req) {
482-
if (err) {
483-
that.emit('error', err);
484-
return;
485-
}
486-
that.setReadable(bucket.connection_.requester(req));
487-
});
488-
}
489-
if (this.metadata.mediaLink) {
490-
createAuthorizedReq(this.metadata.mediaLink);
491-
} else {
492-
this.getMetadata(function(err, metadata) {
493-
if (err) {
494-
that.emit('error', err);
495-
return;
496-
}
497-
createAuthorizedReq(metadata.mediaLink);
498-
});
499-
}
500-
};
501-
502-
/**
503-
* Set the writable stream for Duplexify after making an authorized connection.
504-
*
505-
* @private
506-
*/
507-
File.prototype.setWritableStream_ = function() {
508-
var that = this;
509-
this.getWritableStream_(function(err, writable) {
510-
if (err) {
511-
that.emit('error', err);
512-
return;
513-
}
514-
writable.on('complete', function(res) {
515-
util.handleResp(null, res, res.body, function(err, data) {
516-
if (err) {
517-
that.emit('error', err);
518-
return;
519-
}
520-
that.emit('complete', data);
521-
});
522-
});
523-
that.setWritable(writable);
524-
through().pipe(writable);
525-
});
526-
};
527-
528482
module.exports = File;

0 commit comments

Comments
 (0)