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
@@ -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);
|