Class: Condo::Strata::AmazonS3

Inherits:
Object
  • Object
show all
Defined in:
lib/condo/strata/amazon_s3.rb

Instance Method Summary collapse

Constructor Details

#initialize(options) ⇒ AmazonS3

Returns a new instance of AmazonS3.

Raises:

  • (ArgumentError)


7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# File 'lib/condo/strata/amazon_s3.rb', line 7

def initialize(options)
	@options = {
		:name => :AmazonS3,
		:location => :'us-east-1',
		:fog => {
			:provider => :AWS,
			:aws_access_key_id => options[:access_id],
			:aws_secret_access_key => options[:secret_key],
			:region => (options[:location] || 'us-east-1')
		}
	}.merge!(options)
	
	
	raise ArgumentError, 'Amazon Access ID missing' if @options[:access_id].nil?
	raise ArgumentError, 'Amazon Secret Key missing' if @options[:secret_key].nil?
	
	
	@options[:location] = @options[:location].to_sym
	@options[:region] = @options[:location] == :'us-east-1' ? 's3.amazonaws.com' : "s3-#{@options[:location]}.amazonaws.com"
end

Instance Method Details

#destroy(upload) ⇒ Object



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
237
238
239
240
241
242
243
244
245
246
# File 'lib/condo/strata/amazon_s3.rb', line 210

def destroy(upload)
	connection = fog_connection
	directory = connection.directories.get(upload.bucket_name)	# it is assumed this exists - if not then the upload wouldn't have taken place
	file = directory.files.get(upload.object_key)
	
	if upload.resumable
		return file.destroy unless file.nil?
		begin
			if upload.resumable_id.present?
				connection.abort_multipart_upload(upload.bucket_name, upload.object_key, upload.resumable_id)
				return true
			end
		rescue
			# In-case resumable_id was invalid or did not match the object key
		end
		
		#
		# The user may have provided an invalid upload key, we'll need to search for the upload and destroy it
		#
		begin
			resp = connection.list_multipart_uploads(upload.bucket_name, {'prefix' => upload.object_key})
			resp.body['Upload'].each do |file|
				#
				# TODO:: BUGBUG:: there is an edge case where there may be more multi-part uploads with this this prefix then will be provided in a single request
				# => We'll need to handle this edge case to avoid abuse and dangling objects
				#
				connection.abort_multipart_upload(upload.bucket_name, upload.object_key, file['UploadId']) if file['Key'] == upload.object_key	# Ensure an exact match
			end
			return true	# The upload was either never initialised or has been destroyed
		rescue
			return false
		end
	else
		return true if file.nil?
		return file.destroy
	end
end

#fog_connectionObject



204
205
206
207
# File 'lib/condo/strata/amazon_s3.rb', line 204

def fog_connection
	@fog = @fog || Fog::Storage.new(@options[:fog])
	return @fog
end

#get_object(options) ⇒ Object

Create a signed URL for accessing a private file



43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
# File 'lib/condo/strata/amazon_s3.rb', line 43

def get_object(options)
	options = {}.merge!(options)	# Need to deep copy here
	options[:object_options] = {
		:expires => 5.minutes.from_now,
		:date => Time.now,
		:verb => :get,		# Post for multi-part uploads http://docs.amazonwebservices.com/AmazonS3/latest/API/mpUploadInitiate.html
		:headers => {},
		:parameters => {},
		:protocol => :https
	}.merge!(options[:object_options] || {})
	options.merge!(@options)
	
	#
	# provide the signed request
	#
	sign_request(options)[:url]
end

#get_parts(options) ⇒ Object

Returns the request to get the parts of a resumable upload



130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
# File 'lib/condo/strata/amazon_s3.rb', line 130

def get_parts(options)
	options[:object_options] = {
		:expires => 5.minutes.from_now,
		:date => Time.now,
		:verb => :get,
		:headers => {},
		:parameters => {},
		:protocol => :https
	}.merge!(options[:object_options])
	options.merge!(@options)
	
	#
	# Set the upload 
	#
	options[:object_options][:parameters]['uploadId'] = options[:resumable_id]
	
	#
	# provide the signed request
	#
	{
		:type => :parts,
		:signature => sign_request(options)
	}
