asset_sync 2.4.0 → 2.15.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/asset_sync.gemspec CHANGED
@@ -15,8 +15,6 @@ Gem::Specification.new do |s|
15
15
 
16
16
  s.license = 'MIT'
17
17
 
18
- s.rubyforge_project = "asset_sync"
19
-
20
18
  s.add_dependency("fog-core")
21
19
  s.add_dependency('unf')
22
20
  s.add_dependency('activemodel', ">= 4.1.0")
@@ -24,14 +22,19 @@ Gem::Specification.new do |s|
24
22
 
25
23
  s.add_development_dependency "rspec"
26
24
  s.add_development_dependency "bundler"
27
- s.add_development_dependency "jeweler"
25
+ s.add_development_dependency "coveralls", ">= 0.7"
26
+
27
+ s.add_development_dependency('mime-types', ">= 3.0")
28
28
 
29
29
  s.add_development_dependency "fog-aws"
30
- s.add_development_dependency "fog-azure-rm"
30
+ s.add_development_dependency "gitlab-fog-azure-rm"
31
+ s.add_development_dependency "fog-backblaze"
31
32
 
32
33
  s.add_development_dependency "uglifier"
33
34
  s.add_development_dependency "appraisal"
34
35
 
36
+ s.add_development_dependency "gem-release"
37
+
35
38
  s.files = `git ls-files`.split("\n")
36
39
  s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
37
40
  s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
@@ -5,6 +5,6 @@ source "https://rubygems.org"
5
5
  gem "rcov", platforms: :mri_18, group: [:development, :test]
6
6
  gem "simplecov", platforms: [:jruby, :mri_19, :ruby_19, :mri_20, :rbx], group: [:development, :test], require: false
7
7
  gem "jruby-openssl", platform: :jruby
8
- gem "rails", "~> 4.1.0"
8
+ gem "rails", "~> 5.2.0"
9
9
 
10
10
  gemspec path: "../"
@@ -5,6 +5,6 @@ source "https://rubygems.org"
5
5
  gem "rcov", platforms: :mri_18, group: [:development, :test]
6
6
  gem "simplecov", platforms: [:jruby, :mri_19, :ruby_19, :mri_20, :rbx], group: [:development, :test], require: false
7
7
  gem "jruby-openssl", platform: :jruby
8
- gem "rails", "~> 4.2.0"
8
+ gem "rails", "~> 6.0.0"
9
9
 
10
10
  gemspec path: "../"
@@ -5,6 +5,6 @@ source "https://rubygems.org"
5
5
  gem "rcov", platforms: :mri_18, group: [:development, :test]
6
6
  gem "simplecov", platforms: [:jruby, :mri_19, :ruby_19, :mri_20, :rbx], group: [:development, :test], require: false
7
7
  gem "jruby-openssl", platform: :jruby
8
- gem "rails", "~> 5.0.0"
8
+ gem "rails", "~> 6.1.0"
9
9
 
10
10
  gemspec path: "../"
@@ -1,3 +1,5 @@
1
+ require "yaml"
2
+
1
3
  module AssetSync
2
4
 
3
5
  class << self
@@ -60,6 +62,14 @@ module AssetSync
60
62
  stdout.puts msg unless config.log_silently?
61
63
  end
62
64
 
65
+ def load_yaml(yaml)
66
+ if YAML.respond_to?(:unsafe_load)
67
+ YAML.unsafe_load(yaml)
68
+ else
69
+ YAML.load(yaml)
70
+ end
71
+ end
72
+
63
73
  def enabled?
64
74
  config.enabled?
65
75
  end
@@ -1,6 +1,7 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "active_model"
2
4
  require "erb"
3
- require "yaml"
4
5
 
5
6
  module AssetSync
6
7
  class Config
@@ -17,7 +18,6 @@ module AssetSync
17
18
  attr_accessor :always_upload
18
19
  attr_accessor :ignored_files
19
20
  attr_accessor :prefix
20
- attr_accessor :public_path
21
21
  attr_accessor :enabled
22
22
  attr_accessor :custom_headers
23
23
  attr_accessor :run_on_precompile
@@ -25,15 +25,28 @@ module AssetSync
25
25
  attr_accessor :cdn_distribution_id
