@@ -25,6 +25,8 @@ def initialize(options)
2525 @headers = options [ :headers ] || [ ]
2626 @remote_path = @remote_path . sub ( /^\/ / , '' ) # remove leading slash
2727 @pull_dir = options [ :dir ]
28+ @bust_cache_files = [ ]
29+ @thread_pool = [ ]
2830 connect
2931 end
3032
@@ -78,64 +80,61 @@ def connect
7880 # Write site files to the selected bucket
7981 #
8082 def write_files
81- puts "Writing #{ pluralize ( 'file' , site_files . size ) } :" if @verbose
82- files_to_invalidate = [ ]
83+ puts "Writing #{ pluralize ( 'file' , site_files . size ) } #{ " (sequential mode)" unless parallel_upload? } :" if @verbose
84+ @bust_cache_files = [ ]
85+
8386 site_files . each do |file |
84- s3_filename = remote_path ( file )
85- o = @bucket . objects [ s3_filename ]
86- file_with_options = get_file_with_metadata ( file , s3_filename ) ;
87-
88- begin
89- s3sum = o . etag . tr ( '"' , '' ) if o . exists?
90- rescue AWS ::S3 ::Errors ::NoSuchKey
91- s3sum = ""
87+ if parallel_upload?
88+ threaded { write_file file }
89+ else
90+ write_file file
9291 end
92+ end
9393
94- if @incremental && ( s3sum == Digest ::MD5 . file ( file ) . hexdigest )
95- if @verbose
96- puts "= #{ remote_path ( file ) } "
97- else
98- progress ( '=' )
99- end
100- else
101- o . write ( file_with_options )
102- files_to_invalidate . push ( file )
103- if @verbose
104- puts "+ #{ remote_path ( file ) } "
105- else
106- progress ( '+' )
107- end
108- end
94+ @thread_pool . each ( &:join )
95+ bust_cloudfront_cache
96+ end
97+
98+ def write_file file
99+ if write_file? file
100+ s3_upload_file file
101+ @bust_cache_files << file
102+ @verbose ? puts ( "+ #{ remote_path ( file ) } " ) : progress ( '+' )
103+ else
104+ @verbose ? puts ( "= #{ remote_path ( file ) } " ) : progress ( '=' )
109105 end
106+ end
110107
111- invalidate_cache ( files_to_invalidate ) unless @distro_id . nil?
108+ def s3_upload_file file
109+ s3_object ( file ) . write File . open ( file ) , s3_object_options ( file )
112110 end
113111
114- def invalidate_cache ( files )
112+ def bust_cloudfront_cache
113+ return if @distro_id . nil?
114+
115115 puts "Invalidating cache for #{ pluralize ( 'file' , site_files . size ) } " if @verbose
116116 @cloudfront . create_invalidation (
117117 distribution_id : @distro_id ,
118118 invalidation_batch :{
119119 paths :{
120- quantity : files . size ,
121- items : files . map { |file | "/" + remote_path ( file ) }
122- } ,
120+ quantity : @bust_cache_files . size ,
121+ items : @bust_cache_files . map { |file | "/" + remote_path ( file ) }
122+ } ,
123123 # String of 8 random chars to uniquely id this invalidation
124124 caller_reference : ( 0 ...8 ) . map { ( 'a' ..'z' ) . to_a [ rand ( 26 ) ] } . join
125125 }
126- ) unless files . empty?
126+ ) unless @bust_cache_files . empty?
127+ @bust_cache_files = [ ]
127128 end
128129
129- def get_file_with_metadata ( file , s3_filename )
130- file_with_options = {
131- :file => file ,
132- :acl => :public_read
133- }
130+ def s3_object_options ( file )
131+ s3_filename = remote_path file
132+ s3_options = { :acl => :public_read }
134133
135134 @headers . each do |conf |
136135 if conf . has_key? 'filename' and s3_filename . match ( conf [ 'filename' ] )
137136 if @verbose
138- puts "+ #{ remote_path ( file ) } matched pattern #{ conf [ 'filename' ] } "
137+ puts "+ #{ s3_filename } matched pattern #{ conf [ 'filename' ] } "
139138 end
140139
141140 if conf . has_key? 'expires'
@@ -151,24 +150,24 @@ def get_file_with_metadata(file, s3_filename)
151150 expireDate = ( Time . now + ( 60 * 60 * 24 * relative_days [ 1 ] . to_i ) ) . httpdate
152151 end
153152
154- file_with_options [ :expires ] = expireDate
153+ s3_options [ :expires ] = expireDate
155154 end
156155
157156 if conf . has_key? 'content_type'
158- file_with_options [ :content_type ] = conf [ 'content_type' ]
157+ s3_options [ :content_type ] = conf [ 'content_type' ]
159158 end
160159
161160 if conf . has_key? 'cache_control'
162- file_with_options [ :cache_control ] = conf [ 'cache_control' ]
161+ s3_options [ :cache_control ] = conf [ 'cache_control' ]
163162 end
164163
165164 if conf . has_key? 'content_encoding'
166- file_with_options [ :content_encoding ] = conf [ 'content_encoding' ]
165+ s3_options [ :content_encoding ] = conf [ 'content_encoding' ]
167166 end
168167 end
169168 end
170169
171- return file_with_options
170+ s3_options
172171 end
173172
174173 # Delete files from the bucket, to ensure a 1:1 match with site files
@@ -273,9 +272,34 @@ def self.default_config(options={})
273272#{ "verbose: #{ options [ :verbose ] || 'false' } " . ljust ( 40 ) } # Print out all file operations.
274273#{ "incremental: #{ options [ :incremental ] || 'false' } " . ljust ( 40 ) } # Only upload new/changed files
275274#{ "delete: #{ options [ :delete ] || 'false' } " . ljust ( 40 ) } # Remove files from destination which do not match source files.
275+ #{ "parallel: #{ options [ :parallel ] || 'true' } " . ljust ( 40 ) } # Speed up deployment by uploading files in parallel.
276276CONFIG
277277 end
278278
279+ protected
280+
281+ def write_file? file
282+ file_digest = Digest ::MD5 . file ( file ) . hexdigest
283+ o = s3_object file
284+ s3sum = o . etag . tr ( '"' , '' ) if o . exists?
285+ @incremental == false || s3sum . to_s != file_digest
286+ end
287+
288+ def s3_object file
289+ s3_filename = remote_path file
290+ @bucket . objects [ s3_filename ]
291+ end
292+
293+ def parallel_upload?
294+ @options [ :parallel ]
295+ end
296+
297+ def threaded &blk
298+ @thread_pool << Thread . new ( blk ) do |operation |
299+ operation . call
300+ end
301+ end
302+
279303 end
280304 end
281305end
0 commit comments