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.
- checksums.yaml +7 -0
- data/README.textile +133 -133
- data/app/assets/javascripts/condo.js +9 -6
- data/app/assets/javascripts/condo/amazon.js +403 -406
- data/app/assets/javascripts/condo/condo.js +184 -0
- data/app/assets/javascripts/condo/config.js +69 -80
- data/app/assets/javascripts/condo/google.js +338 -255
- data/app/assets/javascripts/condo/md5/hash.worker.emulator.js +23 -23
- data/app/assets/javascripts/condo/md5/hash.worker.js +11 -11
- data/app/assets/javascripts/condo/md5/hasher.js +119 -100
- data/app/assets/javascripts/condo/md5/spark-md5.js +276 -161
- data/app/assets/javascripts/condo/rackspace.js +326 -329
- data/app/assets/javascripts/condo/{abstract-md5.js.erb → services/abstract-md5.js.erb} +86 -93
- data/app/assets/javascripts/condo/{base64.js → services/base64.js} +2 -10
- data/app/assets/javascripts/condo/services/broadcaster.js +26 -0
- data/app/assets/javascripts/condo/services/uploader.js +302 -0
- data/app/assets/javascripts/core/core.js +4 -0
- data/app/assets/javascripts/core/services/1-safe-apply.js +17 -0
- data/app/assets/javascripts/core/services/2-messaging.js +171 -0
- data/lib/condo.rb +269 -269
- data/lib/condo/configuration.rb +137 -139
- data/lib/condo/errors.rb +8 -8
- data/lib/condo/strata/amazon_s3.rb +301 -301
- data/lib/condo/strata/google_cloud_storage.rb +315 -314
- data/lib/condo/strata/rackspace_cloud_files.rb +245 -223
- data/lib/condo/version.rb +1 -1
- metadata +21 -44
- data/app/assets/javascripts/condo/broadcaster.js +0 -60
- data/app/assets/javascripts/condo/controller.js +0 -194
- data/app/assets/javascripts/condo/uploader.js +0 -310
- data/test/dummy/db/test.sqlite3 +0 -0
- data/test/dummy/log/test.log +0 -25
@@ -1,93 +1,86 @@
|
|
1
|
-
(function
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
}
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
}
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
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
|
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);
|