26
26
  attr_accessor :cache_asset_regexps
27
27
  attr_accessor :include_manifest
28
+ attr_accessor :concurrent_uploads
29
+ attr_accessor :concurrent_uploads_max_threads
30
+ attr_accessor :remote_file_list_cache_file_path
28
31
 
29
32
  # FOG configuration
30
33
  attr_accessor :fog_provider # Currently Supported ['AWS', 'Rackspace']
31
34
  attr_accessor :fog_directory # e.g. 'the-bucket-name'
32
35
  attr_accessor :fog_region # e.g. 'eu-west-1'
36
+ attr_reader :fog_public # e.g. true, false, "default"
33
37
 
34
38
  # Amazon AWS
35
- attr_accessor :aws_access_key_id, :aws_secret_access_key, :aws_reduced_redundancy, :aws_iam_roles, :aws_signature_version
39
+ attr_accessor :aws_access_key_id
40
+ attr_accessor :aws_secret_access_key
41
+ attr_accessor :aws_session_token
42
+ attr_accessor :aws_reduced_redundancy
43
+ attr_accessor :aws_iam_roles
44
+ attr_accessor :aws_signature_version
45
+ attr_accessor :aws_acl
46
+
47
+ # Fog
36
48
  attr_accessor :fog_host # e.g. 's3.amazonaws.com'
49
+ attr_accessor :fog_port # e.g. '9000'
37
50
  attr_accessor :fog_path_style # e.g. true
38
51
  attr_accessor :fog_scheme # e.g. 'http'
39
52
 
@@ -41,12 +54,20 @@ module AssetSync
41
54
  attr_accessor :rackspace_username, :rackspace_api_key, :rackspace_auth_url
42
55
 
43
56
  # Google Storage
44
- attr_accessor :google_storage_secret_access_key, :google_storage_access_key_id
57
+ attr_accessor :google_storage_secret_access_key, :google_storage_access_key_id # when using S3 interop
58
+ attr_accessor :google_json_key_location # when using service accounts
59
+ attr_accessor :google_json_key_string # when using service accounts
60
+ attr_accessor :google_project # when using service accounts
45
61
 
46
62
  # Azure Blob with Fog::AzureRM
47
63
  attr_accessor :azure_storage_account_name
48
64
  attr_accessor :azure_storage_access_key
49
65
 
66
+ # Backblaze B2 with Fog::Backblaze
67
+ attr_accessor :b2_key_id
68
+ attr_accessor :b2_key_token
69
+ attr_accessor :b2_bucket_id
70
+
50
71
  validates :existing_remote_files, :inclusion => { :in => %w(keep delete ignore) }
51
72
 
52
73
  validates :fog_provider, :presence => true
@@ -56,11 +77,19 @@ module AssetSync
56
77
  validates :aws_secret_access_key, :presence => true, :if => proc {aws? && !aws_iam?}
57
78
  validates :rackspace_username, :presence => true, :if => :rackspace?
58
79
  validates :rackspace_api_key, :presence => true, :if => :rackspace?
59
- validates :google_storage_secret_access_key, :presence => true, :if => :google?
60
- validates :google_storage_access_key_id, :presence => true, :if => :google?
80
+ validates :google_storage_secret_access_key, :presence => true, :if => :google_interop?
81
+ validates :google_storage_access_key_id, :presence => true, :if => :google_interop?
82
+ validates :google_project, :presence => true, :if => :google_service_account?
83
+ validate(:if => :google_service_account?) do
84
+ unless google_json_key_location.present? || google_json_key_string.present?
85
+ errors.add(:base, 'must provide either google_json_key_location or google_json_key_string if using Google service account')
86
+ end
87
+ end
88
+ validates :concurrent_uploads, :inclusion => { :in => [true, false] }
61
89
 
62
90
  def initialize
63
91
  self.fog_region = nil
92
+ self.fog_public = true
64
93
  self.existing_remote_files = 'keep'
65
94
  self.gzip_compression = false
66
95
  self.manifest = false
@@ -75,6 +104,9 @@ module AssetSync
75
104
  self.invalidate = []
76
105
  self.cache_asset_regexps = []
