condo 1.0.6 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (60) hide show
  1. checksums.yaml +4 -4
  2. data/README.textile +19 -32
  3. data/lib/condo.rb +124 -127
  4. data/lib/condo/configuration.rb +41 -76
  5. data/lib/condo/engine.rb +32 -39
  6. data/lib/condo/errors.rb +6 -8
  7. data/lib/condo/strata/amazon_s3.rb +246 -294
  8. data/lib/condo/strata/google_cloud_storage.rb +238 -272
  9. data/lib/condo/strata/open_stack_swift.rb +251 -0
  10. data/lib/condo/version.rb +1 -1
  11. metadata +31 -96
  12. data/app/assets/javascripts/condo.js +0 -9
  13. data/app/assets/javascripts/condo/amazon.js +0 -403
  14. data/app/assets/javascripts/condo/condo.js +0 -184
  15. data/app/assets/javascripts/condo/config.js +0 -69
  16. data/app/assets/javascripts/condo/google.js +0 -338
  17. data/app/assets/javascripts/condo/md5/hash.worker.emulator.js +0 -23
  18. data/app/assets/javascripts/condo/md5/hash.worker.js +0 -11
  19. data/app/assets/javascripts/condo/md5/hasher.js +0 -119
  20. data/app/assets/javascripts/condo/md5/spark-md5.js +0 -599
  21. data/app/assets/javascripts/condo/rackspace.js +0 -326
  22. data/app/assets/javascripts/condo/services/abstract-md5.js.erb +0 -86
  23. data/app/assets/javascripts/condo/services/base64.js +0 -184
  24. data/app/assets/javascripts/condo/services/broadcaster.js +0 -26
  25. data/app/assets/javascripts/condo/services/uploader.js +0 -302
  26. data/app/assets/javascripts/core/core.js +0 -4
  27. data/app/assets/javascripts/core/services/1-safe-apply.js +0 -17
  28. data/app/assets/javascripts/core/services/2-messaging.js +0 -171
  29. data/lib/condo/strata/rackspace_cloud_files.rb +0 -245
  30. data/test/condo_test.rb +0 -27
  31. data/test/dummy/README.rdoc +0 -261
  32. data/test/dummy/Rakefile +0 -7
  33. data/test/dummy/app/assets/javascripts/application.js +0 -15
  34. data/test/dummy/app/assets/stylesheets/application.css +0 -13
  35. data/test/dummy/app/controllers/application_controller.rb +0 -3
  36. data/test/dummy/app/helpers/application_helper.rb +0 -2
  37. data/test/dummy/app/views/layouts/application.html.erb +0 -14
  38. data/test/dummy/config.ru +0 -4
  39. data/test/dummy/config/application.rb +0 -59
  40. data/test/dummy/config/boot.rb +0 -10
  41. data/test/dummy/config/database.yml +0 -25
  42. data/test/dummy/config/environment.rb +0 -5
  43. data/test/dummy/config/environments/development.rb +0 -37
  44. data/test/dummy/config/environments/production.rb +0 -67
  45. data/test/dummy/config/environments/test.rb +0 -37
  46. data/test/dummy/config/initializers/backtrace_silencers.rb +0 -7
  47. data/test/dummy/config/initializers/inflections.rb +0 -15
  48. data/test/dummy/config/initializers/mime_types.rb +0 -5
  49. data/test/dummy/config/initializers/secret_token.rb +0 -7
  50. data/test/dummy/config/initializers/session_store.rb +0 -8
  51. data/test/dummy/config/initializers/wrap_parameters.rb +0 -14
  52. data/test/dummy/config/locales/en.yml +0 -5
  53. data/test/dummy/config/routes.rb +0 -58
  54. data/test/dummy/public/404.html +0 -26
  55. data/test/dummy/public/422.html +0 -26
  56. data/test/dummy/public/500.html +0 -25
  57. data/test/dummy/public/favicon.ico +0 -0
  58. data/test/dummy/script/rails +0 -6
  59. data/test/integration/navigation_test.rb +0 -10
  60. data/test/test_helper.rb +0 -15
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 335bdd9a275e2321248b001a74b6e123d50358ad
4
- data.tar.gz: 2f13c901661a82d195d0bd090bb549d00c6de4e5
3
+ metadata.gz: 92cc7003ecce7499b7c92bc3aed7facde40cf79d
4
+ data.tar.gz: a4b4e8d9a93bace70e3c236d13d23d97116dd44e
5
5
  SHA512:
