condo 0.0.1

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 (54) hide show
  1. data/LGPL3-LICENSE +165 -0
  2. data/README.textile +20 -0
  3. data/Rakefile +40 -0
  4. data/app/assets/javascripts/condo.js +7 -0
  5. data/app/assets/javascripts/condo/amazon.js +409 -0
  6. data/app/assets/javascripts/condo/base64.js +192 -0
  7. data/app/assets/javascripts/condo/controller.js +162 -0
  8. data/app/assets/javascripts/condo/google.js +292 -0
  9. data/app/assets/javascripts/condo/rackspace.js +340 -0
  10. data/app/assets/javascripts/condo/spark-md5.js +470 -0
  11. data/app/assets/javascripts/condo/uploader.js +298 -0
  12. data/lib/condo.rb +267 -0
  13. data/lib/condo/configuration.rb +129 -0
  14. data/lib/condo/engine.rb +36 -0
  15. data/lib/condo/errors.rb +9 -0
  16. data/lib/condo/strata/amazon_s3.rb +301 -0
  17. data/lib/condo/strata/google_cloud_storage.rb +306 -0
  18. data/lib/condo/strata/rackspace_cloud_files.rb +223 -0
  19. data/lib/condo/version.rb +3 -0
  20. data/lib/tasks/condo_tasks.rake +4 -0
  21. data/test/condo_test.rb +27 -0
  22. data/test/dummy/README.rdoc +261 -0
  23. data/test/dummy/Rakefile +7 -0
  24. data/test/dummy/app/assets/javascripts/application.js +15 -0
  25. data/test/dummy/app/assets/stylesheets/application.css +13 -0
  26. data/test/dummy/app/controllers/application_controller.rb +3 -0
  27. data/test/dummy/app/helpers/application_helper.rb +2 -0
  28. data/test/dummy/app/views/layouts/application.html.erb +14 -0
  29. data/test/dummy/config.ru +4 -0
  30. data/test/dummy/config/application.rb +59 -0
  31. data/test/dummy/config/boot.rb +10 -0
  32. data/test/dummy/config/database.yml +25 -0
  33. data/test/dummy/config/environment.rb +5 -0
  34. data/test/dummy/config/environments/development.rb +37 -0
  35. data/test/dummy/config/environments/production.rb +67 -0
  36. data/test/dummy/config/environments/test.rb +37 -0
  37. data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
  38. data/test/dummy/config/initializers/inflections.rb +15 -0
  39. data/test/dummy/config/initializers/mime_types.rb +5 -0
  40. data/test/dummy/config/initializers/secret_token.rb +7 -0
  41. data/test/dummy/config/initializers/session_store.rb +8 -0
  42. data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
  43. data/test/dummy/config/locales/en.yml +5 -0
  44. data/test/dummy/config/routes.rb +58 -0
  45. data/test/dummy/db/test.sqlite3 +0 -0
  46. data/test/dummy/log/test.log +25 -0
  47. data/test/dummy/public/404.html +26 -0
  48. data/test/dummy/public/422.html +26 -0
  49. data/test/dummy/public/500.html +25 -0
  50. data/test/dummy/public/favicon.ico +0 -0
  51. data/test/dummy/script/rails +6 -0
  52. data/test/integration/navigation_test.rb +10 -0
  53. data/test/test_helper.rb +15 -0
  54. metadata +180 -0