77
106
  self.include_manifest = false
107
+ self.concurrent_uploads = false
108
+ self.concurrent_uploads_max_threads = 10
109
+ self.remote_file_list_cache_file_path = nil
78
110
  @additional_local_file_paths_procs = []
79
111
 
80
112
  load_yml! if defined?(::Rails) && yml_exists?
@@ -126,10 +158,22 @@ module AssetSync
126
158
  fog_provider =~ /google/i
127
159
  end
128
160
 
161
+ def google_interop?
162
+ google? && google_json_key_location.nil? && google_json_key_string.nil?
163
+ end
164
+
165
+ def google_service_account?
166
+ google? && (google_json_key_location || google_json_key_string)
167
+ end
168
+
129
169
  def azure_rm?
130
170
  fog_provider =~ /azurerm/i
131
171
  end
132
172
 
173
+ def backblaze?
174
+ fog_provider =~ /backblaze/i
175
+ end
176
+
133
177
  def cache_asset_regexp=(cache_asset_regexp)
134
178
  self.cache_asset_regexps = [cache_asset_regexp]
135
179
  end
@@ -139,7 +183,7 @@ module AssetSync
139
183
  end
140
184
 
141
185
  def yml
142
- @yml ||= ::YAML.load(::ERB.new(IO.read(yml_path)).result)[::Rails.env] || {}
186
+ @yml ||= ::AssetSync.load_yaml(::ERB.new(IO.read(yml_path)).result)[::Rails.env] || {}
143
187
  end
144
188
 
145
189
  def yml_path
@@ -155,40 +199,68 @@ module AssetSync
155
199
  @public_path || ::Rails.public_path
156
200
  end
157
201
 
202
+ def public_path=(path)
203
+ # Generate absolute path even when relative path passed in
204
+ # Required for generating relative sprockets manifest path
205
+ pathname = Pathname(path)
206
+ @public_path = if pathname.absolute?
207
+ pathname
208
+ elsif defined?(::Rails.root)
209
+ ::Rails.root.join(pathname)
210
+ else
211
+ Pathname(::Dir.pwd).join(pathname)
212
+ end
213
+ end
214
+
158
215
  def load_yml!
159
216
  self.enabled = yml["enabled"] if yml.has_key?('enabled')
160
217
  self.fog_provider = yml["fog_provider"]
161
218
  self.fog_host = yml["fog_host"]
219
+ self.fog_port = yml["fog_port"]
162
220
  self.fog_directory = yml["fog_directory"]
163
221
  self.fog_region = yml["fog_region"]
222
+ self.fog_public = yml["fog_public"] if yml.has_key?("fog_public")
164
223
  self.fog_path_style = yml["fog_path_style"]
165
224
  self.fog_scheme = yml["fog_scheme"]
166
225
  self.aws_access_key_id = yml["aws_access_key_id"]
167
226
  self.aws_secret_access_key = yml["aws_secret_access_key"]
227
+ self.aws_session_token = yml["aws_session_token"] if yml.has_key?("aws_session_token")
168
228
  self.aws_reduced_redundancy = yml["aws_reduced_redundancy"]
169
229
  self.aws_iam_roles = yml["aws_iam_roles"]
170
230
  self.aws_signature_version = yml["aws_signature_version"]
231
+ self.aws_acl = yml["aws_acl"]
171
232
  self.rackspace_username = yml["rackspace_username"]
172
233
  self.rackspace_auth_url = yml["rackspace_auth_url"] if yml.has_key?("rackspace_auth_url")
173
234
  self.rackspace_api_key = yml["rackspace_api_key"]
174
- self.google_storage_secret_access_key = yml["google_storage_secret_access_key"]
175
- self.google_storage_access_key_id = yml["google_storage_access_key_id"]
235
+ self.google_json_key_location = yml["google_json_key_location"] if yml.has_key?("google_json_key_location")
236
+ self.google_project = yml["google_project"] if yml.has_key?("google_project")
237
+ self.google_storage_secret_access_key = yml["google_storage_secret_access_key"] if yml.has_key?("google_storage_secret_access_key")
238
+ self.google_storage_access_key_id = yml["google_storage_access_key_id"] if yml.has_key?("google_storage_access_key_id")
239
+ self.google_json_key_string = yml["google_json_key_string"] if yml.has_key?("google_json_key_string")
176
240
  self.existing_remote_files = yml["existing_remote_files"] if yml.has_key?("existing_remote_files")