end

#locationObject



34
35
36
# File 'lib/condo/strata/amazon_s3.rb', line 34

def location
	@options[:location]
end

#nameObject



29
30
31
# File 'lib/condo/strata/amazon_s3.rb', line 29

def name
	@options[:name]
end

#new_upload(options) ⇒ Object

Creates a new upload request (either single shot or multi-part)

> Passed: bucket_name, object_key, object_options, file_size



66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
# File 'lib/condo/strata/amazon_s3.rb', line 66

def new_upload(options)
	options = {}.merge!(options)	# Need to deep copy here
	options[:object_options] = {
		:permissions => :private,
		:expires => 5.minutes.from_now,
		:date => Time.now,
		:verb => :post,		# Post for multi-part uploads http://docs.amazonwebservices.com/AmazonS3/latest/API/mpUploadInitiate.html
		:headers => {},
		:parameters => {},
		:protocol => :https
	}.merge!(options[:object_options])
	options.merge!(@options)
	
	#
	# Set the access control headers
	#
	if options[:object_options][:headers]['x-amz-acl'].nil?
		options[:object_options][:headers]['x-amz-acl'] = case options[:object_options][:permissions]
		when :public
			:'public-read'
		else
			:private
		end
	end
	
	#
	# Decide what type of request is being sent
	#
	request = {}
	if options[:file_size] > 5.megabytes	# 5 mb (minimum chunk size)
		options[:object_options][:parameters][:uploads] = ''	# Customise the request to be a chunked upload
		options.delete(:file_id)								# Does not apply to chunked uploads
		
		request[:type] = :chunked_upload
	else
		if options[:file_id].present? && options[:object_options][:headers]['Content-Md5'].nil?
			#
			# The client side is sending hex formatted ids that will match the amazon etag
			# => We need this to be base64 for the md5 header (this is now done at the client side)
			#
			# options[:file_id] = [[options[:file_id]].pack("H*")].pack("m0")	# (the 0 avoids the call to strip - now done client side)
			# [ options[:file_id] ].pack('m').strip			# This wasn't correct
			# Base64.encode64(options[:file_id]).strip		# This also wasn't correct
			#
			options[:object_options][:headers]['Content-Md5'] = options[:file_id]
		end
		options[:object_options][:headers]['Content-Type'] = 'binary/octet-stream' if options[:object_options][:headers]['Content-Type'].nil?
		options[:object_options][:verb] = :put	# Put for direct uploads http://docs.amazonwebservices.com/AmazonS3/latest/API/RESTObjectPUT.html
		
		request[:type] = :direct_upload
	end
	
	
	#
	# provide the signed request
	#
	request[:signature] = sign_request(options)
	request
end

#set_part(options) ⇒ Object

Returns the requests for uploading parts and completing a resumable upload



159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
# File 'lib/condo/strata/amazon_s3.rb', line 159

def set_part(options)
	options[:object_options] = {
		:expires => 5.minutes.from_now,
		:date => Time.now,
		:headers => {},
		:parameters => {},
		:protocol => :https
	}.merge!(options[:object_options])
	options.merge!(@options)
	
	
	request = {}
	if options[:part] == 'finish'
		#
		# Send the commitment response
		#
		options[:object_options][:headers]['Content-Type'] = 'application/xml; charset=UTF-8' if options[:object_options][:headers]['Content-Type'].nil?
		options[:object_options][:verb] = :post
		request[:type] = :finish
	else
		#
		# Send the part upload request
		#
		options[:object_options][:headers]['Content-Md5'] = options[:file_id] if options[:file_id].present? && options[:object_options][:headers]['Content-Md5'].nil?
		options[:object_options][:headers]['Content-Type'] = 'binary/octet-stream' if options[:object_options][:headers]['Content-Type'].nil?
		options[:object_options][:parameters]['partNumber'] = options[:part]
		options[:object_options][:verb] = :put
		request[:type] = :part_upload
	end
	
	
	#
	# Set the upload 
	#
	options[:object_options][:parameters]['uploadId'] = options[:resumable_id]
	
	
	#
	# provide the signed request
	#
	request[:signature] = sign_request(options)
	request
end