6
- metadata.gz: a842e8e7fd460e1345205e3e73d5e74efdd28b5962b6a990320b919c4f5b68b58300b5249a0df31a48354ac78b8eeb788c23f95d32353ea0409179e13b1542c7
7
- data.tar.gz: 8e9e92aa03407f99aecfb0ac2cba68f5ba077d2d19834cc75668ad992d46b8c9436c2e40e26e434b5c146ff31dc943992f09d6319f7f6700f2193fab624af14d
6
+ metadata.gz: c45dcf3f6aed9abe71705db52d83ffd3731c105259d10d43672f2deff33d2e19bcb6e3d7e77a7b47e373ef882ddf3ffc9949661363460a103f25356787e402cc
7
+ data.tar.gz: bbd682e45ad4a9f651d77abd5cae57e14b163ca476d38cc89c78ff8b3678c35ab37a9c6df36a601e2f28339a605ff67958a2bef7faf82d59f661aad6a6ba3ebd
@@ -3,6 +3,7 @@ h1. Condominios aka Condo
3
3
  A "Rails plugin":http://guides.rubyonrails.org/plugins.html and "AngularJS application":http://angularjs.org/ that makes direct uploads to multiple cloud storage providers easy.
4
4
  Only supports "XMLHttpRequest Level 2":http://en.wikipedia.org/wiki/XMLHttpRequest capable browsers and cloud providers that have a "RESTful API":http://en.wikipedia.org/wiki/Representational_state_transfer with "CORS":http://en.wikipedia.org/wiki/Cross-origin_resource_sharing support.
5
5
 
6
+
6
7
  Why compromise?
7
8
 
8
9
  Get started now: @gem install condo@ or checkout the "example application":https://github.com/cotag/condo_example
@@ -22,18 +23,23 @@ The API is RESTful, providing an abstraction layer and signed URLs that can be u
22
23
  The main advantages are:
23
24
  * Off-loads processing to client machines
24
25
  * Better guarantees against upload corruption
25
- ** file hashing on the client side instead of an intermediary where it probably won't be hashed either
26
- * Upload results are guaranteed if the cloud provider provides atomic operations
26
+ ** file hashing on the client side
27
+ * Upload results are guaranteed
27
28
  ** user is always aware of any failures in the process
28
29
  * Detailed progress and control over the upload
29
30
 
30
31
  This has numerous advantages over traditional Form Data style post uploads too.
31
32
  * Progress bars
32
33
  * Resumability when uploading large files
34
+ * Optional parallel uploads (multiple parts of the file simultaneously)
33
35
 
34
36
 
35
37
  Support for all major browsers
36
- * Tested in Firefox latest stable, Chromes latest stable, Safari 6, Opera 12 and IE10
38
+ * Chrome
39
+ * Firefox
40
+ * Safari
41
+ * Opera
42
+ * IE10+
37
43
 
38
44
 
39
45
  h2. Usage
@@ -88,12 +94,11 @@ In an initialiser do the following:
88
94
 
89
95
  <pre><code class="ruby">