177
241
  self.gzip_compression = yml["gzip_compression"] if yml.has_key?("gzip_compression")
178
242
  self.manifest = yml["manifest"] if yml.has_key?("manifest")
179
243
  self.fail_silently = yml["fail_silently"] if yml.has_key?("fail_silently")
244
+ self.log_silently = yml["log_silently"] if yml.has_key?("log_silently")
180
245
  self.always_upload = yml["always_upload"] if yml.has_key?("always_upload")
181
246
  self.ignored_files = yml["ignored_files"] if yml.has_key?("ignored_files")
182
- self.custom_headers = yml["custom_headers"] if yml.has_key?("custom_headers")
247
+ self.custom_headers = yml["custom_headers"] if yml.has_key?("custom_headers")
183
248
  self.run_on_precompile = yml["run_on_precompile"] if yml.has_key?("run_on_precompile")
184
249
  self.invalidate = yml["invalidate"] if yml.has_key?("invalidate")
185
250
  self.cdn_distribution_id = yml['cdn_distribution_id'] if yml.has_key?("cdn_distribution_id")
186
251
  self.cache_asset_regexps = yml['cache_asset_regexps'] if yml.has_key?("cache_asset_regexps")
187
252
  self.include_manifest = yml['include_manifest'] if yml.has_key?("include_manifest")
253
+ self.concurrent_uploads = yml['concurrent_uploads'] if yml.has_key?('concurrent_uploads')
254
+ self.concurrent_uploads_max_threads = yml['concurrent_uploads_max_threads'] if yml.has_key?('concurrent_uploads_max_threads')
255
+ self.remote_file_list_cache_file_path = yml['remote_file_list_cache_file_path'] if yml.has_key?('remote_file_list_cache_file_path')
188
256
 
189
257
  self.azure_storage_account_name = yml['azure_storage_account_name'] if yml.has_key?("azure_storage_account_name")
190
258
  self.azure_storage_access_key = yml['azure_storage_access_key'] if yml.has_key?("azure_storage_access_key")
191
259
 
260
+ self.b2_key_id = yml['b2_key_id'] if yml.has_key?("b2_key_id")
261
+ self.b2_key_token = yml['b2_key_token'] if yml.has_key?("b2_key_token")
262
+ self.b2_bucket_id = yml['b2_bucket_id'] if yml.has_key?("b2_bucket_id")
263
+
192
264
  # TODO deprecate the other old style config settings. FML.
193
265
  self.aws_access_key_id = yml["aws_access_key"] if yml.has_key?("aws_access_key")
194
266
  self.aws_secret_access_key = yml["aws_access_secret"] if yml.has_key?("aws_access_secret")
@@ -217,8 +289,10 @@ module AssetSync
217
289
  :aws_access_key_id => aws_access_key_id,
218
290
  :aws_secret_access_key => aws_secret_access_key
219
291
  })
292
+ options.merge!({:aws_session_token => aws_session_token}) if aws_session_token
220
293
  end
221
294
  options.merge!({:host => fog_host}) if fog_host
295
+ options.merge!({:port => fog_port}) if fog_port
222
296
  options.merge!({:scheme => fog_scheme}) if fog_scheme
223
297
  options.merge!({:aws_signature_version => aws_signature_version}) if aws_signature_version
224
298
  options.merge!({:path_style => fog_path_style}) if fog_path_style
@@ -231,10 +305,16 @@ module AssetSync
231
305
  options.merge!({ :rackspace_region => fog_region }) if fog_region
232
306
  options.merge!({ :rackspace_auth_url => rackspace_auth_url }) if rackspace_auth_url
233
307
  elsif google?
