condo 1.0.4 → 1.0.6

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.
Files changed (32) hide show
  1. checksums.yaml +7 -0
  2. data/README.textile +133 -133
  3. data/app/assets/javascripts/condo.js +9 -6
  4. data/app/assets/javascripts/condo/amazon.js +403 -406
  5. data/app/assets/javascripts/condo/condo.js +184 -0
  6. data/app/assets/javascripts/condo/config.js +69 -80
  7. data/app/assets/javascripts/condo/google.js +338 -255
  8. data/app/assets/javascripts/condo/md5/hash.worker.emulator.js +23 -23
  9. data/app/assets/javascripts/condo/md5/hash.worker.js +11 -11
  10. data/app/assets/javascripts/condo/md5/hasher.js +119 -100
  11. data/app/assets/javascripts/condo/md5/spark-md5.js +276 -161
  12. data/app/assets/javascripts/condo/rackspace.js +326 -329
  13. data/app/assets/javascripts/condo/{abstract-md5.js.erb → services/abstract-md5.js.erb} +86 -93
  14. data/app/assets/javascripts/condo/{base64.js → services/base64.js} +2 -10
  15. data/app/assets/javascripts/condo/services/broadcaster.js +26 -0
  16. data/app/assets/javascripts/condo/services/uploader.js +302 -0
  17. data/app/assets/javascripts/core/core.js +4 -0
  18. data/app/assets/javascripts/core/services/1-safe-apply.js +17 -0
  19. data/app/assets/javascripts/core/services/2-messaging.js +171 -0
  20. data/lib/condo.rb +269 -269
  21. data/lib/condo/configuration.rb +137 -139
  22. data/lib/condo/errors.rb +8 -8
  23. data/lib/condo/strata/amazon_s3.rb +301 -301
  24. data/lib/condo/strata/google_cloud_storage.rb +315 -314
  25. data/lib/condo/strata/rackspace_cloud_files.rb +245 -223
  26. data/lib/condo/version.rb +1 -1
  27. metadata +21 -44
  28. data/app/assets/javascripts/condo/broadcaster.js +0 -60
  29. data/app/assets/javascripts/condo/controller.js +0 -194
  30. data/app/assets/javascripts/condo/uploader.js +0 -310
  31. data/test/dummy/db/test.sqlite3 +0 -0
  32. data/test/dummy/log/test.log +0 -25
