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,93 +1,86 @@
1
- (function (factory) {
2
- if (typeof define === 'function' && define.amd) {
3
- // AMD
4
- define('condo-abstract-md5', ['jquery', 'condo-broadcaster'], factory);
5
- } else {
6
- // Browser global
7
- factory(jQuery);
8
- }
9
- }(function ($, uploads, undefined) {
10
- 'use strict';
11
-
12
- angular.module('CondoAbstractMd5', ['CondoBroadcaster'])
13
- .factory('Condo.Md5', ['$rootScope', '$q', 'Condo.Broadcast', function($rootScope, $q, broadcaster) {
14
-
15
- var hasher,
16
- queue = [],
17
- ready = false,
18
- processing = undefined,
19
-
20
- //
21
- // Resolves the hashing promise
22
- //
23
- recievedMessage = function(e) {
24
- if(e.data.success) {
25
- processing.result.resolve(e.data.result);
26
- } else {
27
- processing.result.reject(e.data.result);
28
- }
29
-
30
- processing = undefined;
31
- processNext();
32
-
33
- if(!$rootScope.$$phase) {
34
- $rootScope.$apply(); // This triggers the promise response
35
- }
36
- },
37
-
38
- //
39
- // starts processing the next item if the queue is not empty
40
- //
41
- processNext = function() {
42
- if(ready && processing === undefined && queue.length > 0) {
43
- processing = queue.pop();
44
- hasher.postMessage(processing.blob);
45
- }
46
- };
47
-
48
-
49
- if(!!window.Worker) {
50
- hasher = new Worker('<%= asset_path("condo/md5/hash.worker.js") %>');
51
- hasher.onmessage = recievedMessage;
52
- hasher.onerror = function(e) {
53
- ready = false;
54
- broadcaster.broadcast('coNotice', {
55
- type: 'error',
56
- number: 1
57
- });
58
- };
59
- ready = true;
60
- } else {
61
- $.getScript('<%= asset_path("condo/md5/hash.worker.emulator.js") %>', function() {
62
- hasher = new CondoHashWorkerEmulator(recievedMessage);
63
- ready = true;
64
- processNext(); // It is possible
65
- }).fail(function(jqxhr, settings, exception) {
66
- broadcaster.broadcast('coNotice', {
67
- type: 'error',
68
- number: 1
69
- });
70
- });
71
- }
72
-
73
-
74
-
75
- return {
76
- //
77
- // Will queue a start message and return the hash result
78
- //
79
- hash: function(blob) {
80
- var result = $q.defer();
81
-
82
- queue.push({
83
- blob: blob,
84
- result: result
85
- });
86
- processNext();
87
-
88
- return result.promise;
89
- }
90
- };
91
- }]);
92
- }));
93
-
1
+ (function(angular, undefined) {
2
+ 'use strict';
3
+
4
+ angular.module('Condo').
5
+
6
+ factory('Condo.Md5', ['$rootScope', '$q', 'Condo.Broadcast', function($rootScope, $q, broadcaster) {
7
+
8
+ var hasher,
9
+ queue = [],
10
+ ready = false,
11
+ processing = undefined,
12
+
13
+ //
14
+ // Resolves the hashing promise
15
+ //
16
+ recievedMessage = function(e) {
17
+ if(e.data.success) {
18
+ processing.result.resolve(e.data.result);
19
+ } else {
20
+ processing.result.reject(e.data.result);
21
+ }
22
+
23
+ processing = undefined;
24
+ processNext();
25
+
26
+ if(!$rootScope.$$phase) {
27
+ $rootScope.$apply(); // This triggers the promise response
28
+ }
29
+ },
30
+
31
+ //
32
+ // starts processing the next item if the queue is not empty
33
+ //
34
+ processNext = function() {
35
+ if(ready && processing === undefined && queue.length > 0) {
36
+ processing = queue.pop();
37
+ hasher.postMessage(processing.blob);
38
+ }
39
+ };
40
+
41
+
42
+ if(!!window.Worker) {
43
+ hasher = new Worker('<%= asset_path("condo/md5/hash.worker.js") %>');
44
+ hasher.onmessage = recievedMessage;
45
+ hasher.onerror = function(e) {
46
+ ready = false;
47
+ broadcaster.publish('coNotice', {
48
+ type: 'error',
49
+ number: 1
50
+ });
51
+ };
52
+ ready = true;
53
+ } else {
54
+ $.getScript('<%= asset_path("condo/md5/hash.worker.emulator.js") %>', function() {
55
+ hasher = new CondoHashWorkerEmulator(recievedMessage);
56
+ ready = true;
57
+ processNext(); // It is possible
58
+ }).fail(function(jqxhr, settings, exception) {
59
+ broadcaster.publish('coNotice', {
60
+ type: 'error',
61
+ number: 1
62
+ });
63
+ });
64
+ }
65
+
66
+
67
+
68
+ return {
69
+ //
70
+ // Will queue a start message and return the hash result
71
+ //
72
+ hash: function(blob) {
73
+ var result = $q.defer();
74
+
75
+ queue.push({
76
+ blob: blob,
77
+ result: result
78
+ });
79
+ processNext();
80
+
81
+ return result.promise;
82
+ }
83
+ };
84
+ }]);
85
+ })(angular);
86
+
@@ -47,15 +47,7 @@
47
47
  */
