Class: Middleman::S3Sync::Resource

Inherits:
Object
  • Object
show all
Includes:
Status
Defined in:
lib/middleman/s3_sync/resource.rb

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from Status

#say_status

Constructor Details

#initialize(resource, partial_s3_resource) ⇒ Resource

Returns a new instance of Resource.



9
10
11
12
13
14
15
16
17
18
19
# File 'lib/middleman/s3_sync/resource.rb', line 9

def initialize(resource, partial_s3_resource)
  @resource = resource
  @path = if resource
            resource.destination_path.sub(/^\//, '')
          elsif partial_s3_resource&.key
            partial_s3_resource.key.sub(/^\//, '')
          else
            ''
          end
  @partial_s3_resource = partial_s3_resource
end

Instance Attribute Details

#content_typeObject

Returns the value of attribute content_type.



4
5
6
# File 'lib/middleman/s3_sync/resource.rb', line 4

def content_type
  @content_type
end

#full_s3_resourceObject

S3 resource as returned by a HEAD request



26
27
28
# File 'lib/middleman/s3_sync/resource.rb', line 26

def full_s3_resource
  @full_s3_resource
end

#gzippedObject

Returns the value of attribute gzipped.



4
5
6
# File 'lib/middleman/s3_sync/resource.rb', line 4

def gzipped
  @gzipped
end

#options=(value) ⇒ Object

Sets the attribute options

Parameters:

  • value

    the value to set the attribute options to.



4
5
6
# File 'lib/middleman/s3_sync/resource.rb', line 4

def options=(value)
  @options = value
end

#partial_s3_resourceObject

Returns the value of attribute partial_s3_resource.



4
5
6
# File 'lib/middleman/s3_sync/resource.rb', line 4

def partial_s3_resource
  @partial_s3_resource
end

#pathObject

Returns the value of attribute path.



4
5
6
# File 'lib/middleman/s3_sync/resource.rb', line 4

def path
  @path
end

#resourceObject

Returns the value of attribute resource.



4
5
6
# File 'lib/middleman/s3_sync/resource.rb', line 4

def resource
  @resource
end

Instance Method Details

#alternate_encoding?Boolean

Returns:

  • (Boolean)


173
174
175
# File 'lib/middleman/s3_sync/resource.rb', line 173

def alternate_encoding?
  status == :alternate_encoding
end

#caching_policyObject



320
321
322
# File 'lib/middleman/s3_sync/resource.rb', line 320

def caching_policy
  @caching_policy ||= Middleman::S3Sync.caching_policy_for(content_type)
end

#caching_policy_match?Boolean

Returns:

  • (Boolean)


324
325
326
327
328
329
330
# File 'lib/middleman/s3_sync/resource.rb', line 324

def caching_policy_match?
  if caching_policy && full_s3_resource && full_s3_resource.respond_to?(:cache_control)
    caching_policy.cache_control == full_s3_resource.cache_control
  else
    true
  end
end

#create!Object



109
110
111
112
113
114
# File 'lib/middleman/s3_sync/resource.rb', line 109

def create!
  say_status "#{ANSI.green{"Creating"}} #{remote_path}#{ gzipped ? ANSI.white {' (gzipped)'} : ''}"
  unless options.dry_run
    upload!
  end
end

#destroy!Object



104
105
106
107
# File 'lib/middleman/s3_sync/resource.rb', line 104

def destroy!
  say_status "#{ANSI.red{"Deleting"}} #{remote_path}"
  bucket.object(remote_path.sub(/^\//, '')).delete unless options.dry_run
end

#directory?Boolean

Returns:

  • (Boolean)


275
276
277
# File 'lib/middleman/s3_sync/resource.rb', line 275

def directory?
  File.directory?(local_path)
end

#encoding_match?Boolean

Returns:

  • (Boolean)


287
288
289
# File 'lib/middleman/s3_sync/resource.rb', line 287

def encoding_match?
  (options.prefer_gzip && gzipped && full_s3_resource.content_encoding == 'gzip') || (!options.prefer_gzip && !gzipped && !full_s3_resource.content_encoding )
end

#identical?Boolean

Returns:

  • (Boolean)


177
178
179
# File 'lib/middleman/s3_sync/resource.rb', line 177

def identical?
  status == :identical
end

#ignore!Object



154
155
156
157
158
159
160
161
162
163
# File 'lib/middleman/s3_sync/resource.rb', line 154

def ignore!
  if options.verbose
    reason = if redirect?
               :redirect
             elsif directory?
               :directory
             end
    say_status "#{ANSI.yellow{"Ignoring"}} #{remote_path} #{ reason ? ANSI.white {"(#{reason})" } : "" }"
  end
end

#local?Boolean

Returns:

  • (Boolean)


238
239
240
# File 'lib/middleman/s3_sync/resource.rb', line 238

def local?
  File.exist?(local_path) && resource
end

#local_contentObject



189
190
191
192
193
194
195
# File 'lib/middleman/s3_sync/resource.rb', line 189

def local_content
  if block_given?
    File.open(local_path) { |f| yield f.read }
  else
    File.read(local_path)
  end
end

#local_content_md5Object



301
302
303
304
305
306
307
308
309
# File 'lib/middleman/s3_sync/resource.rb', line 301

def local_content_md5
  @local_content_md5 ||= begin
    if File.exist?(original_path)
      Digest::MD5.hexdigest(File.read(original_path))
    else
      nil
    end
  end
end

#local_object_md5Object



297
298
299
# File 'lib/middleman/s3_sync/resource.rb', line 297

def local_object_md5
  @local_object_md5 ||= Digest::MD5.hexdigest(File.read(local_path))
end

#local_pathObject



95
96
97
98
99
100
101
102
# File 'lib/middleman/s3_sync/resource.rb', line 95

def local_path
  local_path = build_dir + '/' + path.gsub(/^#{options.prefix}/, '')
  if options.prefer_gzip && File.exist?(local_path + ".gz")
    @gzipped = true
    local_path += ".gz"
  end
  local_path
end

#metadata_match?Boolean

Returns:

  • (Boolean)


251
252
253
# File 'lib/middleman/s3_sync/resource.rb', line 251

def 
  redirect_match? && caching_policy_match?
end

#normalize_path(prefix, path) ⇒ Object



48
49
50
51
52
53
# File 'lib/middleman/s3_sync/resource.rb', line 48

def normalize_path(prefix, path)
  # Remove any trailing slash from prefix and leading slash from path
  prefix = prefix.chomp('/')
  path = path.sub(/^\//, '')
  "#{prefix}/#{path}"
end

#original_pathObject



311
312
313
# File 'lib/middleman/s3_sync/resource.rb', line 311

def original_path
  gzipped ? local_path.gsub(/\.gz$/, '') : local_path
end

#redirect?Boolean

Returns:

  • (Boolean)


246
247
248
249
# File 'lib/middleman/s3_sync/resource.rb', line 246

def redirect?
  (resource && resource.respond_to?(:redirect?) && resource.redirect?) || 
    (full_s3_resource && full_s3_resource.respond_to?(:website_redirect_location) && full_s3_resource.website_redirect_location)
end

#redirect_match?Boolean

Returns:

  • (Boolean)


255
256
257
258
259
260
261
# File 'lib/middleman/s3_sync/resource.rb', line 255

def redirect_match?
  if redirect?
    redirect_url == remote_redirect_url
  else
    true
  end
end

#redirect_urlObject



271
272
273
# File 'lib/middleman/s3_sync/resource.rb', line 271

def redirect_url
  resource.respond_to?(:target_url) ? resource.target_url : nil
end

#relative_pathObject



279
280
281
# File 'lib/middleman/s3_sync/resource.rb', line 279

def relative_path
  @relative_path ||= local_path.gsub(/#{build_dir}/, '')
end

#remote?Boolean

Returns:

  • (Boolean)


242
243
244
# File 'lib/middleman/s3_sync/resource.rb', line 242

def remote?
  !full_s3_resource.nil?
end

#remote_content_md5Object



291
292
293
294
295
# File 'lib/middleman/s3_sync/resource.rb', line 291

def remote_content_md5
  if full_s3_resource && full_s3_resource.
    full_s3_resource.['content-md5']
  end
end

#remote_object_md5Object



283
284
285
# File 'lib/middleman/s3_sync/resource.rb', line 283

def remote_object_md5
  s3_resource.etag.gsub(/"/, '') if s3_resource.etag
end

#remote_pathObject Also known as: key



34
35
36
37
38
39
40
41
42
43
44
45
# File 'lib/middleman/s3_sync/resource.rb', line 34

def remote_path
  if s3_resource
    if s3_resource.respond_to?(:key)
      s3_resource.key.sub(/^\//, '')
    else
      # For HeadObjectOutput objects which don't have key method
      options.prefix ? normalize_path(options.prefix, path) : path.sub(/^\//, '')
    end
  else
    options.prefix ? normalize_path(options.prefix, path) : path.sub(/^\//, '')
  end.sub(/^\//, '')  # Ensure no leading slash
end

#remote_redirect_urlObject



267
268
269
# File 'lib/middleman/s3_sync/resource.rb', line 267

def remote_redirect_url
  full_s3_resource&.website_redirect_location
end

#s3_resourceObject



21
22
23
# File 'lib/middleman/s3_sync/resource.rb', line 21

def s3_resource
  @full_s3_resource || @partial_s3_resource
end

#shunned?Boolean

Returns:

  • (Boolean)


263
264
265
# File 'lib/middleman/s3_sync/resource.rb', line 263

def shunned?
  !!path[Regexp.union(options.ignore_paths)]
end

#statusObject



197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
# File 'lib/middleman/s3_sync/resource.rb', line 197

def status
  @status ||= if shunned?
                :ignored
              elsif directory?
                if remote?
                  :deleted
                else
                  :ignored
                end
              elsif local? && remote?
                if options.force
                  :updated
                elsif not 
                  :updated
                elsif local_object_md5 == remote_object_md5
                  :identical
                else
                  if !gzipped
                    # we're not gzipped, object hashes being different indicates updated content
                    :updated
                  elsif !encoding_match? || local_content_md5 != remote_content_md5
                    # we're gzipped, so we checked the content MD5, and it also changed
                    :updated
                  else
                    # we're gzipped, the object hashes differ, but the content hashes are equal
                    # this means the gzipped bits changed while the original bits did not
                    # what's more, we spent a HEAD request to find out
                    :alternate_encoding
                  end
                end
              elsif local?
                :new
              elsif remote? && redirect?
                :ignored
              elsif remote?
                :deleted
              else
                :ignored
              end
end

#to_create?Boolean

Returns:

  • (Boolean)


169
170
171
# File 'lib/middleman/s3_sync/resource.rb', line 169

def to_create?
  status == :new
end

#to_delete?Boolean

Returns:

  • (Boolean)


165
166
167
# File 'lib/middleman/s3_sync/resource.rb', line 165

def to_delete?
  status == :deleted
end

#to_hObject Also known as: attributes



55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
# File 'lib/middleman/s3_sync/resource.rb', line 55

def to_h
  attributes = {
    :key => key,
    :acl => options.acl,
    :content_type => content_type,
    'content-md5' => local_content_md5
  }

  if caching_policy
    attributes[:cache_control] = caching_policy.cache_control
    attributes[:expires] = caching_policy.expires
  end

  if options.prefer_gzip && gzipped
    attributes[:content_encoding] = "gzip"
  end

  if options.reduced_redundancy_storage
    attributes[:storage_class] = 'REDUCED_REDUNDANCY'
  end

  if options.encryption
    attributes[:encryption] = 'AES256'
  end

  if redirect?
    attributes['website-redirect-location'] = redirect_url
  end

  attributes
end

#to_ignore?Boolean

Returns:

  • (Boolean)


185
186
187
# File 'lib/middleman/s3_sync/resource.rb', line 185

def to_ignore?
  status == :ignored || status == :alternate_encoding
end

#to_update?Boolean

Returns:

  • (Boolean)


181
182
183
# File 'lib/middleman/s3_sync/resource.rb', line 181

def to_update?
  status == :updated
end

#update!Object



88
89
90
91
92
93
# File 'lib/middleman/s3_sync/resource.rb', line 88

def update!
  say_status "#{ANSI.blue{"Updating"}} #{remote_path}#{ gzipped ? ANSI.white {' (gzipped)'} : ''}"
  unless options.dry_run
    upload!
  end
end

#upload!Object



116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
# File 'lib/middleman/s3_sync/resource.rb', line 116

def upload!
  object = bucket.object(remote_path.sub(/^\//, ''))
  upload_options = {
    body: local_content,
    content_type: content_type,
    acl: options.acl
  }

  # Add metadata if present
  if local_content_md5
    upload_options[:metadata] = { 'content-md5' => local_content_md5 }
  end

  # Add redirect if present
  upload_options[:website_redirect_location] = redirect_url if redirect?

  # Add content encoding if present
  upload_options[:content_encoding] = "gzip" if options.prefer_gzip && gzipped

  # Add cache control and expires if present
  if caching_policy
    upload_options[:cache_control] = caching_policy.cache_control
    upload_options[:expires] = caching_policy.expires
  end

  # Add storage class if needed
  if options.reduced_redundancy_storage
    upload_options[:storage_class] = 'REDUCED_REDUNDANCY'
  end

  # Add encryption if needed
  if options.encryption
    upload_options[:server_side_encryption] = 'AES256'
  end

  object.put(upload_options)
end