@@ -0,0 +1,192 @@
1
+ /*
2
+ * Copyright (c) 2010 Nick Galbreath
3
+ * http://code.google.com/p/stringencoders/source/browse/#svn/trunk/javascript
4
+ *
5
+ * Permission is hereby granted, free of charge, to any person
6
+ * obtaining a copy of this software and associated documentation
7
+ * files (the "Software"), to deal in the Software without
8
+ * restriction, including without limitation the rights to use,
9
+ * copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ * copies of the Software, and to permit persons to whom the
11
+ * Software is furnished to do so, subject to the following
12
+ * conditions:
13
+ *
14
+ * The above copyright notice and this permission notice shall be
15
+ * included in all copies or substantial portions of the Software.
16
+ *
17
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
18
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
19
+ * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
20
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
21
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
22
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
23
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
24
+ * OTHER DEALINGS IN THE SOFTWARE.
25
+ */
26
+
27
+ /* base64 encode/decode compatible with window.btoa/atob
28
+ *
29
+ * window.atob/btoa is a Firefox extension to convert binary data (the "b")
30
+ * to base64 (ascii, the "a").
31
+ *
32
+ * It is also found in Safari and Chrome. It is not available in IE.
33
+ *
34
+ * if (!window.btoa) window.btoa = base64.encode
35
+ * if (!window.atob) window.atob = base64.decode
36
+ *
37
+ * The original spec's for atob/btoa are a bit lacking
38
+ * https://developer.mozilla.org/en/DOM/window.atob
39
+ * https://developer.mozilla.org/en/DOM/window.btoa
40
+ *
41
+ * window.btoa and base64.encode takes a string where charCodeAt is [0,255]
42
+ * If any character is not [0,255], then an DOMException(5) is thrown.
43
+ *
44
+ * window.atob and base64.decode take a base64-encoded string
45
+ * If the input length is not a multiple of 4, or contains invalid characters
46
+ * then an DOMException(5) is thrown.
47
+ */
48
+
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) {
59
+ 'use strict';
60
+
61
+ var base64 = {};
62
+ base64.PADCHAR = '=';
63
+ base64.ALPHA = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
64
+
65
+ base64.makeDOMException = function() {
66
+ // sadly in FF,Safari,Chrome you can't make a DOMException
67
+ var e, tmp;
68
+
69
+ try {
70
+ return new DOMException(DOMException.INVALID_CHARACTER_ERR);
71
+ } catch (tmp) {
72
+ // not available, just passback a duck-typed equiv
73
+ // https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Global_Objects/Error
74
+ // https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Global_Objects/Error/prototype
75
+ var ex = new Error("DOM Exception 5");
76
+
77
+ // ex.number and ex.description is IE-specific.
78
+ ex.code = ex.number = 5;
79
+ ex.name = ex.description = "INVALID_CHARACTER_ERR";
80
+
81
+ // Safari/Chrome output format
82
+ ex.toString = function() { return 'Error: ' + ex.name + ': ' + ex.message; };
83
+ return ex;
84
+ }
85
+ }
86
+
87
+ base64.getbyte64 = function(s,i) {
88
+ // This is oddly fast, except on Chrome/V8.
89
+ // Minimal or no improvement in performance by using a
90
+ // object with properties mapping chars to value (eg. 'A': 0)
91
+ var idx = base64.ALPHA.indexOf(s.charAt(i));
92
+ if (idx === -1) {
93
+ throw base64.makeDOMException();
94
+ }
95
+ return idx;
96
+ }
97
+
98
+ base64.decode = function(s) {
99
+ // convert to string
100
+ s = '' + s;
101
+ var getbyte64 = base64.getbyte64;
102
+ var pads, i, b10;
103
+ var imax = s.length
104
+ if (imax === 0) {
105
+ return s;
106
+ }
107
+
108
+ if (imax % 4 !== 0) {
109
+ throw base64.makeDOMException();
110
+ }
111
+
112
+ pads = 0
113
+ if (s.charAt(imax - 1) === base64.PADCHAR) {
114
+ pads = 1;
115
+ if (s.charAt(imax - 2) === base64.PADCHAR) {
116
+ pads = 2;
117
+ }
118
+ // either way, we want to ignore this last block
119
+ imax -= 4;
120
+ }
121
+
122
+ var x = [];
123
+ for (i = 0; i < imax; i += 4) {
124
+ b10 = (getbyte64(s,i) << 18) | (getbyte64(s,i+1) << 12) |
125
+ (getbyte64(s,i+2) << 6) | getbyte64(s,i+3);
126
+ x.push(String.fromCharCode(b10 >> 16, (b10 >> 8) & 0xff, b10 & 0xff));
127
+ }
128
+
129
+ switch (pads) {
130
+ case 1:
131
+ b10 = (getbyte64(s,i) << 18) | (getbyte64(s,i+1) << 12) | (getbyte64(s,i+2) << 6);
132
+ x.push(String.fromCharCode(b10 >> 16, (b10 >> 8) & 0xff));
133
+ break;
134
+ case 2:
135
+ b10 = (getbyte64(s,i) << 18) | (getbyte64(s,i+1) << 12);
136
+ x.push(String.fromCharCode(b10 >> 16));
137
+ break;
138
+ }
139
+ return x.join('');
140
+ }
141
+
142
+ base64.getbyte = function(s,i) {
143
+ var x = s.charCodeAt(i);
144
+ if (x > 255) {
145
+ throw base64.makeDOMException();
146
+ }
147
+ return x;
148
+ }
149
+
150
+ base64.encode = function(s) {
151
+ if (arguments.length !== 1) {
152
+ throw new SyntaxError("Not enough arguments");
153
+ }
154
+ var padchar = base64.PADCHAR;
155
+ var alpha = base64.ALPHA;
156
+ var getbyte = base64.getbyte;
157
+
158
+ var i, b10;
159
+ var x = [];
160
+
161
+ // convert to string
162
+ s = '' + s;
163
+
164
+ var imax = s.length - s.length % 3;
165
+
166
+ if (s.length === 0) {
167
+ return s;
168
+ }
169
+ for (i = 0; i < imax; i += 3) {
170
+ b10 = (getbyte(s,i) << 16) | (getbyte(s,i+1) << 8) | getbyte(s,i+2);
171
+ x.push(alpha.charAt(b10 >> 18));
172
+ x.push(alpha.charAt((b10 >> 12) & 0x3F));
173
+ x.push(alpha.charAt((b10 >> 6) & 0x3f));
174
+ x.push(alpha.charAt(b10 & 0x3f));
175
+ }
176
+ switch (s.length - imax) {
177
+ case 1:
178
+ b10 = getbyte(s,i) << 16;
179
+ x.push(alpha.charAt(b10 >> 18) + alpha.charAt((b10 >> 12) & 0x3F) +
180
+ padchar + padchar);
181
+ break;
182
+ case 2:
183
+ b10 = (getbyte(s,i) << 16) | (getbyte(s,i+1) << 8);
184
+ x.push(alpha.charAt(b10 >> 18) + alpha.charAt((b10 >> 12) & 0x3F) +
185
+ alpha.charAt((b10 >> 6) & 0x3f) + padchar);
186
+ break;
187
+ }
188
+ return x.join('');
189
+ }
190
+
191
+ return base64;
192
+ }));
@@ -0,0 +1,162 @@
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
+ * * https://github.com/umdjs/umd
13
+ * * https://github.com/addyosmani/jquery-plugin-patterns
14
+ * * http://ericterpstra.com/2012/09/angular-cats-part-3-communicating-with-broadcast/
15
+ * * http://docs.angularjs.org/api/ng.$rootScope.Scope#$watch
16
+ *
17
+ **/
18
+
19
+ (function (factory) {
20
+ if (typeof define === 'function' && define.amd) {
21
+ // AMD
22
+ define(['jquery', 'condo_uploader'], factory);
23
+ } else {
24
+ // Browser globals
25
+ factory(jQuery, window.CondoUploader);
26
+ }
27
+ }(function ($, uploads, undefined) {
28
+ 'use strict';
29
+
30
+
31
+
32
+ //
33
+ // Create a controller for managing the upload states
34
+ //
35
+ uploads.factory('Condo.Broadcast', ['$rootScope', function($rootScope) {
36
+ // eventBroadcaster is the object created by the factory method.
37
+ var eventBroadcaster = {};
38
+
39
+ // The message is a string or object to carry data with the event.
40
+ eventBroadcaster.message = '';
41
+
42
+ // The event name is a string used to define event types.
43
+ eventBroadcaster.eventName = '';
44
+
45
+ // This method is called from within a controller to define an event and attach data to the eventBroadcaster object.
46
+ eventBroadcaster.broadcast = function(evName, msg) {
47
+ this.message = msg;
48
+ this.eventName = evName;
49
+ this.broadcastItem();
50
+ };
51
+
52
+ // This method broadcasts an event with the specified name.
53
+ eventBroadcaster.broadcastItem = function() {
54
+ $rootScope.$broadcast(this.eventName);
55
+ };
56
+
57
+ return eventBroadcaster;
58
+
59
+
60
+
61
+ }]).controller('UploadsCtrl', ['$scope', 'Condo.Api', 'Condo.Broadcast', function($scope, api, broadcaster) {
62
+
63
+ $scope.uploads = [];
64
+ $scope.endpoint = '/uploads'; // Default, the directive can overwrite this
65
+ $scope.autostart = true;
66
+
67
+
68
+ $scope.add = function(files) {
69
+ var length = files.length,
70
+ i = 0,
71
+ ret = 0; // We only want to check for auto-start after the files have been added
72
+
73
+ for (; i < length; i += 1) {
74
+ api.check_provider($scope.endpoint, files[i]).then(function(upload){
75
+ ret += 1;
76
+ $scope.uploads.push(upload);
77
+ if(ret == length)
78
+ $scope.check_autostart();
79
+ }, function(failure) {
80
+
81
+ ret += 1;
82
+ if(ret == length)
83
+ $scope.check_autostart();
84
+
85
+ //
86
+ // broadcast this so it can be handled by a directive
87
+ //
88
+ broadcaster.broadcast('coFileAddFailed', failure);
89
+ });
90
+ }
91
+ };
92
+
93
+
94
+ $scope.abort = function(upload) {
95
+ upload.abort();
96
+ $scope.check_autostart();
97
+ };
98
+
99
+
100
+ $scope.remove = function(upload) {
101
+ //
102
+ // Splice(upload, 1) was unreliable. This is better
103
+ //
104
+ for (var i = 0, length = $scope.uploads.length; i < length; i += 1) {
105
+ if($scope.uploads[i] === upload) {
106
+ $scope.uploads.splice(i, 1);
107
+ break;
108
+ }
109
+ }
110
+ };
111
+
112
+
113
+ $scope.playpause = function(upload) {
114
+ if (upload.state == 3) // Uploading
115
+ upload.pause();
116
+ else
117
+ upload.start();
118
+ };
119
+
120
+
121
+ //
122
+ // Watch autostart and trigger a check when it is changed
123
+ //
124
+ $scope.$watch('autostart', function(newValue, oldValue) {
125
+ if (newValue === true)
126
+ $scope.check_autostart();
127
+ });
128
+
129
+
130
+ $scope.check_autostart = function() {
131
+ //
132
+ // Check if any uploads have been started already
133
+ // If there are no active uploads we'll auto-start
134
+ //
135
+ if ($scope.autostart) {
136
+ var shouldStart = true,
137
+ state, i, length;
138
+
139
+ for (i = 0, length = $scope.uploads.length; i < length; i += 1) {
140
+ state = $scope.uploads[i].state;
141
+ if (state > 0 && state < 4) {
142
+ shouldStart = false;
143
+ break;
144
+ }
145
+ }
146
+
147
+ if (shouldStart) {
148
+ for (i = 0; i < length; i += 1) {
149
+ if ($scope.uploads[i].state == 0) {
150
+ $scope.uploads[i].start();
151
+ break;
152
+ }
153
+ }
154
+ }
155
+ }
156
+ };
157
+
158
+ }]);
159
+
160
+
161
+
162
+ }));
@@ -0,0 +1,292 @@
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
+ * * https://github.com/umdjs/umd
13
+ * * https://github.com/addyosmani/jquery-plugin-patterns
14
+ * *
15
+ *
16
+ **/
17
+
18
+ (function (factory) {
19
+ if (typeof define === 'function' && define.amd) {
20
+ // AMD
21
+ define(['jquery', 'spark-md5', 'base64', 'condo_uploader'], factory);
22
+ } else {
23
+ // Browser globals
24
+ factory(jQuery, window.SparkMD5, window.base64, window.CondoUploader);
25
+ }
26
+ }(function ($, MD5, base64, uploads, undefined) {
27
+ 'use strict';
28
+
29
+ //
30
+ // TODO:: Create an Amazon, google factory etc
31
+ // We should split all these into different files too (controller and factories separate from directives and views)
32
+ // So we can have different views for the same controller
33
+ //
34
+ uploads.factory('Condo.GoogleCloudStorage', ['$rootScope', '$q', function($rootScope, $q) {
35
+ var PENDING = 0,
36
+ STARTED = 1,
37
+ PAUSED = 2,
38
+ UPLOADING = 3,
39
+ COMPLETED = 4,
40
+ ABORTED = 5,
41
+
42
+
43
+
44
+ hexToBin = function(input) {
45
+ var result = "";
46
+
47
+ if ((input.length % 2) > 0) {
48
+ input = '0' + input;
49
+ }
50
+
51
+ for (var i = 0, length = input.length; i < length; i += 2) {
52
+ result += String.fromCharCode(parseInt(input.slice(i, i + 2), 16));
53
+ }
54
+
55
+ return result;
56
+ },
57
+
58
+
59
+ GoogleCloudStorage = function (api, file) {
60
+ var self = this,
61
+ strategy = null,
62
+ part_size = 1048576, // This is the amount of the file we read into memory as we are building the hash (1mb)
63
+ defaultError = function(reason) {
64
+ self.pause(reason);
65
+ },
66
+
67
+ restart = function() {
68
+ strategy = null;
69
+ },
70
+
71
+
72
+ completeUpload = function() {
73
+ api.update().then(function(data) {
74
+ self.state = COMPLETED;
75
+ }, defaultError);
76
+ },
77
+
78
+
79
+ //
80
+ // We need to sign our uploads so Google can confirm they are valid for us
81
+ // TODO:: use http://updates.html5rocks.com/2011/12/Transferable-Objects-Lightning-Fast
82
+ // where available :) - especially important since we have to hash the entire file
83
+ //
84
+ build_request = function(part_number, hash) {
85
+ var result = $q.defer(),
86
+ reader = new FileReader(),
87
+ fail = function(){
88
+ result.reject('file read failed');
89
+ },
90
+ current_part;
91
+
92
+ if (part_number == 1) {
93
+ hash = new MD5();
94
+ }
95
+
96
+ if (file.size > part_size) { // If file bigger then 5mb we expect a chunked upload
97
+ var endbyte = part_number * part_size;
98
+ if (endbyte > file.size)
99
+ endbyte = file.size;
100
+ current_part = file.slice((part_number - 1) * part_size, endbyte);
101
+ } else {
102
+ current_part = file;
103
+ }
104
+
105
+ reader.onload = function(e) {
106
+ hash.appendBinary(e.target.result);
107
+ result.resolve(hash);
108
+
109
+
110
+ if(!$rootScope.$$phase) {
111
+ $rootScope.$apply(); // This triggers the promise response if required
112
+ }
113
+ };
114
+ reader.onerror = fail;
115
+ reader.onabort = fail;
116
+ reader.readAsBinaryString(current_part);
117
+
118
+ //
119
+ // Chaining promises means the UI will have a chance to update
120
+ //
121
+ return result.promise.then(function(val){
122
+ if ((part_number * part_size) < file.size) {
123
+ return build_request(part_number + 1, val);
124
+ } else {
125
+ return {
126
+ data: file,
127
+ data_id: base64.encode(hexToBin(val.end()))
128
+ }
129
+ }
130
+ }, function(reason){
131
+ $q.reject(reason);
132
+ });
133
+ },
134
+
135
+ //
136
+ // Direct file upload strategy
137
+ //
138
+ GoogleDirect = function(data) {
139
+ //
140
+ // resume
141
+ // abort
142
+ // pause
143
+ //
144
+ var $this = this,
145
+ finalising = false;
146
+
147
+ //
148
+ // Update the parent
149
+ //
150
+ self.state = UPLOADING;
151
+
152
+
153
+ //
154
+ // This will only be called when the upload has finished and we need to inform the application
155
+ //
156
+ this.resume = function() {
157
+ self.state = UPLOADING;
158
+ completeUpload();
159
+ }
160
+
161
+ this.pause = function() {
162
+ api.abort();
163
+
164
+ if(!finalising) {
165
+ restart(); // Should occur before events triggered
166
+ self.progress = 0;
167
+ }
168
+ };
169
+
170
+
171
+ //
172
+ // AJAX for upload goes here
173
+ //
174
+ data['data'] = file;
175
+ api.process_request(data, function(progress) {
176
+ self.progress = progress;
177
+ }).then(function(result) {
178
+ finalising = true;
179
+ $this.resume(); // Resume informs the application that the upload is complete
180
+ }, function(reason) {
181
+ self.progress = 0;
182
+ defaultError(reason);
183
+ });
184
+ }, // END DIRECT
185
+
186
+
187
+ //
188
+ // Resumable upload strategy--------------------------------------------------
189
+ //
190
+ GoogleResumable = function (data, first_chunk) {
191
+
192
+ }; // END RESUMABLE
193
+
194
+
195
+ //
196
+ // Variables required for all drivers
197
+ //
198
+ this.state = PENDING;
199
+ this.progress = 0;
200
+ this.message = 'pending';
201
+ this.name = file.name;
202
+ this.size = file.size;
203
+
204
+
205
+ //
206
+ // Support file slicing
207
+ //
208
+ if (typeof(file.slice) != 'function')
209
+ file.slice = file.webkitSlice || file.mozSlice;
210
+
211
+
212
+ this.start = function(){
213
+ if(strategy == null) { // We need to create the upload
214
+
215
+ this.message = null;
216
+ this.state = STARTED;
217
+ strategy = {}; // This function shouldn't be called twice so we need a state
218
+
219
+ build_request(1).then(function(result) {
220
+ if (self.state != STARTED)
221
+ return; // upload was paused or aborted as we were reading the file
222
+
223
+ api.create({file_id: result.data_id}).
224
+ then(function(data) {
225
+ if(data.type == 'direct_upload') {
226
+ strategy = new GoogleDirect(data);
227
+ } else {
228
+ strategy = new GoogleResumable(data, result);
229
+ }
230
+ }, defaultError);
231
+
232
+ }, function(reason){
233
+ self.pause(reason);
234
+ }); // END BUILD_REQUEST
235
+
236
+
237
+ } else if (this.state == PAUSED) { // We need to resume the upload if it is paused
238
+ this.message = null;
239
+ strategy.resume();
240
+ }
241
+ };
242
+
243
+ this.pause = function(reason) {
244
+ if(strategy != null && this.state == UPLOADING) { // Check if the upload is uploading
245
+ this.state = PAUSED;
246
+ strategy.pause();
247
+ } else if (this.state <= STARTED) {
248
+ this.state = PAUSED;
249
+ restart();
250
+ }
251
+ if(this.state == PAUSED)
252
+ this.message = reason;
253
+ };
254
+
255
+ this.abort = function(reason) {
256
+ if(strategy != null && this.state < COMPLETED) { // Check the upload has not finished
257
+ var old_state = this.state;
258
+
259
+ this.state = ABORTED;
260
+ api.abort();
261
+
262
+
263
+ //
264
+ // As we may not have successfully deleted the upload
265
+ // or we aborted before we received a response from create
266
+ //
267
+ restart(); // nullifies strategy
268
+
269
+
270
+ //
271
+ // if we have an upload_id then we should destroy the upload
272
+ // we won't worry if this fails as it should be automatically cleaned up by the back end
273
+ //
274
+ if(old_state > STARTED) {
275
+ api.destroy();
276
+ }
277
+
278
+ this.message = reason;
279
+ }
280
+ };
281
+ }; // END GOOGLE
282
+
283
+
284
+ return {
285
+ new_upload: function(api, file) {
286
+ return new GoogleCloudStorage(api, file);
287
+ }
288
+ };
289
+
290
+ }]);
291
+
292
+ }));