@@ -1,314 +1,315 @@
1
- module Condo; end
2
- module Condo::Strata; end
3
-
4
-
5
- class Fog::Storage::Google::Real
6
- def condo_request(*args)
7
- request(*args)
8
- end
9
- end
10
-
11
-
12
- class Condo::Strata::GoogleCloudStorage
13
-
14
- def initialize(options)
15
- @options = {
16
- :name => :GoogleCloudStorage,
17
- :location => :na, # US or Europe, set at bucket creation time
18
- :fog => {
19
- :provider => 'Google',
20
- :google_storage_access_key_id => options[:fog_access_id] || options[:access_id],
21
- :google_storage_secret_access_key => options[:fog_secret_key] || options[:secret_key]
22
- },
23
- :api => 1
24
- }.merge!(options)
25
-
26
-
27
- raise ArgumentError, 'Google Access ID missing' if @options[:access_id].nil?
28
- raise ArgumentError, 'Google Secret Key missing' if @options[:secret_key].nil?
29
-
30
- if @options[:api] == 2
31
- @options[:secret_key] = OpenSSL::PKey::RSA.new(@options[:secret_key])
32
- end
33
-
34
- @options[:location] = @options[:location].to_sym
35
- end
36
-
37
-
38
- #
39
- # Enable CORS on a bucket for a domain
40
- #
41
- def enable_cors(bucket, origin = '*')
42
- data =
43
- <<-DATA
44
- <?xml version="1.0" encoding="UTF-8"?>
45
- <CorsConfig>
46
- <Cors>
47
- <Origins>
48
- <Origin>#{origin}</Origin>
49
- </Origins>
50
- <Methods>
51
- <Method>GET</Method>
52
- <Method>HEAD</Method>
53
- <Method>POST</Method>
54
- <Method>PUT</Method>
55
- </Methods>
56
- <ResponseHeaders>
57
- <ResponseHeader>origin</ResponseHeader>
58
- <ResponseHeader>content-md5</ResponseHeader>
59
- <ResponseHeader>authorization</ResponseHeader>
60
- <ResponseHeader>x-goog-date</ResponseHeader>
61
- <ResponseHeader>x-goog-acl</ResponseHeader>
62
- <ResponseHeader>content-type</ResponseHeader>
63
- <ResponseHeader>accept</ResponseHeader>
64
- <ResponseHeader>x-goog-api-version</ResponseHeader>
65
- <ResponseHeader>x-goog-resumable</ResponseHeader>
66
- </ResponseHeaders>
67
- <MaxAgeSec>1800</MaxAgeSec>
68
- </Cors>
69
- </CorsConfig>
70
- DATA
71
-
72
- fog_connection.condo_request(
73
- :expects => 200,
74
- :body => data,
75
- :method => 'PUT',
76
- :headers => {},
77
- :host => "#{bucket}.storage.googleapis.com",
78
- :idempotent => true,
79
- :path => '?cors' # There is an issue with Fog where this isn't included as a canonical_resource
80
- )
81
- end
82
-
83
-
84
- def name
85
- @options[:name]
86
- end
87
-
88
-
89
- def location
90
- @options[:location]
91
- end
92
-
93
-
94
- #
95
- # Create a signed URL for accessing a private file
96
- #
97
- def get_object(options)
98
- options = {}.merge!(options) # Need to deep copy here
99
- options[:object_options] = {
100
- :expires => 5.minutes.from_now,
101
- :verb => :get,
102
- :headers => {},
103
- :parameters => {},
104
- :protocol => :https
105
- }.merge!(options[:object_options] || {})
106
- options.merge!(@options)
107
-
108
- #
109
- # provide the signed request
110
- #
111
- sign_request(options)[:url]
112
- end
113
-
114
-
115
- #
116
- # Creates a new upload request (either single shot or multi-part)
117
- # => Passed: bucket_name, object_key, object_options, file_size
118
- #
119
- def new_upload(options)
120
- options = {}.merge!(options) # Need to deep copy here
121
- options[:object_options] = {
122
- :permissions => :private,
123
- :expires => 5.minutes.from_now,
124
- :verb => :put, # This will be a post for resumable uploads
125
- :headers => {},
126
- :parameters => {},
127
- :protocol => :https
128
- }.merge!(options[:object_options] || {})
129
- options.merge!(@options)
130
-
131
-
132
- #
133
- # Set the access control headers
134
- #
135
- options[:object_options][:headers]['x-goog-api-version'] = @options[:api]
136
-
137
- if options[:object_options][:headers]['x-goog-acl'].nil?
138
- options[:object_options][:headers]['x-goog-acl'] = case options[:object_options][:permissions]
139
- when :public
140
- :'public-read'
141
- else
142
- :private
143
- end
144
- end
145
-
146
- options[:object_options][:headers]['Content-Md5'] = options[:file_id] if options[:file_id].present? && options[:object_options][:headers]['Content-Md5'].nil?
147
- options[:object_options][:headers]['Content-Type'] = 'binary/octet-stream' if options[:object_options][:headers]['Content-Type'].nil?
148
-
149
-
150
- #
151
- # Decide what type of request is being sent
152
- # => Currently google only supports direct uploads (no CORS resumables yet!)
153
- #
154
- return {
155
- :signature => sign_request(options),
156
- :type => :direct_upload
157
- }
158
-
159
- #
160
- # This is what we'll return when resumables work with CORS
161
- #
162
- if options[:file_size] > 2.megabytes
163
- # Resumables may not support the md5 header at this time - have to compare ETag and fail on the client side
164
- options[:object_options][:verb] = :post
165
- options[:object_options][:headers]['x-goog-resumable'] = 'start'
166
- return {
167
- :signature => sign_request(options),
168
- :type => :chunked_upload # triggers resumable
169
- }
170
- end
171
- end
172
-
173
-
174
- #
175
- # Creates a request for the byte we were up to
176
- # => doesn't work with CORS yet
177
- #
178
- def get_parts(options)
179
- options[:object_options] = {
180
- :expires => 5.minutes.from_now,
181
- :verb => :put,
182
- :headers => {},
183
- :parameters => {},
184
- :protocol => :https
185
- }.merge!(options[:object_options])
186
- options.merge!(@options)
187
-
188
- #
189
- # Set the upload and request the range of bytes we are after
190
- #
191
- options[:object_options][:parameters]['upload_id'] = options[:resumable_id]
192
- ## This can be set on the client side as it is not part of the signed request
193
- #options[:object_options][:headers]['Content-Range'] = "bytes */#{options[:file_size]}"
194
-
195
- #
196
- # provide the signed request
197
- #
198
- {
199
- :type => :parts,
200
- :signature => sign_request(options)
201
- }
202
- end
203
-
204
-
205
- #
206
- # Returns the requests for uploading parts and completing a resumable upload
207
- #
208
- def set_part(options)
209
- resp = get_parts(options)
210
- resp[:type] = :part_upload
211
- return resp
212
- end
213
-
214
-
215
- def fog_connection
216
- @fog = @fog || Fog::Storage.new(@options[:fog])
217
- return @fog
218
- end
219
-
220
-
221
- def destroy(upload)
222
- connection = fog_connection
223
- directory = connection.directories.get(upload.bucket_name) # it is assumed this exists - if not then the upload wouldn't have taken place
224
- file = directory.files.get(upload.object_key) # NOTE:: I only assume this works with resumables... should look into it
225
-
226
- return true if file.nil?
227
- return file.destroy
228
- end
229
-
230
-
231
-
232
- protected
233
-
234
-
235
-
236
- def sign_request(options)
237
-
238
- #
239
- # Build base URL
240
- #
241
- verb = options[:object_options][:verb].to_s.upcase.to_sym
242
- options[:object_options][:expires] = options[:object_options][:expires].utc.to_i
243
-
244
- url = "/#{options[:object_key]}"
245
-
246
-
247
- #
248
- # Add signed request params
249
- #
250
- other_params = ''
251
- signed_params = '?'
252
- (options[:object_options][:parameters] || {}).each do |key, value|
253
- if ['acl', 'cors', 'location', 'logging', 'requestPayment', 'torrent', 'versions', 'versioning'].include?(key)
254
- signed_params << "#{key}&"
255
- else
256
- other_params << (value.empty? ? "#{key}&" : "#{key}=#{value}&")
257
- end
258
- end
259
- signed_params.chop!
260
-
261
- url << signed_params
262
-
263
-
264
- #
265
- # Build a request signature
266
- #
267
- signature = "#{verb}\n#{options[:file_id]}\n#{options[:object_options][:headers]['Content-Type']}\n#{options[:object_options][:expires]}\n"
268
- if verb != :GET
269
- options[:object_options][:headers]['x-goog-date'] ||= Time.now.utc.httpdate
270
-
271
- google_headers, canonical_google_headers = {}, '' # Copied from https://github.com/fog/fog/blob/master/lib/fog/google/storage.rb
272
- for key, value in options[:object_options][:headers]
273
- if key[0..6] == 'x-goog-'
274
- google_headers[key] = value
275
- end
276
- end
277
-
278
- google_headers = google_headers.sort {|x, y| x[0] <=> y[0]}
279
- for key, value in google_headers
280
- signature << "#{key}:#{value}\n"
281
- end
282
- end
283
-
284
- signature << "/#{options[:bucket_name]}#{url}"
285
-
286
-
287
- #
288
- # Encode the request signature
289
- #
290
- if @options[:api] == 1
291
- signature = Base64.encode64(OpenSSL::HMAC.digest(OpenSSL::Digest::Digest.new('sha1'), @options[:secret_key], signature)).gsub("\n","")
292
- options[:object_options][:headers]['Authorization'] = "GOOG1 #{@options[:access_id]}:#{signature}"
293
- else
294
- signature = Base64.encode64(@options[:secret_key].sign(OpenSSL::Digest::SHA256.new, signature)).gsub("\n","")
295
- end
296
-
297
-
298
- url += signed_params.present? ? '&' : '?'
299
- url = "#{options[:object_options][:protocol]}://#{options[:bucket_name]}.storage.googleapis.com#{url}#{other_params}GoogleAccessId=#{@options[:access_id]}&Expires=#{options[:object_options][:expires]}&Signature=#{CGI::escape(signature)}"
300
-
301
-
302
- #
303
- # Finish building the request
304
- #
305
- return {
306
- :verb => options[:object_options][:verb].to_s.upcase,
307
- :url => url,
308
- :headers => options[:object_options][:headers]
309
- }
310
- end
311
-
312
-
313
- end
314
-
1
+ module Condo; end
2
+ module Condo::Strata; end
3
+
4
+
5
+ class Fog::Storage::Google::Real
6
+ def condo_request(*args)
7
+ request(*args)
8
+ end
9
+ end
10
+
11
+
12
+ class Condo::Strata::GoogleCloudStorage
13
+
14
+ def initialize(options)
15
+ @options = {
16
+ :name => :GoogleCloudStorage,
17
+ :location => :na, # US or Europe, set at bucket creation time
18
+ :fog => {
19
+ :provider => 'Google',
20
+ :google_storage_access_key_id => options[:fog_access_id] || options[:access_id],
21
+ :google_storage_secret_access_key => options[:fog_secret_key] || options[:secret_key]
22
+ },
23
+ :api => 1
24
+ }.merge!(options)
25
+
26
+
27
+ raise ArgumentError, 'Google Access ID missing' if @options[:access_id].nil?
28
+ raise ArgumentError, 'Google Secret Key missing' if @options[:secret_key].nil?
29
+
30
+ if @options[:api] == 2
31
+ @options[:secret_key] = OpenSSL::PKey::RSA.new(@options[:secret_key])
32
+ end
33
+
34
+ @options[:location] = @options[:location].to_sym
35
+ end
36
+
37
+
38
+ #
39
+ # Enable CORS on a bucket for a domain
40
+ #
41
+ def enable_cors(bucket, origin = '*')
42
+ data =
43
+ <<-DATA
44
+ <?xml version="1.0" encoding="UTF-8"?>
45
+ <CorsConfig>
46
+ <Cors>
47
+ <Origins>
48
+ <Origin>#{origin}</Origin>
49
+ </Origins>
50
+ <Methods>
51
+ <Method>GET</Method>
52
+ <Method>HEAD</Method>
53
+ <Method>POST</Method>
54
+ <Method>PUT</Method>
55
+ </Methods>
56
+ <ResponseHeaders>
57
+ <ResponseHeader>origin</ResponseHeader>
58
+ <ResponseHeader>content-md5</ResponseHeader>
59
+ <ResponseHeader>authorization</ResponseHeader>
60
+ <ResponseHeader>x-goog-date</ResponseHeader>
61
+ <ResponseHeader>x-goog-acl</ResponseHeader>
62
+ <ResponseHeader>content-type</ResponseHeader>
63
+ <ResponseHeader>accept</ResponseHeader>
64
+ <ResponseHeader>x-goog-api-version</ResponseHeader>
65
+ <ResponseHeader>x-goog-resumable</ResponseHeader>
66
+ <ResponseHeader>content-range</ResponseHeader>
67
+ <ResponseHeader>x-requested-with</ResponseHeader>
68
+ </ResponseHeaders>
69
+ <MaxAgeSec>1800</MaxAgeSec>
70
+ </Cors>
71
+ </CorsConfig>
72
+ DATA
73
+
74
+ fog_connection.condo_request(
75
+ :expects => 200,
76
+ :body => data,
77
+ :method => 'PUT',
78
+ :headers => {},
79
+ :host => "#{bucket}.storage.googleapis.com",
80
+ :idempotent => true,
81
+ :path => '?cors' # There is an issue with Fog where this isn't included as a canonical_resource
82
+ )
83
+ end
84
+
85
+
86
+ def name
87
+ @options[:name]
88
+ end
89
+
90
+
91
+ def location
92
+ @options[:location]
93
+ end
94
+
95
+
96
+ #
97
+ # Create a signed URL for accessing a private file
98
+ #
99
+ def get_object(options)
100
+ options = {}.merge!(options) # Need to deep copy here
101
+ options[:object_options] = {
102
+ :expires => 5.minutes.from_now,
103
+ :verb => :get,
104
+ :headers => {},
105
+ :parameters => {},
106
+ :protocol => :https
107
+ }.merge!(options[:object_options] || {})
108
+ options.merge!(@options)
109
+
110
+ #
111
+ # provide the signed request
112
+ #
113
+ sign_request(options)[:url]
114
+ end
115
+
116
+
117
+ #
118
+ # Creates a new upload request (either single shot or multi-part)
119
+ # => Passed: bucket_name, object_key, object_options, file_size
120
+ #
121
+ def new_upload(options)
122
+ options = {}.merge!(options) # Need to deep copy here
123
+ options[:object_options] = {
124
+ :permissions => :private,
125
+ :expires => 5.minutes.from_now,
126
+ :verb => :put, # put for direct uploads
127
+ :headers => {},
128
+ :parameters => {},
129
+ :protocol => :https
130
+ }.merge!(options[:object_options] || {})
131
+ options.merge!(@options)
132
+
133
+
134
+ options[:object_options][:headers]['x-goog-api-version'] = @options[:api]
135
+
136
+ if options[:object_options][:headers]['x-goog-acl'].nil?
137
+ options[:object_options][:headers]['x-goog-acl'] = case options[:object_options][:permissions]
138
+ when :public
139
+ :'public-read'
140
+ else
141
+ :private
142
+ end
143
+ end
144
+
145
+ options[:object_options][:headers]['Content-Type'] = 'binary/octet-stream' if options[:object_options][:headers]['Content-Type'].nil?
146
+
147
+
148
+ #
149
+ # Decide what type of request is being sent
150
+ #
151
+ if options[:file_size] > 1.megabytes
152
+ # Resumables may not support the md5 header at this time - have to compare ETag and fail on the client side
153
+ options[:object_options][:verb] = :post
154
+ options[:object_options][:headers]['x-goog-resumable'] = 'start'
155
+ return {
156
+ :signature => sign_request(options),
157
+ :type => :chunked_upload # triggers resumable
158
+ }
159
+ else
160
+ options[:object_options][:headers]['Content-Md5'] = options[:file_id] if options[:file_id].present? && options[:object_options][:headers]['Content-Md5'].nil?
161
+ return {
162
+ :signature => sign_request(options),
163
+ :type => :direct_upload
164
+ }
165
+ end
166
+ end
167
+
168
+
169
+ #
170
+ # Creates a request for the byte we were up to
171
+ #
172
+ def get_parts(options, setting_parts = false)
173
+ options[:object_options] = {
174
+ :expires => 5.minutes.from_now,
175
+ :verb => :put, # put for direct uploads
176
+ :headers => {},
177
+ :parameters => {},
178
+ :protocol => :https
179
+ }.merge!(options[:object_options] || {})
180
+ options.merge!(@options)
181
+
182
+ #
183
+ # Set the upload and request the range of bytes we are after
184
+ #
185
+ if setting_parts
186
+ options[:object_options][:headers]['Content-Md5'] = options[:file_id] if options[:file_id].present? && options[:object_options][:headers]['Content-Md5'].nil?
187
+ options[:object_options][:headers]['Content-Range'] = "bytes #{options[:part]}-#{options[:file_size] - 1}/#{options[:file_size]}"
188
+ else
189
+ options[:object_options][:headers]['Content-Range'] = "bytes */#{options[:file_size]}"
190
+ end
191
+ options[:object_options][:headers]['x-goog-api-version'] = @options[:api]
192
+ options[:object_options][:parameters]['upload_id'] = options[:resumable_id]
193
+
194
+ #
195
+ # provide the signed request
196
+ #
197
+ {
198
+ :expected => 308,
199
+ :type => :status,
200
+ :signature => sign_request(options)
201
+ }
202
+ end
203
+
204
+
205
+ #
206
+ # Returns the requests for uploading parts and completing a resumable upload
207
+ #
208
+ def set_part(options)
209
+ resp = get_parts(options, true)
210
+ resp[:type] = :resume_upload
211
+ resp[:type] = :resume_upload
212
+ return resp
213
+ end
214
+
215
+
216
+ def fog_connection
217
+ @fog = @fog || Fog::Storage.new(@options[:fog])
218
+ return @fog
219
+ end
220
+
221
+
222
+ def destroy(upload)
223
+ connection = fog_connection
224
+ directory = connection.directories.get(upload.bucket_name) # it is assumed this exists - if not then the upload wouldn't have taken place
225
+ file = directory.files.get(upload.object_key) # NOTE:: I only assume this works with resumables... should look into it
226
+
227
+ return true if file.nil?
228
+ return file.destroy
229
+ end
230
+
231
+
232
+
233
+ protected
234
+
235
+
236
+
237
+ def sign_request(options)
238
+
239
+ #
240
+ # Build base URL
241
+ #
242
+ verb = options[:object_options][:verb].to_s.upcase.to_sym
243
+ options[:object_options][:expires] = options[:object_options][:expires].utc.to_i
244
+
245
+ url = "/#{options[:object_key]}"
246
+
247
+
248
+ #
249
+ # Add signed request params
250
+ #
251
+ other_params = ''
252
+ signed_params = '?'
253
+ (options[:object_options][:parameters] || {}).each do |key, value|
254
+ if ['acl', 'cors', 'location', 'logging', 'requestPayment', 'torrent', 'versions', 'versioning'].include?(key)
255
+ signed_params << "#{key}&"
256
+ else
257
+ other_params << (value.empty? ? "#{key}&" : "#{key}=#{value}&")
258
+ end
259
+ end
260
+ signed_params.chop!
261
+
262
+ url << signed_params
263
+
264
+
265
+ #
266
+ # Build a request signature
267
+ #
268
+ signature = "#{verb}\n#{options[:object_options][:headers]['Content-Md5']}\n#{options[:object_options][:headers]['Content-Type']}\n#{options[:object_options][:expires]}\n"
269
+ if verb != :GET
270
+ options[:object_options][:headers]['x-goog-date'] ||= Time.now.utc.httpdate
271
+
272
+ google_headers, canonical_google_headers = {}, '' # Copied from https://github.com/fog/fog/blob/master/lib/fog/google/storage.rb
273
+ for key, value in options[:object_options][:headers]
274
+ if key[0..6] == 'x-goog-'
275
+ google_headers[key] = value
276
+ end
277
+ end
278
+
279
+ google_headers = google_headers.sort {|x, y| x[0] <=> y[0]}
280
+ for key, value in google_headers
281
+ signature << "#{key}:#{value}\n"
282
+ end
283
+ end
284
+
285
+ signature << "/#{options[:bucket_name]}#{url}"
286
+
287
+
288
+ #
289
+ # Encode the request signature
290
+ #
291
+ if @options[:api] == 1
292
+ signature = Base64.encode64(OpenSSL::HMAC.digest(OpenSSL::Digest::Digest.new('sha1'), @options[:secret_key], signature)).gsub("\n","")
293
+ options[:object_options][:headers]['Authorization'] = "GOOG1 #{@options[:access_id]}:#{signature}"
294
+ else
295
+ signature = Base64.encode64(@options[:secret_key].sign(OpenSSL::Digest::SHA256.new, signature)).gsub("\n","")
296
+ end
297
+
298
+
299
+ url += signed_params.present? ? '&' : '?'
300
+ url = "#{options[:object_options][:protocol]}://#{options[:bucket_name]}.storage.googleapis.com#{url}#{other_params}GoogleAccessId=#{@options[:access_id]}&Expires=#{options[:object_options][:expires]}&Signature=#{CGI::escape(signature)}"
301
+
302
+
303
+ #
304
+ # Finish building the request
305
+ #
306
+ return {
307
+ :verb => options[:object_options][:verb].to_s.upcase,
308
+ :url => url,
309
+ :headers => options[:object_options][:headers]
310
+ }
311
+ end
312
+
313
+
314
+ end
315
+