90
96
  Condo::Configuration.add_residence(:AmazonS3, {
91
- :access_id => ENV['S3_KEY'],
92
- :secret_key => ENV['S3_SECRET']
93
- # :location => 'us-west-1' # or 'ap-southeast-1' etc (see http://docs.amazonwebservices.com/general/latest/gr/rande.html#s3_region)
94
- # Defaults to 'us-east-1' or US Standard - not required for Google
95
- # :namespace => :admin_resident # Allows you to assign different defaults to different controllers
96
- # Controller must have the following line 'set_namespace :admin_resident'
97
+ :access_id => ENV['S3_KEY'],
98
+ :secret_key => ENV['S3_SECRET']
99
+ # :location => 'us-west-1' # or 'ap-southeast-1' etc (see http://docs.amazonwebservices.com/general/latest/gr/rande.html#s3_region)
100
+ # Defaults to 'us-east-1' or US Standard - not required for Google
101
+ # :namespace => :admin_resident # Allows you to assign different defaults to different controllers or users etc
97
102
  })
98
103
 
99
104
  </code></pre>
@@ -102,32 +107,14 @@ The first residence to be defined in a namespace will be the default. To change
102
107
  Currently available residencies:
103
108
  * :AmazonS3
104
109
  * :GoogleCloudStorage
105
- * :RackspaceCloudFiles
106
-
110
+ * :RackspaceCloudFiles (Works with Swift left as rackspace for backwards compatibility)
107
111
 
108
- You can also define a dynamic residence each request (maybe clients provided you with access information for their storage provider)
109
112
 
110
- <pre><code class="ruby">
111
- set_residence(:AmazonS3, {
112
- :access_id => user.s3_key,
113
- :secret_key => user.s3_secret,
114
- :dynamic => true # Otherwise the same as add_residence
115
- });
116
-
117
-
118
- </code></pre>
113
+ Note:: There is also a callback to dynamically set storage provider. Which is useful if your users use:
114
+ * Their own bucket
115
+ * Different cloud providers etc
119
116
 
120
117
 
121
118
  h3. Callbacks
122
119
 
123
- These are pretty well defined "here":https://github.com/cotag/condo_example/blob/master/app/controllers/uploads_controller.rb
124
-
125
-
126
- h2. TODO::
127
-
128
- # Write tests... So many tests
129
- # Create a wiki describing things in more detail
130
- # Implement API for more residencies
131
- # Sign other useful requests (bucket listings with search etc)
132
- #* For Dropbox or Megaupload style applications
133
-
120
+ These are pretty well defined "here":https://github.com/cotag/Condominios/blob/master/lib/condo/configuration.rb
@@ -1,3 +1,6 @@
1
+ # encoding: utf-8
2
+
3
+ require 'fog'
1
4
  require 'condo/engine'
2
5
  require 'condo/errors'
3
6
  require 'condo/configuration'
@@ -6,14 +9,12 @@ require 'condo/configuration'
6
9
  module Condo
7
10
  def self.included(base)
8
11
  base.class_eval do
9
-
10
-
12
+
13
+
11
14
  def new
12
- #
13
15
  # Returns the provider that will be used for this file upload
14
16
  resident = current_resident
15
17
 
16
- #
17
18
  # Ensure parameters are correct
18
19
  params.require(:file_size)
19
20
  params.require(:file_name)
@@ -23,30 +24,25 @@ module Condo
23
24
  file_name: @@callbacks[:sanitize_filename].call(permitted[:file_name])
24
25
  }
25
26
  @upload[:file_path] = @@callbacks[:sanitize_filepath].call(permitted[:file_path]) if permitted[:file_path]
26
-
27
+
27
28
  valid, errors = instance_exec(@upload, &@@callbacks[:pre_validation]) # Ensure the upload request is valid before uploading
28
29
 
29
- if !!valid
30
- set_residence(nil, {:resident => resident, :params => @upload}) if condo_config.dynamic_provider_present?(@@namespace)
30
+ if valid
31
31
  residence = current_residence
32
-
33
- render :json => {:residence => residence.name}
34
-
32
+
33
+ render json: {residence: residence.name}
35
34
  elsif errors.is_a? Hash
36
- render :json => errors, :status => :not_acceptable
35
+ render json: errors, status: :not_acceptable
37
36
  else