234
- options.merge!({
235
- :google_storage_secret_access_key => google_storage_secret_access_key,
236
- :google_storage_access_key_id => google_storage_access_key_id
237
- })
308
+ if google_json_key_location
309
+ options.merge!({:google_json_key_location => google_json_key_location, :google_project => google_project})
310
+ elsif google_json_key_string
311
+ options.merge!({:google_json_key_string => google_json_key_string, :google_project => google_project})
312
+ else
313
+ options.merge!({
314
+ :google_storage_secret_access_key => google_storage_secret_access_key,
315
+ :google_storage_access_key_id => google_storage_access_key_id
316
+ })
317
+ end
238
318
  options.merge!({:region => fog_region}) if fog_region
239
319
  elsif azure_rm?
240
320
  require 'fog/azurerm'
@@ -243,11 +323,18 @@ module AssetSync
243
323
  :azure_storage_access_key => azure_storage_access_key,
244
324
  })
245
325
  options.merge!({:environment => fog_region}) if fog_region
326
+ elsif backblaze?
327
+ require 'fog/backblaze'
328
+ options.merge!({
329
+ :b2_key_id => b2_key_id,
330
+ :b2_key_token => b2_key_token,
331
+ :b2_bucket_id => b2_bucket_id,
332
+ })
246
333
  else
247
334
  raise ArgumentError, "AssetSync Unknown provider: #{fog_provider} only AWS, Rackspace and Google are supported currently."
248
335
  end
249
336
 
250
- return options
337
+ options
251
338
  end
252
339
 
253
340
  # @api
@@ -268,6 +355,15 @@ module AssetSync
268
355
  end
269
356
  end
270
357
 
358
+ #@api
359
+ def file_ext_to_mime_type_overrides
360
+ @file_ext_to_mime_type_overrides ||= FileExtToMimeTypeOverrides.new
361
+ end
362
+
363
+ def fog_public=(new_val)
364
+ @fog_public = FogPublicValue.new(new_val)
365
+ end
366
+
271
367
  private
272
368
 
273
369
  # This is a proc to get additional local files paths
@@ -277,5 +373,57 @@ module AssetSync
277
373
  def default_manifest_directory
278
374
  File.join(::Rails.public_path, assets_prefix)
279
375
  end
376
+
377
+
378
+ # @api private
379
+ class FileExtToMimeTypeOverrides
380
+ def initialize
381
+ # The default is to prevent new mime type `application/ecmascript` to be returned
382
+ # which disables compression on some CDNs
383
+ @overrides = {
384
+ "js" => "application/javascript",
385
+ }
386
+ end
387
+
388
+ # @api
389
+ def add(ext, mime_type)
390
+ # Symbol / Mime type object might be passed in
391
+ # But we want strings only
392
+ @overrides.store(
393
+ ext.to_s, mime_type.to_s,
394
+ )
395
+ end
396
+
397
+ # @api
398
+ def clear
399
+ @overrides = {}
400
+ end
401
+
402
+
403
+ # @api private
404
+ def key?(key)
405
+ @overrides.key?(key)
406
+ end
407
+
408
+ # @api private
409
+ def fetch(key)
410
+ @overrides.fetch(key)
411
+ end
412
+ end
413
+
414
+ # @api private
415
+ class FogPublicValue
416
+ def initialize(val)
417
+ @value = val
418
+ end
419
+
420
+ def use_explicit_value?
421
+ @value.to_s != "default"
422
+ end
423
+
424
+ def to_bool
425
+ !!@value
426
+ end
427
+ end
280
428
  end
281
429
  end
@@ -17,12 +17,15 @@ module AssetSync
17
17
  config.fog_directory = ENV['FOG_DIRECTORY'] if ENV.has_key?('FOG_DIRECTORY')
18
18
  config.fog_region = ENV['FOG_REGION'] if ENV.has_key?('FOG_REGION')
19
19
  config.fog_host = ENV['FOG_HOST'] if ENV.has_key?('FOG_HOST')
20
+ config.fog_port = ENV['FOG_PORT'] if ENV.has_key?('FOG_PORT')
20
21
  config.fog_scheme = ENV['FOG_SCHEMA'] if ENV.has_key?('FOG_SCHEMA')
21
22
  config.fog_path_style = ENV['FOG_PATH_STYLE'] if ENV.has_key?('FOG_PATH_STYLE')
22
23
 
23
24
  config.aws_access_key_id = ENV['AWS_ACCESS_KEY_ID'] if ENV.has_key?('AWS_ACCESS_KEY_ID')