48
48
 
49
49
 
50
- (function (factory) {
51
- if (typeof define === 'function' && define.amd) {
52
- // AMD
53
- define('base64', factory);
54
- } else {
55
- // Browser globals
56
- window.base64 = factory();
57
- }
58
- }(function (undefined) {
50
+ window.base64 = (function(undefined) {
59
51
  'use strict';
60
52
 
61
53
  var base64 = {};
@@ -189,4 +181,4 @@
189
181
  }
190
182
 
191
183
  return base64;
192
- }));
184
+ })();
@@ -0,0 +1,26 @@
1
+ /**
2
+ * CoTag Condo Amazon S3 Strategy
3
+ * Direct to cloud resumable uploads for Google Cloud Storage
4
+ *
5
+ * Copyright (c) 2012 CoTag Media.
6
+ *
7
+ * @author Stephen von Takach <steve@cotag.me>
8
+ * @copyright 2012 cotag.me
9
+ *
10
+ *
11
+ * References:
12
+ * *
13
+ *
14
+ **/
15
+
16
+
17
+ (function(angular, undefined) {
18
+ 'use strict';
19
+
20
+ angular.module('Condo').
21
+
22
+ factory('Condo.Broadcast', ['$channel', function($channel) {
23
+ return $channel.openChannel('once unique');
24
+ }]);
25
+
26
+ })(angular);
@@ -0,0 +1,302 @@
1
+ /**
2
+ * CoTag Condo
3
+ * Direct to cloud resumable uploads
4
+ *
5
+ * Copyright (c) 2012 CoTag Media.
6
+ *
7
+ * @author Stephen von Takach <steve@cotag.me>
8
+ * @copyright 2012 cotag.me
9
+ *
10
+ *
11
+ * References:
12
+ * * http://docs.angularjs.org/api/ng.$http
13
+ * * http://docs.angularjs.org/api/ng.$q
14
+ *
15
+ **/
16
+
17
+
18
+ (function(jQuery, angular, undefined) { // jQuery required for progress event
19
+ 'use strict';
20
+
21
+ angular.module('Condo').
22
+
23
+ //
24
+ // Implements the Condo API
25
+ //
26
+ provider('Condo.Api', function() {
27
+
28
+ var residencies = {};
29
+
30
+ this.register = function(provider_name, dependency) {
31
+ residencies[provider_name] = dependency;
32
+ };
33
+
34
+
35
+ this.$get = ['$http', '$rootScope', '$q', '$injector', function($http, $rootScope, $q, $injector) {
36
+ var token = $('meta[name="csrf-token"]').attr('content'),
37
+
38
+
39
+ condoConnection = function(api_endpoint, params) {
40
+ this.endpoint = api_endpoint; // The API mounting point
41
+ this.params = params; // Custom API parameters
42
+
43
+ this.upload_id = null; // The current upload ID
44
+ this.aborting = false; // Has the user has requested an abort?
45
+ this.xhr = null; // Any active cloud file xhr requests
46
+ };
47
+
48
+
49
+ // Inject the handlers
50
+ angular.forEach(residencies, function(value, key) {
51
+ residencies[key] = $injector.get(value);
52
+ });
53
+
54
+
55
+ $http.defaults.headers = {};
56
+ $http.defaults.headers['common'] = {'X-Requested-With': 'XMLHttpRequest'};
57
+ $http.defaults.headers['post'] = {'X-CSRF-Token': token};
58
+ $http.defaults.headers['put'] = {'X-CSRF-Token': token};
59
+ $http.defaults.headers['delete'] = {'X-CSRF-Token': token};
60
+
61
+ condoConnection.prototype = {
62
+
63
+
64
+ //
65
+ // Creates an entry in the database for the requested file and returns the upload signature
66
+ // If an entry already exists it returns a parts request signature for resumable uploads
67
+ //
68
+ create: function(options) { // file_id: 123, options: {}
69
+ var self = this;
70
+ options = options || {};
71
+ this.aborting = false;
72
+
73
+ if(!!options['file_id'])
74
+ this.params['file_id'] = options['file_id'];
75
+
76
+ if(!!options['parameters'])
77
+ this.params['parameters'] = options['parameters']; // We may be requesting the next set of parts
78
+
79
+ return $http({
80
+ method: 'POST',
81
+ url: this.endpoint,
82
+ params: this.params
83
+ }).then(function(result){
84
+ result = result.data;
85
+ self.upload_id = result.upload_id; // Extract the upload id from the results
86
+
87
+ if (!self.aborting)
88
+ return result;
89
+ else
90
+ return $q.reject(undefined);
91
+ }, function(reason) {
92
+ return $q.reject('upload error');
93
+ });
94
+ },
95
+
96
+
97
+ //
98
+ // This requests a chunk signature
99
+ // Only used for resumable uploads
100
+ //
101
+ edit: function(part_number, part_id) {
102
+ var self = this;
103
+ this.aborting = false;
104
+
105
+ return $http({
106
+ method: 'GET',
107
+ url: this.endpoint + '/' + this.upload_id + '/edit',
108
+ params: {
109
+ part: part_number,
110
+ file_id: part_id
111
+ }
112
+ }).then(function(result){
113
+ if (!self.aborting)
114
+ return result.data;
115
+ else
116
+ return $q.reject(undefined);
117
+ }, function(reason) {
118
+ return $q.reject('upload error');
119
+ });
120
+ },
121
+
122
+
123
+ //
124
+ // If resumable id is present the upload is updated
125
+ // Otherwise the upload deemed complete
126
+ //
127
+ update: function(params) { // optional parameters (resumable_id, file_id and part)
128
+ var self = this;
129
+
130
+ this.aborting = false;
131
+ params = params || {};
132
+
133
+ return $http({
134
+ method: 'PUT',
135
+ url: this.endpoint + '/' + this.upload_id,
136
+ params: params
137
+ }).then(function(result){
138
+ if (!self.aborting)
139
+ return result.data;
140
+ else
141
+ return $q.reject(undefined);
142
+ }, function(reason) {
143
+ if (reason.status == 401 && params.resumable_id == undefined) {
144
+ return ''; // User may have paused upload as put was being sent. We should let this through just to update the UI
145
+ } else
146
+ return $q.reject('upload error');
147
+ });
148
+ },
149
+
150
+
151
+ //
152
+ // Cancels a resumable upload
153
+ // The actual destruction of the file is handled on the server side as we can't trust the client to do this
154
+ // We don't care if this succeeds as the back-end will destroy the file eventually anyway.
155
+ //
156
+ destroy: function() {
157
+ return $http({
158
+ method: 'DELETE',
159
+ url: this.endpoint + '/' + this.upload_id
160
+ });
161
+ },
162
+
163
+
164
+
165
+ //
166
+ // Provides a promise for any request this is what communicated with the cloud storage servers
167
+ //
168
+ process_request: function(signature, progress_callback) {
169
+ var self = this,
170
+ result = $q.defer(),
171
+ params = {
172
+ url: signature.signature.url,
173
+ type: signature.signature.verb,
174
+ headers: signature.signature.headers,
175
+ processData: false,
176
+ success: function(response, textStatus, jqXHR) {
177
+ self.xhr = null;
178
+ result.resolve([response, jqXHR]);
179
+ },
180
+ error: function(jqXHR, textStatus, errorThrown) {
181
+ if(jqXHR.status === signature.expected) {
182
+ self.xhr = null;
183
+ result.resolve([errorThrown, jqXHR]);
184
+ } else {
185
+ self.xhr = null;
186
+ if (!self.aborting)
187
+ result.reject('upload error');
188
+ else
189
+ result.reject(undefined);
190
+ }
191
+ },
192
+ complete: function(jqXHR, textStatus) {
193
+ if(!$rootScope.$$phase) {
194
+ $rootScope.$apply(); // This triggers the promise response
195
+ }
196
+ }
197
+ };
198
+
199
+ this.aborting = false;
200
+
201
+ if (!!self.xhr) {
202
+ result.reject('request in progress'); // This is awesome
203
+ return result.promise;
204
+ }
205
+
206
+ if(!!signature.data){
207
+ params['data'] = signature.data;
208
+ }
209
+
210
+ if(!!progress_callback) {
211
+ params['xhr'] = function() {
212
+ var xhr = jQuery.ajaxSettings.xhr();
213
+ if(!!xhr.upload){
214
+ xhr.upload.addEventListener('progress', function(e) {
215
+ if (e.lengthComputable) {
216
+ var phase = $rootScope.$$phase;
217
+ if(phase == '$apply' || phase == '$digest') {
218
+ progress_callback(e.loaded);
219
+ } else {
220
+ $rootScope.$apply(function(){
221
+ progress_callback(e.loaded);
222
+ });
223
+ }
224
+ }
225
+ }, false);
226
+ }
227
+ return xhr;
228
+ };
229
+ }
230
+
231
+ this.xhr = jQuery.ajax(params);
232
+
233
+ return result.promise;
234
+ },
235
+
236
+
237
+ //
238
+ // Will trigger the error call-back of the xhr object
239
+ //
240
+ abort: function() {
241
+ this.aborting = true;
242
+ if(!!this.xhr) {
243
+ this.xhr.abort();
244
+ }
245
+ }
246
+ };
247
+
248
+ return {
249
+ //
250
+ // Used to determine what upload strategy to use (Amazon, Google, etc)
251
+ //
252
+ check_provider: function(api_endpoint, the_file, params) {
253
+ params = params || {};
254
+ params['file_size'] = the_file.size;
255
+ params['file_name'] = the_file.name;
256
+
257
+ if(!!the_file.dir_path)
258
+ params['file_path'] = the_file.dir_path;
259
+
260
+ return $http({
261
+ method: 'GET',
262
+ url: api_endpoint + '/new',
263
+ params: params
264
+ }).then(function(result){
265
+ if(!!residencies[result.data.residence]) {
266
+
267
+ var api = new condoConnection(api_endpoint, params);
268
+
269
+ //
270
+ // TODO:: Check if a file is already in the list and reject if it is
271
+ //
272
+ return residencies[result.data.residence].new_upload(api, the_file); // return the instantiated provider
273
+
274
+ } else {
275
+ return $q.reject({
276
+ type: 'error',
277
+ number: 0,
278
+ file: the_file
279
+ });
280
+ }
281
+ }, function(reason) {
282
+ if(reason.status == 406) {
283
+ return $q.reject({
284
+ type: 'warn',
285
+ number: 0,
286
+ details: reason.data,
287
+ file: the_file
288
+ });
289
+ } else {
290
+ return $q.reject({
291
+ type: 'warn',
292
+ number: 1,
293
+ file: the_file
294
+ });
295
+ }
296
+ });
297
+ }
298
+ };
299
+ }];
300
+ });
301
+
302
+ })(jQuery, angular);