38
- render :nothing => true, :status => :not_acceptable
37
+ render nothing: true, status: :not_acceptable
39
38
  end
40
39
  end
41
-
40
+
42
41
  def create
43
- #
44
42
  # Check for existing upload or create a new one
45
43
  # => mutually exclusive so can send back either the parts signature from show or a bucket creation signature and the upload_id
46
- #
47
44
  resident = current_resident
48
-
49
- #
45
+
50
46
  # Ensure parameters are correct
51
47
  params.require(:file_size)
52
48
  params.require(:file_name)
@@ -59,54 +55,48 @@ module Condo
59
55
  user_id: resident
60
56
  }
61
57
  @upload[:file_path] = @@callbacks[:sanitize_filepath].call(permitted[:file_path]) if permitted[:file_path]
62
-
63
- #
58
+
64
59
  # Check for existing uploads
65
60
  upload = condo_backend.check_exists(@upload)
66
-
61
+
67
62
  if upload.present?
68
- residence = set_residence(upload.provider_name, {
69
- :location => upload.provider_location,
70
- :upload => upload
71
- })
72
-
73
- #
63
+ residence = current_residence(upload)
64
+
74
65
  # Return the parts or direct upload sig
75
- #
76
66
  request = nil
77
67
  if upload.resumable_id.present? && upload.resumable
78
68
  request = residence.get_parts({
79
- :bucket_name => upload.bucket_name,
80
- :object_key => upload.object_key,
81
- :object_options => upload.object_options,
82
- :file_size => upload.file_size,
83
- :resumable_id => upload.resumable_id
69
+ bucket_name: upload.bucket_name,
70
+ object_key: upload.object_key,
71
+ object_options: upload.object_options,
72
+ file_size: upload.file_size,
73
+ resumable_id: upload.resumable_id
84
74
  })
75
+
76
+ request[:part_list] = upload.part_list || []
77
+ request[:part_data] = upload.part_data if upload.part_data
85
78
  else
86
79
  request = residence.new_upload({
87
- :bucket_name => upload.bucket_name,
88
- :object_key => upload.object_key,
89
- :object_options => upload.object_options,
90
- :file_size => upload.file_size,
91
- :file_id => upload.file_id
80
+ bucket_name: upload.bucket_name,
81
+ object_key: upload.object_key,
82
+ object_options: upload.object_options,
83
+ file_size: upload.file_size,
84
+ file_id: upload.file_id
92
85
  })
93
86
  end
94
87
 
95
- render :json => request.merge(:upload_id => upload.id, :residence => residence.name)
88
+ render json: request.merge!({
89
+ upload_id: upload.id,
90
+ residence: residence.name
91
+ })
96
92
  else
97
- #
98
93
  # Create a new upload
99
- #
100
94
  valid, errors = instance_exec(@upload, &@@callbacks[:pre_validation]) # Ensure the upload request is valid before uploading
101
-
102
-
95
+
103
96
  if valid
104
- set_residence(nil, {:resident => resident, :params => @upload}) if condo_config.dynamic_provider_present?(@@namespace)
105
97
  residence = current_residence
106
-
107
- #
98
+
108
99
  # Build the request
109
- #
110
100
  @upload.merge!({
111
101
  bucket_name: (instance_eval &@@callbacks[:bucket_name]), # Allow the application to define a custom bucket name
112
102
  object_key: instance_exec(@upload, &@@callbacks[:object_key]), # The object key should also be generated by the application
@@ -114,142 +104,155 @@ module Condo
114
104
  })
115
105
  request = residence.new_upload(@upload)
116
106
  resumable = request[:type] == :chunked_upload
117
-
118
- #
107
+
119
108
  # Save a reference to this upload in the database
120
109
  # => This should throw an error on failure
