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
@@ -0,0 +1,184 @@
|
|
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://ericterpstra.com/2012/09/angular-cats-part-3-communicating-with-broadcast/
|
13
|
+
* * http://docs.angularjs.org/api/ng.$rootScope.Scope#$watch
|
14
|
+
*
|
15
|
+
**/
|
16
|
+
|
17
|
+
|
18
|
+
(function (angular, undefined) {
|
19
|
+
'use strict';
|
20
|
+
|
21
|
+
//
|
22
|
+
// Create a controller for managing the upload states
|
23
|
+
//
|
24
|
+
angular.module('Condo', ['Core']).
|
25
|
+
controller('Condo.Controller', ['$scope', 'Condo.Api', 'Condo.Broadcast', 'Condo.Config', function($scope, api, broadcaster, config) {
|
26
|
+
|
27
|
+
$scope.uploads = [];
|
28
|
+
$scope.upload_count = 0;
|
29
|
+
|
30
|
+
|
31
|
+
//
|
32
|
+
// See Condo.Config for configuration options
|
33
|
+
//
|
34
|
+
$scope.endpoint = config.endpoint;
|
35
|
+
$scope.autostart = config.autostart;
|
36
|
+
$scope.ignore_errors = config.ignore_errors; // Continue to autostart after an error?
|
37
|
+
$scope.parallelism = config.parallelism; // number of uploads at once
|
38
|
+
|
39
|
+
|
40
|
+
$scope.add = function(files) {
|
41
|
+
var length = files.length,
|
42
|
+
i = 0,
|
43
|
+
ret = 0, // We only want to check for auto-start after the files have been added
|
44
|
+
file;
|
45
|
+
|
46
|
+
for (; i < length; i += 1) {
|
47
|
+
file = files[i];
|
48
|
+
|
49
|
+
if(file.size <= 0 || file.type == '')
|
50
|
+
continue;
|
51
|
+
|
52
|
+
//
|
53
|
+
// check file size is acceptable
|
54
|
+
//
|
55
|
+
if(!config.file_checker(file) || (config.size_limit != undefined && file.size > config.size_limit)) {
|
56
|
+
broadcaster.publish('coNotice', {
|
57
|
+
type: 'warn',
|
58
|
+
number: 0,
|
59
|
+
file: file
|
60
|
+
});
|
61
|
+
continue;
|
62
|
+
}
|
63
|
+
|
64
|
+
$scope.upload_count += 1;
|
65
|
+
|
66
|
+
api.check_provider($scope.endpoint, files[i]).then(function(upload){
|
67
|
+
ret += 1;
|
68
|
+
$scope.uploads.push(upload);
|
69
|
+
if(ret == length)
|
70
|
+
$scope.check_autostart();
|
71
|
+
}, function(failure) {
|
72
|
+
|
73
|
+
$scope.upload_count -= 1;
|
74
|
+
|
75
|
+
ret += 1;
|
76
|
+
if(ret == length)
|
77
|
+
$scope.check_autostart();
|
78
|
+
|
79
|
+
//
|
80
|
+
// broadcast this so it can be handled by a directive
|
81
|
+
//
|
82
|
+
broadcaster.publish('coNotice', failure);
|
83
|
+
});
|
84
|
+
}
|
85
|
+
};
|
86
|
+
|
87
|
+
|
88
|
+
$scope.abort = function(upload) {
|
89
|
+
upload.abort();
|
90
|
+
$scope.check_autostart();
|
91
|
+
};
|
92
|
+
|
93
|
+
|
94
|
+
$scope.remove = function(upload) {
|
95
|
+
//
|
96
|
+
// Splice(upload, 1) was unreliable. This is better
|
97
|
+
//
|
98
|
+
for (var i = 0, length = $scope.uploads.length; i < length; i += 1) {
|
99
|
+
if($scope.uploads[i] === upload) {
|
100
|
+
$scope.uploads.splice(i, 1);
|
101
|
+
$scope.upload_count -= 1;
|
102
|
+
break;
|
103
|
+
}
|
104
|
+
}
|
105
|
+
};
|
106
|
+
|
107
|
+
|
108
|
+
$scope.playpause = function(upload) {
|
109
|
+
if (upload.state == 3) // Uploading
|
110
|
+
upload.pause();
|
111
|
+
else
|
112
|
+
upload.start();
|
113
|
+
};
|
114
|
+
|
115
|
+
|
116
|
+
//
|
117
|
+
// Watch autostart and trigger a check when it is changed
|
118
|
+
//
|
119
|
+
$scope.$watch('autostart', function(newValue, oldValue) {
|
120
|
+
if (newValue === true)
|
121
|
+
$scope.check_autostart();
|
122
|
+
});
|
123
|
+
|
124
|
+
|
125
|
+
//
|
126
|
+
// Autostart more uploads as this is bumped up
|
127
|
+
//
|
128
|
+
$scope.$watch('parallelism', function(newValue, oldValue) {
|
129
|
+
if(newValue > oldValue)
|
130
|
+
$scope.check_autostart();
|
131
|
+
});
|
132
|
+
|
133
|
+
|
134
|
+
$scope.check_autostart = function() {
|
135
|
+
//
|
136
|
+
// Check if any uploads have been started already
|
137
|
+
// If there are no active uploads we'll auto-start
|
138
|
+
//
|
139
|
+
// PENDING = 0,
|
140
|
+
// STARTED = 1,
|
141
|
+
// PAUSED = 2,
|
142
|
+
// UPLOADING = 3,
|
143
|
+
// COMPLETED = 4,
|
144
|
+
// ABORTED = 5
|
145
|
+
//
|
146
|
+
if ($scope.autostart) {
|
147
|
+
var shouldStart = true,
|
148
|
+
state, i, length, started = 0;
|
149
|
+
|
150
|
+
for (i = 0, length = $scope.uploads.length; i < length; i += 1) {
|
151
|
+
state = $scope.uploads[i].state;
|
152
|
+
|
153
|
+
//
|
154
|
+
// Count started uploads (that don't have errors if we are ignoring errors)
|
155
|
+
// Up until we've reached our parallel limit, then stop
|
156
|
+
//
|
157
|
+
if (state > 0 && state < 4 && !($scope.uploads[i].error && $scope.ignore_errors)) {
|
158
|
+
started += 1;
|
159
|
+
if(started >= $scope.parallelism) {
|
160
|
+
shouldStart = false;
|
161
|
+
break;
|
162
|
+
}
|
163
|
+
}
|
164
|
+
}
|
165
|
+
|
166
|
+
if (shouldStart) {
|
167
|
+
started = $scope.parallelism - started; // How many can we start
|
168
|
+
|
169
|
+
for (i = 0; i < length; i += 1) {
|
170
|
+
if ($scope.uploads[i].state == 0) {
|
171
|
+
$scope.uploads[i].start();
|
172
|
+
|
173
|
+
started -= 1;
|
174
|
+
if(started <= 0) // Break if we can't start anymore
|
175
|
+
break;
|
176
|
+
}
|
177
|
+
}
|
178
|
+
}
|
179
|
+
}
|
180
|
+
};
|
181
|
+
|
182
|
+
}]);
|
183
|
+
|
184
|
+
})(angular);
|
@@ -1,80 +1,69 @@
|
|
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
|
-
* *
|
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
|
-
delegate: self.delegate,
|
71
|
-
drop_targets: self.drop_targets,
|
72
|
-
hover_class: self.hover_class,
|
73
|
-
supress_notifications: self.supress_notifications
|
74
|
-
};
|
75
|
-
};
|
76
|
-
});
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
}));
|
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/AUTO.$provide
|
13
|
+
* * http://jsfiddle.net/pkozlowski_opensource/PxdSP/14/
|
14
|
+
*
|
15
|
+
**/
|
16
|
+
|
17
|
+
|
18
|
+
(function (angular, undefined) {
|
19
|
+
'use strict';
|
20
|
+
|
21
|
+
|
22
|
+
//
|
23
|
+
// Create a provider for defining the configuration
|
24
|
+
//
|
25
|
+
angular.module('Condo').
|
26
|
+
provider('Condo.Config', function() {
|
27
|
+
|
28
|
+
//
|
29
|
+
// Controller options
|
30
|
+
//
|
31
|
+
this.endpoint = '/uploads'; // Default endpoint path
|
32
|
+
this.autostart = true; // Start uploading as soon as the file is added?
|
33
|
+
this.ignore_errors = true; // Continue to autostart after an error?
|
34
|
+
this.parallelism = 1; // number of autostarted uploads at once
|
35
|
+
this.size_limit = undefined; // defaults to unlimited
|
36
|
+
this.file_checker = function(file) { // client side filtering of files
|
37
|
+
return true;
|
38
|
+
};
|
39
|
+
|
40
|
+
//
|
41
|
+
// Directive options (specifically for the condo default interface)
|
42
|
+
//
|
43
|
+
this.delegate = undefined; // defaults to the condo interface container
|
44
|
+
this.drop_targets = undefined; // defaults to the condo interface container
|
45
|
+
this.hover_class = 'drag-hover'; // for styling the interface
|
46
|
+
this.supress_notifications = false; // this prevents js alerts about warnings and errors if you are observing these yourself (Condo.Broadcast)
|
47
|
+
|
48
|
+
|
49
|
+
|
50
|
+
this.$get = function() {
|
51
|
+
var self = this;
|
52
|
+
|
53
|
+
return {
|
54
|
+
endpoint: self.endpoint,
|
55
|
+
autostart: self.autostart,
|
56
|
+
ignore_errors: self.ignore_errors,
|
57
|
+
parallelism: self.parallelism,
|
58
|
+
file_checker: self.file_checker,
|
59
|
+
size_limit: self.size_limit,
|
60
|
+
|
61
|
+
delegate: self.delegate,
|
62
|
+
drop_targets: self.drop_targets,
|
63
|
+
hover_class: self.hover_class,
|
64
|
+
supress_notifications: self.supress_notifications
|
65
|
+
};
|
66
|
+
};
|
67
|
+
});
|
68
|
+
|
69
|
+
})(angular);
|
@@ -1,255 +1,338 @@
|
|
1
|
-
/**
|
2
|
-
* CoTag Condo
|
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
|
-
|
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
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
//
|
97
|
-
//
|
98
|
-
//
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
//
|
104
|
-
//
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
//
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
//
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
//
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
}
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
1
|
+
/**
|
2
|
+
* CoTag Condo Google 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, base64, undefined) {
|
18
|
+
'use strict';
|
19
|
+
|
20
|
+
angular.module('Condo').
|
21
|
+
|
22
|
+
factory('Condo.Google', ['$q', 'Condo.Md5', function($q, md5) {
|
23
|
+
var PENDING = 0,
|
24
|
+
STARTED = 1,
|
25
|
+
PAUSED = 2,
|
26
|
+
UPLOADING = 3,
|
27
|
+
COMPLETED = 4,
|
28
|
+
ABORTED = 5,
|
29
|
+
|
30
|
+
|
31
|
+
|
32
|
+
hexToBin = function(input) {
|
33
|
+
var result = "", i, length;
|
34
|
+
|
35
|
+
if ((input.length % 2) > 0) {
|
36
|
+
input = '0' + input;
|
37
|
+
}
|
38
|
+
|
39
|
+
for (i = 0, length = input.length; i < length; i += 2) {
|
40
|
+
result += String.fromCharCode(parseInt(input.slice(i, i + 2), 16));
|
41
|
+
}
|
42
|
+
|
43
|
+
return result;
|
44
|
+
},
|
45
|
+
|
46
|
+
|
47
|
+
GoogleCloudStorage = function (api, file) {
|
48
|
+
var self = this,
|
49
|
+
strategy = null,
|
50
|
+
pausing = false,
|
51
|
+
defaultError = function(reason) {
|
52
|
+
self.error = !pausing;
|
53
|
+
pausing = false;
|
54
|
+
self.pause(reason);
|
55
|
+
},
|
56
|
+
|
57
|
+
restart = function() {
|
58
|
+
strategy = null;
|
59
|
+
},
|
60
|
+
|
61
|
+
|
62
|
+
completeUpload = function() {
|
63
|
+
api.update().then(function() {
|
64
|
+
self.progress = self.size; // Update to 100%
|
65
|
+
self.state = COMPLETED;
|
66
|
+
}, defaultError);
|
67
|
+
},
|
68
|
+
|
69
|
+
|
70
|
+
//
|
71
|
+
// We need to sign our uploads so Google can confirm they are valid for us
|
72
|
+
//
|
73
|
+
build_request = function(chunk) {
|
74
|
+
return md5.hash(chunk).then(function(val) {
|
75
|
+
return {
|
76
|
+
data: chunk,
|
77
|
+
data_id: base64.encode(hexToBin(val))
|
78
|
+
};
|
79
|
+
}, function(reason){
|
80
|
+
return $q.reject(reason);
|
81
|
+
});
|
82
|
+
},
|
83
|
+
|
84
|
+
//
|
85
|
+
// Direct file upload strategy
|
86
|
+
//
|
87
|
+
GoogleDirect = function(data) {
|
88
|
+
//
|
89
|
+
// resume
|
90
|
+
// abort
|
91
|
+
// pause
|
92
|
+
//
|
93
|
+
var $this = this,
|
94
|
+
finalising = false;
|
95
|
+
|
96
|
+
//
|
97
|
+
// Update the parent
|
98
|
+
//
|
99
|
+
self.state = UPLOADING;
|
100
|
+
|
101
|
+
|
102
|
+
//
|
103
|
+
// This will only be called when the upload has finished and we need to inform the application
|
104
|
+
//
|
105
|
+
this.resume = function() {
|
106
|
+
self.state = UPLOADING;
|
107
|
+
completeUpload();
|
108
|
+
};
|
109
|
+
|
110
|
+
this.pause = function() {
|
111
|
+
api.abort();
|
112
|
+
|
113
|
+
if(!finalising) {
|
114
|
+
restart(); // Should occur before events triggered
|
115
|
+
self.progress = 0;
|
116
|
+
}
|
117
|
+
};
|
118
|
+
|
119
|
+
|
120
|
+
//
|
121
|
+
// AJAX for upload goes here
|
122
|
+
//
|
123
|
+
data['data'] = file;
|
124
|
+
api.process_request(data, function(progress) {
|
125
|
+
self.progress = progress;
|
126
|
+
}).then(function() {
|
127
|
+
finalising = true;
|
128
|
+
$this.resume(); // Resume informs the application that the upload is complete
|
129
|
+
}, function(reason) {
|
130
|
+
self.progress = 0;
|
131
|
+
defaultError(reason);
|
132
|
+
});
|
133
|
+
}, // END DIRECT
|
134
|
+
|
135
|
+
|
136
|
+
//
|
137
|
+
// Resumable upload strategy--------------------------------------------------
|
138
|
+
//
|
139
|
+
GoogleResumable = function (data, file_hash, finalising) {
|
140
|
+
var getQueryParams = function(qs) {
|
141
|
+
qs = qs.split("+").join(" ");
|
142
|
+
|
143
|
+
var params = {}, tokens,
|
144
|
+
re = /[?&]?([^=]+)=([^&]*)/g;
|
145
|
+
|
146
|
+
while (tokens = re.exec(qs)) { // NOTE:: we expect the assignment here
|
147
|
+
params[decodeURIComponent(tokens[1])] = decodeURIComponent(tokens[2]);
|
148
|
+
}
|
149
|
+
|
150
|
+
return params;
|
151
|
+
},
|
152
|
+
|
153
|
+
|
154
|
+
resume_upload = function(request, file_hash, range_start) {
|
155
|
+
request.data = file_hash.data;
|
156
|
+
api.process_request(request, function(progress) {
|
157
|
+
self.progress = range_start + progress;
|
158
|
+
}).then(function(result) {
|
159
|
+
finalising = true;
|
160
|
+
completeUpload();
|
161
|
+
}, function(reason) {
|
162
|
+
defaultError(reason);
|
163
|
+
});
|
164
|
+
};
|
165
|
+
|
166
|
+
|
167
|
+
|
168
|
+
|
169
|
+
self.state = UPLOADING;
|
170
|
+
|
171
|
+
this.resume = function() {
|
172
|
+
self.state = UPLOADING;
|
173
|
+
if (finalising == true) {
|
174
|
+
completeUpload();
|
175
|
+
} else {
|
176
|
+
api.create({file_id: file_hash.data_id}).
|
177
|
+
then(function(data) {
|
178
|
+
if(data.type == 'direct_upload') {
|
179
|
+
strategy = new GoogleDirect(data);
|
180
|
+
} else {
|
181
|
+
strategy = new GoogleResumable(data, file_hash);
|
182
|
+
}
|
183
|
+
}, defaultError);
|
184
|
+
}
|
185
|
+
};
|
186
|
+
|
187
|
+
this.pause = function() {
|
188
|
+
api.abort();
|
189
|
+
};
|
190
|
+
|
191
|
+
|
192
|
+
|
193
|
+
api.process_request(data).then(function(response) {
|
194
|
+
//
|
195
|
+
// Check if we were grabbing a parts list or creating an upload
|
196
|
+
//
|
197
|
+
if(data.type == 'status') { // the request was for the byte we are up to
|
198
|
+
// Get the byte we were up to here and update the application
|
199
|
+
var range_start = parseInt(response[1].getResponseHeader('Range').split('-')[1], 10) + 1;
|
200
|
+
|
201
|
+
build_request(file.slice(range_start)).then(function(result) {
|
202
|
+
if (self.state != UPLOADING) {
|
203
|
+
return; // upload was paused or aborted as we were reading the file
|
204
|
+
}
|
205
|
+
|
206
|
+
api.edit(range_start, result.data_id).
|
207
|
+
then(function(data) {
|
208
|
+
resume_upload(data, result, range_start);
|
209
|
+
}, defaultError);
|
210
|
+
|
211
|
+
}, defaultError); // END BUILD_REQUEST
|
212
|
+
} else {
|
213
|
+
//
|
214
|
+
// We've created the upload - we need to update our application with the upload id.
|
215
|
+
// This will also return the request for uploading the file which we've already prepared
|
216
|
+
//
|
217
|
+
api.update({
|
218
|
+
resumable_id: getQueryParams(response[1].getResponseHeader('Location').split('?')[1]).upload_id, // grab the upload_id from the Location header
|
219
|
+
file_id: file_hash.data_id,
|
220
|
+
part: 0 // part for google === the byte we are up to
|
221
|
+
}).then(function(data) {
|
222
|
+
resume_upload(data, file_hash, 0); // As this is the first upload attempt we want to upload from byte 0
|
223
|
+
}, function(reason) {
|
224
|
+
defaultError(reason);
|
225
|
+
restart(); // Easier to start from the beginning
|
226
|
+
});
|
227
|
+
}
|
228
|
+
}, function(reason) {
|
229
|
+
defaultError(reason);
|
230
|
+
restart(); // We need to get a new request signature
|
231
|
+
});
|
232
|
+
}; // END RESUMABLE
|
233
|
+
|
234
|
+
|
235
|
+
//
|
236
|
+
// Variables required for all drivers
|
237
|
+
//
|
238
|
+
this.state = PENDING;
|
239
|
+
this.progress = 0;
|
240
|
+
this.message = 'pending';
|
241
|
+
this.name = file.name;
|
242
|
+
this.size = file.size;
|
243
|
+
this.error = false;
|
244
|
+
|
245
|
+
|
246
|
+
//
|
247
|
+
// Support file slicing
|
248
|
+
//
|
249
|
+
if (typeof(file.slice) != 'function') {
|
250
|
+
file.slice = file.webkitSlice || file.mozSlice;
|
251
|
+
}
|
252
|
+
|
253
|
+
|
254
|
+
this.start = function(){
|
255
|
+
if(strategy == null) { // We need to create the upload
|
256
|
+
|
257
|
+
this.error = false;
|
258
|
+
pausing = false;
|
259
|
+
this.message = null;
|
260
|
+
this.state = STARTED;
|
261
|
+
strategy = {}; // This function shouldn't be called twice so we need a state
|
262
|
+
|
263
|
+
build_request(file).then(function(result) {
|
264
|
+
if (self.state != STARTED) { return; } // upload was paused or aborted as we were reading the file
|
265
|
+
|
266
|
+
api.create({file_id: result.data_id}).
|
267
|
+
then(function(data) {
|
268
|
+
if(data.type == 'direct_upload') {
|
269
|
+
strategy = new GoogleDirect(data);
|
270
|
+
} else {
|
271
|
+
strategy = new GoogleResumable(data, result);
|
272
|
+
}
|
273
|
+
}, defaultError);
|
274
|
+
|
275
|
+
}, defaultError); // END BUILD_REQUEST
|
276
|
+
|
277
|
+
|
278
|
+
} else if (this.state == PAUSED) { // We need to resume the upload if it is paused
|
279
|
+
this.error = false;
|
280
|
+
pausing = false;
|
281
|
+
this.message = null;
|
282
|
+
strategy.resume();
|
283
|
+
}
|
284
|
+
};
|
285
|
+
|
286
|
+
this.pause = function(reason) {
|
287
|
+
if(strategy != null && this.state == UPLOADING) { // Check if the upload is uploading
|
288
|
+
this.state = PAUSED;
|
289
|
+
pausing = true;
|
290
|
+
strategy.pause();
|
291
|
+
} else if (this.state <= STARTED) {
|
292
|
+
this.state = PAUSED;
|
293
|
+
restart();
|
294
|
+
}
|
295
|
+
if(this.state == PAUSED) { this.message = reason; }
|
296
|
+
};
|
297
|
+
|
298
|
+
this.abort = function(reason) {
|
299
|
+
if(strategy != null && this.state < COMPLETED) { // Check the upload has not finished
|
300
|
+
var old_state = this.state;
|
301
|
+
|
302
|
+
this.state = ABORTED;
|
303
|
+
api.abort();
|
304
|
+
|
305
|
+
|
306
|
+
//
|
307
|
+
// As we may not have successfully deleted the upload
|
308
|
+
// or we aborted before we received a response from create
|
309
|
+
//
|
310
|
+
restart(); // nullifies strategy
|
311
|
+
|
312
|
+
|
313
|
+
//
|
314
|
+
// if we have an upload_id then we should destroy the upload
|
315
|
+
// we won't worry if this fails as it should be automatically cleaned up by the back end
|
316
|
+
//
|
317
|
+
if(old_state > STARTED) {
|
318
|
+
api.destroy();
|
319
|
+
}
|
320
|
+
|
321
|
+
this.message = reason;
|
322
|
+
}
|
323
|
+
};
|
324
|
+
}; // END GOOGLE
|
325
|
+
|
326
|
+
|
327
|
+
return {
|
328
|
+
new_upload: function(api, file) {
|
329
|
+
return new GoogleCloudStorage(api, file);
|
330
|
+
}
|
331
|
+
};
|
332
|
+
}]).
|
333
|
+
|
334
|
+
config(['Condo.ApiProvider', function (ApiProvider) {
|
335
|
+
ApiProvider.register('GoogleCloudStorage', 'Condo.Google');
|
336
|
+
}]);
|
337
|
+
|
338
|
+
})(angular, window.base64);
|