2323var async = require ( 'async' ) ;
2424var extend = require ( 'extend' ) ;
2525var fs = require ( 'fs' ) ;
26+ var globby = require ( 'globby' ) ;
2627var mime = require ( 'mime-types' ) ;
2728var path = require ( 'path' ) ;
2829
@@ -44,6 +45,12 @@ var File = require('./file.js');
4445 */
4546var util = require ( '../common/util.js' ) ;
4647
48+ /**
49+ * @const {number}
50+ * @private
51+ */
52+ var MAX_PARALLEL_UPLOADS = 5 ;
53+
4754/**
4855 * @const {string}
4956 * @private
@@ -664,8 +671,8 @@ Bucket.prototype.setMetadata = function(metadata, callback) {
664671 * will be uploaded to the File object's bucket and under the File object's
665672 * name. Lastly, when this argument is omitted, the file is uploaded to your
666673 * bucket using the name of the local file.
667- * @param {object= } options.metadata - Metadata to set for your file.
668- * @param {boolean= } options.resumable - Force a resumable upload. (default:
674+ * @param {object } options.metadata - Metadata to set for your file.
675+ * @param {boolean } options.resumable - Force a resumable upload. (default:
669676 * true for files larger than 5MB). Read more about resumable uploads
670677 * [here](http://goo.gl/1JWqCF). NOTE: This behavior is only possible with
671678 * this method, and not {module:storage/file#createWriteStream}. When
@@ -730,59 +737,141 @@ Bucket.prototype.setMetadata = function(metadata, callback) {
730737 * });
731738 */
732739Bucket . prototype . upload = function ( localPath , options , callback ) {
740+ var self = this ;
741+
742+ var errors = [ ] ;
743+ var files = [ ] ;
744+
733745 if ( util . is ( options , 'function' ) ) {
734746 callback = options ;
735747 options = { } ;
736748 }
737749
738- var newFile ;
739- if ( options . destination instanceof File ) {
740- newFile = options . destination ;
741- } else if ( util . is ( options . destination , 'string' ) ) {
742- // Use the string as the name of the file.
743- newFile = this . file ( options . destination ) ;
744- } else {
745- // Resort to using the name of the incoming file.
746- newFile = this . file ( path . basename ( localPath ) ) ;
747- }
750+ options = options || { } ;
748751
749- var metadata = options . metadata || { } ;
750- var contentType = mime . contentType ( path . basename ( localPath ) ) ;
752+ var globOptions = extend ( { } , options . globOptions , { nodir : true } ) ;
751753
752- if ( contentType && ! metadata . contentType ) {
753- metadata . contentType = contentType ;
754- }
754+ globby ( localPath , globOptions , function ( err , filePaths ) {
755+ if ( err ) {
756+ callback ( err ) ;
757+ return ;
758+ }
755759
756- var resumable ;
757- if ( util . is ( options . resumable , 'boolean' ) ) {
758- resumable = options . resumable ;
759- upload ( ) ;
760- } else {
761- // Determine if the upload should be resumable if it's over the threshold.
762- fs . stat ( localPath , function ( err , fd ) {
763- if ( err ) {
764- callback ( err ) ;
765- return ;
766- }
760+ var uploadFileFns = filePaths . map ( function ( filePath ) {
761+ return function ( done ) {
762+ var fileName = path . basename ( filePath ) ;
763+
764+ if ( options . basePath ) {
765+ fileName = path . relative ( options . basePath , filePath ) ;
766+ }
767+
768+ var opts = extend ( { destination : fileName } , options ) ;
767769
768- resumable = fd . size > RESUMABLE_THRESHOLD ;
770+ self . uploadFile ( filePath , opts , function ( err , file ) {
771+ if ( err ) {
772+ errors . push ( err ) ;
773+ } else {
774+ files . push ( file ) ;
775+ }
769776
770- upload ( ) ;
777+ done ( options . force ? null : err || null ) ;
778+ } ) ;
779+ } ;
780+ } ) ;
781+
782+ async . parallelLimit ( uploadFileFns , MAX_PARALLEL_UPLOADS , function ( ) {
783+ if ( options . force ) {
784+ callback ( errors , files ) ;
785+ } else {
786+ callback ( errors [ 0 ] , files ) ;
787+ }
771788 } ) ;
789+ } ) ;
790+ } ;
791+
792+ Bucket . prototype . uploadDirectory = function ( directoryPath , options , callback ) {
793+ if ( util . is ( options , 'function' ) ) {
794+ callback = options ;
795+ options = { } ;
796+ }
797+
798+ options = options || { } ;
799+ options . basePath = directoryPath ;
800+
801+ this . upload ( path . join ( directoryPath , '**/*' ) , options , callback ) ;
802+ } ;
803+
804+ Bucket . prototype . uploadFile = function ( filePath , options , callback ) {
805+ var self = this ;
806+
807+ if ( util . is ( options , 'function' ) ) {
808+ callback = options ;
809+ options = { } ;
772810 }
773811
774- function upload ( ) {
775- fs . createReadStream ( localPath )
776- . pipe ( newFile . createWriteStream ( {
777- validation : options . validation ,
778- resumable : resumable ,
779- metadata : metadata
780- } ) )
781- . on ( 'error' , callback )
782- . on ( 'complete' , function ( ) {
783- callback ( null , newFile ) ;
812+ options = options || { } ;
813+
814+ if ( ! util . is ( options . resumable , 'boolean' ) ) {
815+ // User didn't specify a preference of resumable or simple upload. Check the
816+ // file's size to determine which to use.
817+ if ( ! util . is ( options . size , 'number' ) ) {
818+ fs . stat ( filePath , function ( err , stats ) {
819+ if ( err ) {
820+ callback ( err ) ;
821+ return ;
822+ }
823+
824+ options . size = stats . size ;
825+ self . uploadFile ( filePath , options , callback ) ;
784826 } ) ;
827+ return ;
828+ }
829+
830+ options . resumable = options . size > RESUMABLE_THRESHOLD ;
831+ }
832+
833+ if ( util . is ( options . destination , 'string' ) ) {
834+ options . destination = this . file ( options . destination ) ;
835+ }
836+
837+ if ( ! options . destination ) {
838+ options . destination = this . file ( path . basename ( filePath ) ) ;
785839 }
840+
841+ this . uploadFile_ ( filePath , options , callback ) ;
842+ } ;
843+
844+ /**
845+ * Same signature as {module:storage/bucket#upload}, but simply uploads the file
846+ * after determining its name.
847+ *
848+ * The `upload` function is a public-facing, pre-processor which can read files
849+ * from a directory, then send them to this method.
850+ *
851+ * @private
852+ * @borrows {module:storage/bucket#upload } as uploadFile_
853+ *
854+ * @param {module:storage/file } options.destination - File destination.
855+ */
856+ Bucket . prototype . uploadFile_ = function ( filePath , options , callback ) {
857+ var file = options . destination ;
858+ var metadata = options . metadata || { } ;
859+ var contentType = mime . contentType ( path . basename ( filePath ) ) ;
860+
861+ if ( contentType && ! metadata . contentType ) {
862+ metadata . contentType = contentType ;
863+ }
864+
865+ fs . createReadStream ( filePath )
866+ . pipe ( file . createWriteStream ( {
867+ validation : options . validation ,
868+ resumable : options . resumable ,
869+ metadata : metadata
870+ } ) )
871+ . on ( 'error' , callback )
872+ . on ( 'complete' , function ( ) {
873+ callback ( null , file ) ;
874+ } ) ;
786875} ;
787876
788877/**
0 commit comments