24
25
  config.aws_secret_access_key = ENV['AWS_SECRET_ACCESS_KEY'] if ENV.has_key?('AWS_SECRET_ACCESS_KEY')
26
+ config.aws_session_token = ENV['AWS_SESSION_TOKEN'] if ENV.has_key?('AWS_SESSION_TOKEN')
25
27
  config.aws_signature_version = ENV['AWS_SIGNATURE_VERSION'] if ENV.has_key?('AWS_SIGNATURE_VERSION')
28
+ config.aws_acl = ENV['AWS_ACL'] if ENV.has_key?('AWS_ACL')
26
29
  config.aws_reduced_redundancy = ENV['AWS_REDUCED_REDUNDANCY'] == true if ENV.has_key?('AWS_REDUCED_REDUNDANCY')
27
30
 
28
31
  config.rackspace_username = ENV['RACKSPACE_USERNAME'] if ENV.has_key?('RACKSPACE_USERNAME')
@@ -34,6 +37,10 @@ module AssetSync
34
37
  config.azure_storage_account_name = ENV['AZURE_STORAGE_ACCOUNT_NAME'] if ENV.has_key?('AZURE_STORAGE_ACCOUNT_NAME')
35
38
  config.azure_storage_access_key = ENV['AZURE_STORAGE_ACCESS_KEY'] if ENV.has_key?('AZURE_STORAGE_ACCESS_KEY')
36
39
 
40
+ config.b2_key_id = ENV['B2_KEY_ID'] if ENV.has_key?('B2_KEY_ID')
41
+ config.b2_key_token = ENV['B2_KEY_TOKEN'] if ENV.has_key?('B2_KEY_TOKEN')
42
+ config.b2_bucket_id = ENV['B2_BUCKET_ID'] if ENV.has_key?('B2_BUCKET_ID')
43
+
37
44
  config.enabled = (ENV['ASSET_SYNC_ENABLED'] == 'true') if ENV.has_key?('ASSET_SYNC_ENABLED')
38
45
 
39
46
  config.existing_remote_files = ENV['ASSET_SYNC_EXISTING_REMOTE_FILES'] || "keep"
@@ -41,6 +48,8 @@ module AssetSync
41
48
  config.gzip_compression = (ENV['ASSET_SYNC_GZIP_COMPRESSION'] == 'true') if ENV.has_key?('ASSET_SYNC_GZIP_COMPRESSION')
42
49
  config.manifest = (ENV['ASSET_SYNC_MANIFEST'] == 'true') if ENV.has_key?('ASSET_SYNC_MANIFEST')
43
50
  config.include_manifest = (ENV['ASSET_SYNC_INCLUDE_MANIFEST'] == 'true') if ENV.has_key?('ASSET_SYNC_INCLUDE_MANIFEST')
51
+ config.concurrent_uploads = (ENV['ASSET_SYNC_CONCURRENT_UPLOADS'] == 'true') if ENV.has_key?('ASSET_SYNC_CONCURRENT_UPLOADS')
52
+ config.remote_file_list_cache_file_path = ENV['ASSET_SYNC_REMOTE_FILE_LIST_CACHE_FILE_PATH'] if ENV.has_key?('ASSET_SYNC_REMOTE_FILE_LIST_CACHE_FILE_PATH')
44
53
  end
45
54
 
46
55
  config.prefix = ENV['ASSET_SYNC_PREFIX'] if ENV.has_key?('ASSET_SYNC_PREFIX')
@@ -4,11 +4,16 @@ module AssetSync
4
4
  class MultiMime
5
5
 
6
6
  def self.lookup(ext)
7
+ overrides =
8
+ ::AssetSync.config.file_ext_to_mime_type_overrides
9
+ if overrides.key?(ext)
10
+ return overrides.fetch(ext)
11
+ end
7
12
 
8
13
  if defined?(::MIME::Types)
9
- ::MIME::Types.type_for(ext).first
14
+ ::MIME::Types.type_for(ext).first.to_s
10
15
  elsif defined?(::Mime::Type)
11
- ::Mime::Type.lookup_by_extension(ext)
16
+ ::Mime::Type.lookup_by_extension(ext).to_s
12
17
  elsif defined?(::Rack::Mime)
