23
23
var async = require ( 'async' ) ;
24
24
var extend = require ( 'extend' ) ;
25
25
var fs = require ( 'fs' ) ;
26
+ var globby = require ( 'globby' ) ;
26
27
var mime = require ( 'mime-types' ) ;
27
28
var path = require ( 'path' ) ;
28
29
@@ -44,6 +45,12 @@ var File = require('./file.js');
44
45
*/
45
46
var util = require ( '../common/util.js' ) ;
46
47
48
+ /**
49
+ * @const {number}
50
+ * @private
51
+ */
52
+ var MAX_PARALLEL_UPLOADS = 5 ;
53
+
47
54
/**
48
55
* @const {string}
49
56
* @private
@@ -664,8 +671,8 @@ Bucket.prototype.setMetadata = function(metadata, callback) {
664
671
* will be uploaded to the File object's bucket and under the File object's
665
672
* name. Lastly, when this argument is omitted, the file is uploaded to your
666
673
* 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:
669
676
* true for files larger than 5MB). Read more about resumable uploads
670
677
* [here](http://goo.gl/1JWqCF). NOTE: This behavior is only possible with
671
678
* this method, and not {module:storage/file#createWriteStream}. When
@@ -730,61 +737,141 @@ Bucket.prototype.setMetadata = function(metadata, callback) {
730
737
* });
731
738
*/
732
739
Bucket . prototype . upload = function ( localPath , options , callback ) {
740
+ var self = this ;
741
+
742
+ var errors = [ ] ;
743
+ var files = [ ] ;
744
+
733
745
if ( util . is ( options , 'function' ) ) {
734
746
callback = options ;
735
747
options = { } ;
736
748
}
737
749
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 || { } ;
748
751
749
- var metadata = options . metadata || { } ;
750
- var contentType = mime . contentType ( path . basename ( localPath ) ) ;
752
+ var globOptions = extend ( { } , options . globOptions , { nodir : true } ) ;
751
753
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
+ }
755
759
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 ) ;
767
769
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
+ }
769
776
770
- upload ( ) ;
777
+ done ( options . force ? null : err || null ) ;
778
+ } ) ;
779
+ } ;
771
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
+ }
788
+ } ) ;
789
+ } ) ;
790
+ } ;
791
+
792
+ Bucket . prototype . uploadDirectory = function ( directoryPath , options , callback ) {
793
+ if ( util . is ( options , 'function' ) ) {
794
+ callback = options ;
795
+ options = { } ;
772
796
}
773
797
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' , function ( err ) {
782
- callback ( err ) ;
783
- } )
784
- . on ( 'complete' , function ( ) {
785
- callback ( null , newFile ) ;
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 = { } ;
810
+ }
811
+
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 ) ;
786
826
} ) ;
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 ) ) ;
787
839
}
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
+ } ) ;
788
875
} ;
789
876
790
877
/**
0 commit comments