121
- #
122
- upload = condo_backend.add_entry(@upload.merge!({:provider_name => residence.name, :provider_location => residence.location, :resumable => resumable}))
123
- render :json => request.merge!(:upload_id => upload.id, :residence => residence.name)
110
+ upload = condo_backend.add_entry(@upload.merge!({provider_name: residence.name, provider_location: residence.location, resumable: resumable}))
111
+ render json: request.merge!(upload_id: upload.id, residence: residence.name)
124
112
 
125
113
  elsif errors.is_a? Hash
126
- render :json => errors, :status => :not_acceptable
114
+ render json: errors, status: :not_acceptable
127
115
  else
128
- render :nothing => true, :status => :not_acceptable
116
+ render nothing: true, status: :not_acceptable
129
117
  end
130
118
  end
131
119
  end
132
-
133
-
134
- #
120
+
135
121
  # Authorization check all of these
136
- #
137
122
  def edit
138
- #
139
123
  # Get the signature for parts + final commit
140
- #
141
124
  upload = current_upload
125
+
142
126
  params.require(:part)
143
127
  safe_params = params.permit(:part, :file_id)
144
-
128
+
145
129
  if upload.resumable_id.present? && upload.resumable
146
- residence = set_residence(upload.provider_name, {:location => upload.provider_location, :upload => upload})
147
-
130
+ residence = current_residence(upload)
131
+
148
132
  request = residence.set_part({
149
- :bucket_name => upload.bucket_name,
150
- :object_key => upload.object_key,
151
- :object_options => upload.object_options,
152
- :resumable_id => upload.resumable_id,
153
- :part => safe_params[:part], # part may be called 'finish' for commit signature
154
- :file_size => upload.file_size,
155
- :file_id => safe_params[:file_id]
133
+ bucket_name: upload.bucket_name,
134
+ object_key: upload.object_key,
135
+ object_options: upload.object_options,
136
+ resumable_id: upload.resumable_id,
137
+ # part may be called 'finish' for commit signature
138
+ part: safe_params[:part],
139
+ file_size: upload.file_size,
140
+ file_id: safe_params[:file_id]
156
141
  })
157
-
158
- render :json => request.merge!(:upload_id => upload.id)
142
+
143
+ render json: request.merge!(upload_id: upload.id)
159
144
  else
160
- render :nothing => true, :status => :not_acceptable
145
+ render nothing: true, status: :not_acceptable
161
146
  end
162
147
  end
163
-
164
-
148
+
149
+
165
150
  def update
166
- #
167
151
  # Provide the upload id after creating a resumable upload (may not be completed)
168
152
  # => We then provide the first part signature
169
153
  #
170
154
  # OR
171
155
  #
172
156
  # Complete an upload
173
- #
174
- if params[:resumable_id]
175
- upload = current_upload
157
+ upload = current_upload
158
+
159
+ safe = params.permit(:resumable_id, {part_list: []}, {part_data: [
160
+ :md5,
161
+ :size_bytes,
162
+ :path,
163
+ :part
164
+ ]})
165
+ part_list = safe[:part_list]
166
+ resumable_id = safe[:resumable_id]
167
+
168
+ if resumable_id || part_list
176
169
  if upload.resumable
177
- @current_upload = upload.update_entry :resumable_id => params.permit(:resumable_id)[:resumable_id]
178
- edit
170
+ if part_list
171
+ upload.part_list = part_list || []
172
+
173
+ # We incrementally update this as it might otherwise contain
174
+ # sum(1 -> 10,000) parts over the time of the upload
175
+ # (as is the case with the largest file amazon supports)
176
+ if safe[:part_data]
177
+ upload.part_data ||= {}
178
+ safe[:part_data].each do |part|
179
+ upload.part_data[part[:part]] = part
180
+ end
181
+ end
182
+ end
183
+
184
+ upload.resumable_id = resumable_id if resumable_id
185
+ upload.save!
186
+
187
+ if params[:part_update]
188
+ render nothing: true
189
+ else
190
+ @current_upload = upload
191
+
192
+ # Render is called from edit
193
+ edit
194
+ end
179
195
  else
