condo 1.0.4 → 1.0.6
Sign up to get free protection for your applications and to get access to all the features.
- 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);
|