condo 1.0.6 → 2.0.0

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 (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