13
18
  ext_with_dot = ".#{ext}"
14
19
  ::Rack::Mime.mime_type(ext_with_dot)
@@ -4,7 +4,7 @@ require "asset_sync/multi_mime"
4
4
 
5
5
  module AssetSync
6
6
  class Storage
7
- REGEXP_FINGERPRINTED_FILES = /^(.*)\/([^-]+)-[^\.]+\.([^\.]+)$/
7
+ REGEXP_FINGERPRINTED_FILES = /\A(.*)\/(.+)-[^\.]+\.([^\.]+)\z/m
8
8
  REGEXP_ASSETS_TO_CACHE_CONTROL = /-[0-9a-fA-F]{32,}$/
9
9
 
10
10
  class BucketNotFound < StandardError;
@@ -22,7 +22,13 @@ module AssetSync
22
22
 
23
23
  def bucket
24
24
  # fixes: https://github.com/rumblelabs/asset_sync/issues/18
25
- @bucket ||= connection.directories.get(self.config.fog_directory, :prefix => self.config.assets_prefix)
25
+
26
+ @bucket ||= if self.config.backblaze?
27
+ connection.directories.get(self.config.fog_directory)
28
+ else
29
+ connection.directories.get(self.config.fog_directory, :prefix => self.config.assets_prefix)
30
+ end
31
+
26
32
  end
27
33
 
28
34
  def log(msg)
@@ -37,13 +43,17 @@ module AssetSync
37
43
  self.config.public_path
38
44
  end
39
45
 
46
+ def remote_file_list_cache_file_path
47
+ self.config.remote_file_list_cache_file_path
48
+ end
49
+
40
50
  def ignored_files
41
51
  expand_file_names(self.config.ignored_files)
42
52
  end
43
53
 
44
54
  def get_manifest_path
45
55
  return [] unless self.config.include_manifest
46
-
56
+
47
57
  if ActionView::Base.respond_to?(:assets_manifest)
48
58
  manifest = Sprockets::Manifest.new(ActionView::Base.assets_manifest.environment, ActionView::Base.assets_manifest.dir)
49
59
  manifest_path = manifest.filename
@@ -58,6 +68,32 @@ module AssetSync
58
68
  (get_local_files + config.additional_local_file_paths).uniq
59
69
  end
60
70
 
71
+ def remote_files
72
+ return [] if ignore_existing_remote_files?
73
+ return @remote_files if @remote_files
74
+
75
+ if remote_file_list_cache_file_path && File.file?(remote_file_list_cache_file_path)
76
+ begin
77
+ content = File.read(remote_file_list_cache_file_path)
78
+ return @remote_files = JSON.parse(content)
79
+ rescue JSON::ParserError
80
+ warn "Failed to parse #{remote_file_list_cache_file_path} as json"
81
+ end
82
+ end
83
+
84
+ @remote_files = get_remote_files
85
+ end
86
+
87
+ def update_remote_file_list_cache(local_files_to_upload)
88
+ return unless remote_file_list_cache_file_path
89
+ return if ignore_existing_remote_files?
90
+
91
+ File.open(self.remote_file_list_cache_file_path, 'w') do |file|
92
+ uploaded = local_files_to_upload + remote_files
93
+ file.write(uploaded.to_json)
94
+ end
95
+ end
96
+
61
97
  def always_upload_files
62
98
  expand_file_names(self.config.always_upload) + get_manifest_path
63
99
  end
@@ -81,7 +117,7 @@ module AssetSync
81
117
  return manifest.assets.values.map { |f| File.join(self.config.assets_prefix, f) }
82
118
  elsif File.exist?(self.config.manifest_path)
83
119
  log "Using: Manifest #{self.config.manifest_path}"
84
- yml = YAML.load(IO.read(self.config.manifest_path))
120
+ yml = AssetSync.load_yaml(IO.read(self.config.manifest_path))
85
121
 
86
122
  return yml.map do |original, compiled|
87
123
  # Upload font originals and compiled
@@ -132,9 +168,15 @@ module AssetSync
132
168
  from_remote_files_to_delete = remote_files - local_files - ignored_files - always_upload_files