180
- render :nothing => true, :status => :not_acceptable
196
+ render nothing: true, status: :not_acceptable
181
197
  end
182
- else
183
- response = instance_exec current_upload, &@@callbacks[:upload_complete]
198
+
199
+ else # We are completeing the upload
200
+ response = instance_exec upload, &@@callbacks[:upload_complete]
184
201
  if response
185
- render :nothing => true
202
+ render nothing: true
186
203
  else
187
- render :nothing => true, :status => :not_acceptable
204
+ render nothing: true, status: :not_acceptable
188
205
  end
189
206
  end
190
207
  end
191
-
192
-
208
+
193
209
  def destroy
194
- #
195
210
  # Delete the file from the cloud system - the client is not responsible for this
196
- #
197
211
  response = instance_exec current_upload, &@@callbacks[:destroy_upload]
198
212
  if response
199
- render :nothing => true
213
+ render nothing: true
200
214
  else
201
- render :nothing => true, :status => :not_acceptable
215
+ render nothing: true, status: :not_acceptable
202
216
  end
203
217
  end
204
-
205
-
218
+
219
+
206
220
  protected
207
-
208
-
209
- #
210
- # A before filter can be used to select the cloud provider for the current user
211
- # Otherwise the dynamic residence can be used when users are define their own storage locations
212
- #
213
- def set_residence(name, options = {})
214
- options[:namespace] = @@namespace
215
- @current_residence = condo_config.set_residence(name, options)
216
- end
217
-
218
- def current_residence
219
- @current_residence ||= condo_config.residencies[0]
221
+
222
+
223
+ def current_residence(upload = nil)
224
+ @current_residence ||= instance_exec(condo_config, current_resident, upload, &@@callbacks[:select_residence])
220
225
  end
221
-
226
+
222
227
  def current_upload
223
228
  return @current_upload if @current_upload
224
229
 
225
230
  safe_params = params.permit(:upload_id, :id)
226
- @current_upload = condo_backend.check_exists({:user_id => current_resident, :upload_id => (safe_params[:upload_id] || safe_params[:id])}).tap do |object| #current_residence.name && current_residence.location && resident.id.exists?
231
+ # current_residence.name && current_residence.location && resident.id.exists?
232
+ @current_upload = condo_backend.check_exists({user_id: current_resident, upload_id: (safe_params[:upload_id] || safe_params[:id])}).tap do |object|
227
233
  raise Condo::Errors::NotYourPlace unless object.present?
228
234
  end
229
235
  end
230
-
236
+
231
237
  def current_resident
232
- @current_resident ||= (instance_eval &@@callbacks[:resident_id]).tap do |object| # instance_exec for params
238
+ # instance_exec for params
239
+ @current_resident ||= (instance_eval &@@callbacks[:resident_id]).tap do |object|
233
240
  raise Condo::Errors::LostTheKeys unless object.present?
234
241
  end
235
242
  end
236
-
243
+
237
244
  def condo_backend
238
- Condo::Store
245
+ ::Condo::Store
239
246
  end
240
-
247
+
241
248
  def condo_config
242
- Condo::Configuration.instance
249
+ ::Condo::Configuration
243
250
  end
244
-
245
-
246
- #
251
+
252
+
247
253
  # Defines the default callbacks
248
- #
249
254
  (@@callbacks ||= {}).merge! Condo::Configuration.callbacks
250
- @@namespace ||= :global
251
-
252
-
255
+
253
256
  def self.condo_callback(name, callback = nil, &block)
254
257
  callback ||= block
255
258
  if callback.respond_to?(:call)
@@ -258,12 +261,6 @@ module Condo
258
261
  raise ArgumentError, 'No callback provided'
259
262
  end
260
263
  end
261
-
262
-
263
- def self.condo_namespace(name)
264
- @@namespace = name.to_sym
265
- end
266
-
267
264
  end
268
265
  end
269
266
  end