133
169
 
134
170
  log "Flagging #{from_remote_files_to_delete.size} file(s) for deletion"
135
- # Delete unneeded remote files
136
- bucket.files.each do |f|
137
- delete_file(f, from_remote_files_to_delete)
171
+ # Delete unneeded remote files, if we are on aws delete in bulk else use sequential delete
172
+ if self.config.aws? && connection.respond_to?(:delete_multiple_objects)
173
+ from_remote_files_to_delete.each_slice(500) do |slice|
174
+ connection.delete_multiple_objects(config.fog_directory, slice)
175
+ end
176
+ else
177
+ bucket.files.each do |f|
178
+ delete_file(f, from_remote_files_to_delete)
179
+ end
138
180
  end
139
181
  end
140
182
 
@@ -148,10 +190,19 @@ module AssetSync
148
190
  file = {
149
191
  :key => f,
150
192
  :body => file_handle,
151
- :public => true,
152
193
  :content_type => mime
153
194
  }
154
195
 
196
+ # region fog_public
197
+
198
+ if config.aws? && config.aws_acl
199
+ file[:acl] = config.aws_acl
200
+ elsif config.fog_public.use_explicit_value?
201
+ file[:public] = config.fog_public.to_bool
202
+ end
203
+
204
+ # endregion fog_public
205
+
155
206
  uncompressed_filename = f.sub(/\.gz\z/, '')
156
207
  basename = File.basename(uncompressed_filename, File.extname(uncompressed_filename))
157
208
 
@@ -224,28 +275,38 @@ module AssetSync
224
275
  })
225
276
  end
226
277
 
227
- if config.azure_rm?
228
- # converts content_type from MIME::Type to String.
229
- # because Azure::Storage (called from Fog::AzureRM) expects content_type as a String like "application/json; charset=utf-8"
230
- file[:content_type] = file[:content_type].content_type if file[:content_type].is_a?(::MIME::Type)
231
- end
232
-
233
278
  bucket.files.create( file ) unless ignore
234
279
  file_handle.close
235
280
  gzip_file_handle.close if gzip_file_handle
236
281
  end
237
282
 
238
283
  def upload_files
239
- # get a fresh list of remote files
240
- remote_files = ignore_existing_remote_files? ? [] : get_remote_files
241
284
  # fixes: https://github.com/rumblelabs/asset_sync/issues/19
242
285
  local_files_to_upload = local_files - ignored_files - remote_files + always_upload_files
243
286
  local_files_to_upload = (local_files_to_upload + get_non_fingerprinted(local_files_to_upload)).uniq
244
-
245
- # Upload new files
246
- local_files_to_upload.each do |f|
247
- next unless File.file? "#{path}/#{f}" # Only files.
248
- upload_file f
287
+ # Only files.
288
+ local_files_to_upload = local_files_to_upload.select { |f| File.file? "#{path}/#{f}" }
289
+
290
+ if self.config.concurrent_uploads
291
+ jobs = Queue.new
292
+ local_files_to_upload.each { |f| jobs.push(f) }
293
+ jobs.close
294
+
295
+ num_threads = [self.config.concurrent_uploads_max_threads, local_files_to_upload.length].min
296
+ # Upload new files
297
+ workers = Array.new(num_threads) do
298
+ Thread.new do
299
+ while f = jobs.pop
300
+ upload_file(f)
301
+ end
302
+ end
303
+ end
304
+ workers.map(&:join)
305
+ else
306
+ # Upload new files
307
+ local_files_to_upload.each do |f|
308
+ upload_file f
309
+ end
249
310
  end
250
311
 
251
312
  if self.config.cdn_distribution_id && files_to_invalidate.any?
@@ -254,6 +315,8 @@ module AssetSync
254
315
  data = cdn.post_invalidation(self.config.cdn_distribution_id, files_to_invalidate)
255
316
  log "Invalidation id: #{data.body["Id"]}"
256
317
  end
318
+
319
+ update_remote_file_list_cache(local_files_to_upload)
257
320
  end
258
321
 
259
322
  def sync
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module AssetSync
4
- VERSION = "2.4.0".freeze
4
+ VERSION = "2.15.2"
5
5
  end