resumablejs-rails 0.1.0
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.md +31 -0
- data/lib/resumablejs-rails.rb +8 -0
- data/lib/resumablejs-rails/version.rb +5 -0
- data/vendor/assets/javascripts/resumable.js +717 -0
- metadata +68 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: cd2e7e4367f9bed114d813f61e0e91c579ad295a
|
4
|
+
data.tar.gz: 57680f77bb5313c964be2bbd2b485d67735c2f3f
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 99675ea0f713e987a81d7b2c62e95593dc17ec022a9aa74f251dcad10d08b7b8b856e509c4f0ad581a719d16a2cc65df62b97435e56adad0c1c1288b07a6d690
|
7
|
+
data.tar.gz: 8794a1c2e8d5df2f2331e05f40884678472ef747f426411d98f7098e651f5a4693b15b97a07766bb34238853c85b30c75739d4150a0c4373e8bd45e3f4da6543
|
data/README.md
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
# resumablejs-rails
|
2
|
+
|
3
|
+
resumable.js for the Rails Asset Pipeline
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this to your application's Gemfile:
|
8
|
+
|
9
|
+
gem 'resumablejs-rails'
|
10
|
+
|
11
|
+
And then execute:
|
12
|
+
|
13
|
+
$ bundle
|
14
|
+
|
15
|
+
Or install it yourself as:
|
16
|
+
|
17
|
+
$ gem install resumablejs-rails
|
18
|
+
|
19
|
+
## Usage
|
20
|
+
|
21
|
+
Add the following directive to your Javascript manifest file (application.js):
|
22
|
+
|
23
|
+
//= require resumable
|
24
|
+
|
25
|
+
## Contributing
|
26
|
+
|
27
|
+
1. Fork it
|
28
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
29
|
+
3. Commit your changes (`git commit -am 'Added some feature'`)
|
30
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
31
|
+
5. Create new Pull Request
|
@@ -0,0 +1,717 @@
|
|
1
|
+
/*
|
2
|
+
* MIT Licensed
|
3
|
+
* http://www.23developer.com/opensource
|
4
|
+
* http://github.com/23/resumable.js
|
5
|
+
* Steffen Tiedemann Christensen, steffen@23company.com
|
6
|
+
*/
|
7
|
+
|
8
|
+
var Resumable = function(opts){
|
9
|
+
if ( !(this instanceof Resumable ) ) {
|
10
|
+
return new Resumable( opts );
|
11
|
+
}
|
12
|
+
// SUPPORTED BY BROWSER?
|
13
|
+
// Check if these features are support by the browser:
|
14
|
+
// - File object type
|
15
|
+
// - Blob object type
|
16
|
+
// - FileList object type
|
17
|
+
// - slicing files
|
18
|
+
this.support = (
|
19
|
+
(typeof(File)!=='undefined')
|
20
|
+
&&
|
21
|
+
(typeof(Blob)!=='undefined')
|
22
|
+
&&
|
23
|
+
(typeof(FileList)!=='undefined')
|
24
|
+
&&
|
25
|
+
(!!Blob.prototype.webkitSlice||!!Blob.prototype.mozSlice||Blob.prototype.slice||false)
|
26
|
+
);
|
27
|
+
if(!this.support) return(false);
|
28
|
+
|
29
|
+
|
30
|
+
// PROPERTIES
|
31
|
+
var $ = this;
|
32
|
+
$.files = [];
|
33
|
+
$.defaults = {
|
34
|
+
chunkSize:1*1024*1024,
|
35
|
+
forceChunkSize:false,
|
36
|
+
simultaneousUploads:3,
|
37
|
+
fileParameterName:'file',
|
38
|
+
throttleProgressCallbacks:0.5,
|
39
|
+
query:{},
|
40
|
+
headers:{},
|
41
|
+
preprocess:null,
|
42
|
+
method:'multipart',
|
43
|
+
prioritizeFirstAndLastChunk:false,
|
44
|
+
target:'/',
|
45
|
+
testChunks:true,
|
46
|
+
generateUniqueIdentifier:null,
|
47
|
+
maxChunkRetries:undefined,
|
48
|
+
chunkRetryInterval:undefined,
|
49
|
+
permanentErrors:[415, 500, 501],
|
50
|
+
maxFiles:undefined,
|
51
|
+
maxFilesErrorCallback:function (files, errorCount) {
|
52
|
+
var maxFiles = $.getOpt('maxFiles');
|
53
|
+
alert('Please upload ' + maxFiles + ' file' + (maxFiles === 1 ? '' : 's') + ' at a time.');
|
54
|
+
},
|
55
|
+
minFileSize:undefined,
|
56
|
+
minFileSizeErrorCallback:function(file, errorCount) {
|
57
|
+
alert(file.fileName +' is too small, please upload files larger than ' + $h.formatSize($.getOpt('minFileSize')) + '.');
|
58
|
+
},
|
59
|
+
maxFileSize:undefined,
|
60
|
+
maxFileSizeErrorCallback:function(file, errorCount) {
|
61
|
+
alert(file.fileName +' is too large, please upload files less than ' + $h.formatSize($.getOpt('maxFileSize')) + '.');
|
62
|
+
},
|
63
|
+
fileType: [],
|
64
|
+
fileTypeErrorCallback: function(file, errorCount) {
|
65
|
+
alert(file.fileName +' has type not allowed, please upload files of type ' + $.getOpt('fileType') + '.');
|
66
|
+
}
|
67
|
+
};
|
68
|
+
$.opts = opts||{};
|
69
|
+
$.getOpt = function(o) {
|
70
|
+
var $this = this;
|
71
|
+
// Get multiple option if passed an array
|
72
|
+
if(o instanceof Array) {
|
73
|
+
var options = {};
|
74
|
+
$h.each(o, function(option){
|
75
|
+
options[option] = $this.getOpt(option);
|
76
|
+
});
|
77
|
+
return options;
|
78
|
+
}
|
79
|
+
// Otherwise, just return a simple option
|
80
|
+
if ($this instanceof ResumableChunk) {
|
81
|
+
if (typeof $this.opts[o] !== 'undefined') { return $this.opts[o]; }
|
82
|
+
else { $this = $this.fileObj; }
|
83
|
+
}
|
84
|
+
if ($this instanceof ResumableFile) {
|
85
|
+
if (typeof $this.opts[o] !== 'undefined') { return $this.opts[o]; }
|
86
|
+
else { $this = $this.resumableObj; }
|
87
|
+
}
|
88
|
+
if ($this instanceof Resumable) {
|
89
|
+
if (typeof $this.opts[o] !== 'undefined') { return $this.opts[o]; }
|
90
|
+
else { return $this.defaults[o]; }
|
91
|
+
}
|
92
|
+
};
|
93
|
+
|
94
|
+
// EVENTS
|
95
|
+
// catchAll(event, ...)
|
96
|
+
// fileSuccess(file), fileProgress(file), fileAdded(file, event), fileRetry(file), fileError(file, message),
|
97
|
+
// complete(), progress(), error(message, file), pause()
|
98
|
+
$.events = [];
|
99
|
+
$.on = function(event,callback){
|
100
|
+
$.events.push(event.toLowerCase(), callback);
|
101
|
+
};
|
102
|
+
$.fire = function(){
|
103
|
+
// `arguments` is an object, not array, in FF, so:
|
104
|
+
var args = [];
|
105
|
+
for (var i=0; i<arguments.length; i++) args.push(arguments[i]);
|
106
|
+
// Find event listeners, and support pseudo-event `catchAll`
|
107
|
+
var event = args[0].toLowerCase();
|
108
|
+
for (var i=0; i<=$.events.length; i+=2) {
|
109
|
+
if($.events[i]==event) $.events[i+1].apply($,args.slice(1));
|
110
|
+
if($.events[i]=='catchall') $.events[i+1].apply(null,args);
|
111
|
+
}
|
112
|
+
if(event=='fileerror') $.fire('error', args[2], args[1]);
|
113
|
+
if(event=='fileprogress') $.fire('progress');
|
114
|
+
};
|
115
|
+
|
116
|
+
|
117
|
+
// INTERNAL HELPER METHODS (handy, but ultimately not part of uploading)
|
118
|
+
$h = {
|
119
|
+
stopEvent: function(e){
|
120
|
+
e.stopPropagation();
|
121
|
+
e.preventDefault();
|
122
|
+
},
|
123
|
+
each: function(o,callback){
|
124
|
+
if(typeof(o.length)!=='undefined') {
|
125
|
+
for (var i=0; i<o.length; i++) {
|
126
|
+
// Array or FileList
|
127
|
+
if(callback(o[i])===false) return;
|
128
|
+
}
|
129
|
+
} else {
|
130
|
+
for (i in o) {
|
131
|
+
// Object
|
132
|
+
if(callback(i,o[i])===false) return;
|
133
|
+
}
|
134
|
+
}
|
135
|
+
},
|
136
|
+
generateUniqueIdentifier:function(file){
|
137
|
+
var custom = $.getOpt('generateUniqueIdentifier');
|
138
|
+
if(typeof custom === 'function') {
|
139
|
+
return custom(file);
|
140
|
+
}
|
141
|
+
var relativePath = file.webkitRelativePath||file.fileName||file.name; // Some confusion in different versions of Firefox
|
142
|
+
var size = file.size;
|
143
|
+
return(size + '-' + relativePath.replace(/[^0-9a-zA-Z_-]/img, ''));
|
144
|
+
},
|
145
|
+
contains:function(array,test) {
|
146
|
+
var result = false;
|
147
|
+
|
148
|
+
$h.each(array, function(value) {
|
149
|
+
if (value == test) {
|
150
|
+
result = true;
|
151
|
+
return false;
|
152
|
+
}
|
153
|
+
return true;
|
154
|
+
});
|
155
|
+
|
156
|
+
return result;
|
157
|
+
},
|
158
|
+
formatSize:function(size){
|
159
|
+
if(size<1024) {
|
160
|
+
return size + ' bytes';
|
161
|
+
} else if(size<1024*1024) {
|
162
|
+
return (size/1024.0).toFixed(0) + ' KB';
|
163
|
+
} else if(size<1024*1024*1024) {
|
164
|
+
return (size/1024.0/1024.0).toFixed(1) + ' MB';
|
165
|
+
} else {
|
166
|
+
return (size/1024.0/1024.0/1024.0).toFixed(1) + ' GB';
|
167
|
+
}
|
168
|
+
}
|
169
|
+
};
|
170
|
+
|
171
|
+
var onDrop = function(event){
|
172
|
+
$h.stopEvent(event);
|
173
|
+
appendFilesFromFileList(event.dataTransfer.files, event);
|
174
|
+
};
|
175
|
+
var onDragOver = function(e) {
|
176
|
+
e.preventDefault();
|
177
|
+
};
|
178
|
+
|
179
|
+
// INTERNAL METHODS (both handy and responsible for the heavy load)
|
180
|
+
var appendFilesFromFileList = function(fileList, event){
|
181
|
+
// check for uploading too many files
|
182
|
+
var errorCount = 0;
|
183
|
+
var o = $.getOpt(['maxFiles', 'minFileSize', 'maxFileSize', 'maxFilesErrorCallback', 'minFileSizeErrorCallback', 'maxFileSizeErrorCallback', 'fileType', 'fileTypeErrorCallback']);
|
184
|
+
if (typeof(o.maxFiles)!=='undefined' && o.maxFiles<(fileList.length+$.files.length)) {
|
185
|
+
o.maxFilesErrorCallback(fileList, errorCount++);
|
186
|
+
return false;
|
187
|
+
}
|
188
|
+
var files = [];
|
189
|
+
$h.each(fileList, function(file){
|
190
|
+
file.name = file.fileName = file.fileName||file.name; // consistency across browsers for the error message
|
191
|
+
|
192
|
+
if (o.fileType.length > 0 && !$h.contains(o.fileType, file.type.split('/')[1])) {
|
193
|
+
o.fileTypeErrorCallback(file, errorCount++);
|
194
|
+
return false;
|
195
|
+
}
|
196
|
+
|
197
|
+
if (typeof(o.minFileSize)!=='undefined' && file.size<o.minFileSize) {
|
198
|
+
o.minFileSizeErrorCallback(file, errorCount++);
|
199
|
+
return false;
|
200
|
+
}
|
201
|
+
if (typeof(o.maxFileSize)!=='undefined' && file.size>o.maxFileSize) {
|
202
|
+
o.maxFileSizeErrorCallback(file, errorCount++);
|
203
|
+
return false;
|
204
|
+
}
|
205
|
+
|
206
|
+
// directories have size == 0
|
207
|
+
if (file.size > 0 && !$.getFromUniqueIdentifier($h.generateUniqueIdentifier(file))) {
|
208
|
+
var f = new ResumableFile($, file);
|
209
|
+
$.files.push(f);
|
210
|
+
files.push(f);
|
211
|
+
$.fire('fileAdded', f, event);
|
212
|
+
}
|
213
|
+
});
|
214
|
+
$.fire('filesAdded', files);
|
215
|
+
};
|
216
|
+
|
217
|
+
// INTERNAL OBJECT TYPES
|
218
|
+
function ResumableFile(resumableObj, file){
|
219
|
+
var $ = this;
|
220
|
+
$.opts = {};
|
221
|
+
$.getOpt = resumableObj.getOpt;
|
222
|
+
$._prevProgress = 0;
|
223
|
+
$.resumableObj = resumableObj;
|
224
|
+
$.file = file;
|
225
|
+
$.fileName = file.fileName||file.name; // Some confusion in different versions of Firefox
|
226
|
+
$.size = file.size;
|
227
|
+
$.relativePath = file.webkitRelativePath || $.fileName;
|
228
|
+
$.uniqueIdentifier = $h.generateUniqueIdentifier(file);
|
229
|
+
var _error = false;
|
230
|
+
|
231
|
+
// Callback when something happens within the chunk
|
232
|
+
var chunkEvent = function(event, message){
|
233
|
+
// event can be 'progress', 'success', 'error' or 'retry'
|
234
|
+
switch(event){
|
235
|
+
case 'progress':
|
236
|
+
$.resumableObj.fire('fileProgress', $);
|
237
|
+
break;
|
238
|
+
case 'error':
|
239
|
+
$.abort();
|
240
|
+
_error = true;
|
241
|
+
$.chunks = [];
|
242
|
+
$.resumableObj.fire('fileError', $, message);
|
243
|
+
break;
|
244
|
+
case 'success':
|
245
|
+
if(_error) return;
|
246
|
+
$.resumableObj.fire('fileProgress', $); // it's at least progress
|
247
|
+
if($.progress()==1) {
|
248
|
+
$.resumableObj.fire('fileSuccess', $, message);
|
249
|
+
}
|
250
|
+
break;
|
251
|
+
case 'retry':
|
252
|
+
$.resumableObj.fire('fileRetry', $);
|
253
|
+
break;
|
254
|
+
}
|
255
|
+
}
|
256
|
+
|
257
|
+
// Main code to set up a file object with chunks,
|
258
|
+
// packaged to be able to handle retries if needed.
|
259
|
+
$.chunks = [];
|
260
|
+
$.abort = function(){
|
261
|
+
// Stop current uploads
|
262
|
+
$h.each($.chunks, function(c){
|
263
|
+
if(c.status()=='uploading') c.abort();
|
264
|
+
});
|
265
|
+
$.resumableObj.fire('fileProgress', $);
|
266
|
+
}
|
267
|
+
$.cancel = function(){
|
268
|
+
// Reset this file to be void
|
269
|
+
var _chunks = $.chunks;
|
270
|
+
$.chunks = [];
|
271
|
+
// Stop current uploads
|
272
|
+
$h.each(_chunks, function(c){
|
273
|
+
if(c.status()=='uploading') {
|
274
|
+
c.abort();
|
275
|
+
$.resumableObj.uploadNextChunk();
|
276
|
+
}
|
277
|
+
});
|
278
|
+
$.resumableObj.removeFile($);
|
279
|
+
$.resumableObj.fire('fileProgress', $);
|
280
|
+
},
|
281
|
+
$.retry = function(){
|
282
|
+
$.bootstrap();
|
283
|
+
$.resumableObj.upload();
|
284
|
+
}
|
285
|
+
$.bootstrap = function(){
|
286
|
+
$.abort();
|
287
|
+
_error = false;
|
288
|
+
// Rebuild stack of chunks from file
|
289
|
+
$.chunks = [];
|
290
|
+
$._prevProgress = 0;
|
291
|
+
var round = $.getOpt('forceChunkSize') ? Math.ceil : Math.floor;
|
292
|
+
for (var offset=0; offset<Math.max(round($.file.size/$.getOpt('chunkSize')),1); offset++) {
|
293
|
+
$.chunks.push(new ResumableChunk($.resumableObj, $, offset, chunkEvent));
|
294
|
+
}
|
295
|
+
}
|
296
|
+
$.progress = function(){
|
297
|
+
if(_error) return(1);
|
298
|
+
// Sum up progress across everything
|
299
|
+
var ret = 0;
|
300
|
+
var error = false;
|
301
|
+
$h.each($.chunks, function(c){
|
302
|
+
if(c.status()=='error') error = true;
|
303
|
+
ret += c.progress(true); // get chunk progress relative to entire file
|
304
|
+
});
|
305
|
+
ret = (error ? 1 : (ret>0.999 ? 1 : ret))
|
306
|
+
ret = Math.max($._prevProgress, ret); // We don't want to lose percentages when an upload is paused
|
307
|
+
$._prevProgress = ret;
|
308
|
+
return(ret);
|
309
|
+
}
|
310
|
+
|
311
|
+
// Bootstrap and return
|
312
|
+
$.bootstrap();
|
313
|
+
return(this);
|
314
|
+
}
|
315
|
+
|
316
|
+
function ResumableChunk(resumableObj, fileObj, offset, callback){
|
317
|
+
var $ = this;
|
318
|
+
$.opts = {};
|
319
|
+
$.getOpt = resumableObj.getOpt;
|
320
|
+
$.resumableObj = resumableObj;
|
321
|
+
$.fileObj = fileObj;
|
322
|
+
$.fileObjSize = fileObj.size;
|
323
|
+
$.offset = offset;
|
324
|
+
$.callback = callback;
|
325
|
+
$.lastProgressCallback = (new Date);
|
326
|
+
$.tested = false;
|
327
|
+
$.retries = 0;
|
328
|
+
$.preprocessState = 0; // 0 = unprocessed, 1 = processing, 2 = finished
|
329
|
+
|
330
|
+
// Computed properties
|
331
|
+
var chunkSize = $.getOpt('chunkSize');
|
332
|
+
$.loaded = 0;
|
333
|
+
$.startByte = $.offset*chunkSize;
|
334
|
+
$.endByte = Math.min($.fileObjSize, ($.offset+1)*chunkSize);
|
335
|
+
if ($.fileObjSize-$.endByte < chunkSize && !$.getOpt('forceChunkSize')) {
|
336
|
+
// The last chunk will be bigger than the chunk size, but less than 2*chunkSize
|
337
|
+
$.endByte = $.fileObjSize;
|
338
|
+
}
|
339
|
+
$.xhr = null;
|
340
|
+
|
341
|
+
// test() makes a GET request without any data to see if the chunk has already been uploaded in a previous session
|
342
|
+
$.test = function(){
|
343
|
+
// Set up request and listen for event
|
344
|
+
$.xhr = new XMLHttpRequest();
|
345
|
+
|
346
|
+
var testHandler = function(e){
|
347
|
+
$.tested = true;
|
348
|
+
var status = $.status();
|
349
|
+
if(status=='success') {
|
350
|
+
$.callback(status, $.message());
|
351
|
+
$.resumableObj.uploadNextChunk();
|
352
|
+
} else {
|
353
|
+
$.send();
|
354
|
+
}
|
355
|
+
}
|
356
|
+
$.xhr.addEventListener("load", testHandler, false);
|
357
|
+
$.xhr.addEventListener("error", testHandler, false);
|
358
|
+
|
359
|
+
// Add data from the query options
|
360
|
+
var url = ""
|
361
|
+
var params = [];
|
362
|
+
var customQuery = $.getOpt('query');
|
363
|
+
if(typeof customQuery == "function") customQuery = customQuery($.fileObj, $);
|
364
|
+
$h.each(customQuery, function(k,v){
|
365
|
+
params.push([encodeURIComponent(k), encodeURIComponent(v)].join('='));
|
366
|
+
});
|
367
|
+
// Add extra data to identify chunk
|
368
|
+
params.push(['resumableChunkNumber', encodeURIComponent($.offset+1)].join('='));
|
369
|
+
params.push(['resumableChunkSize', encodeURIComponent($.getOpt('chunkSize'))].join('='));
|
370
|
+
params.push(['resumableCurrentChunkSize', encodeURIComponent($.endByte - $.startByte)].join('='));
|
371
|
+
params.push(['resumableTotalSize', encodeURIComponent($.fileObjSize)].join('='));
|
372
|
+
params.push(['resumableIdentifier', encodeURIComponent($.fileObj.uniqueIdentifier)].join('='));
|
373
|
+
params.push(['resumableFilename', encodeURIComponent($.fileObj.fileName)].join('='));
|
374
|
+
params.push(['resumableRelativePath', encodeURIComponent($.fileObj.relativePath)].join('='));
|
375
|
+
// Append the relevant chunk and send it
|
376
|
+
$.xhr.open("GET", $.getOpt('target') + '?' + params.join('&'));
|
377
|
+
// Add data from header options
|
378
|
+
$h.each($.getOpt('headers'), function(k,v) {
|
379
|
+
$.xhr.setRequestHeader(k, v);
|
380
|
+
});
|
381
|
+
$.xhr.send(null);
|
382
|
+
}
|
383
|
+
|
384
|
+
$.preprocessFinished = function(){
|
385
|
+
$.preprocessState = 2;
|
386
|
+
$.send();
|
387
|
+
}
|
388
|
+
|
389
|
+
// send() uploads the actual data in a POST call
|
390
|
+
$.send = function(){
|
391
|
+
var preprocess = $.getOpt('preprocess');
|
392
|
+
if(typeof preprocess === 'function') {
|
393
|
+
switch($.preprocessState) {
|
394
|
+
case 0: preprocess($); $.preprocessState = 1; return;
|
395
|
+
case 1: return;
|
396
|
+
case 2: break;
|
397
|
+
}
|
398
|
+
}
|
399
|
+
if($.getOpt('testChunks') && !$.tested) {
|
400
|
+
$.test();
|
401
|
+
return;
|
402
|
+
}
|
403
|
+
|
404
|
+
// Set up request and listen for event
|
405
|
+
$.xhr = new XMLHttpRequest();
|
406
|
+
|
407
|
+
// Progress
|
408
|
+
$.xhr.upload.addEventListener("progress", function(e){
|
409
|
+
if( (new Date) - $.lastProgressCallback > $.getOpt('throttleProgressCallbacks') * 1000 ) {
|
410
|
+
$.callback('progress');
|
411
|
+
$.lastProgressCallback = (new Date);
|
412
|
+
}
|
413
|
+
$.loaded=e.loaded||0;
|
414
|
+
}, false);
|
415
|
+
$.loaded = 0;
|
416
|
+
$.callback('progress');
|
417
|
+
|
418
|
+
// Done (either done, failed or retry)
|
419
|
+
var doneHandler = function(e){
|
420
|
+
var status = $.status();
|
421
|
+
if(status=='success'||status=='error') {
|
422
|
+
$.callback(status, $.message());
|
423
|
+
$.resumableObj.uploadNextChunk();
|
424
|
+
} else {
|
425
|
+
$.callback('retry', $.message());
|
426
|
+
$.abort();
|
427
|
+
$.retries++;
|
428
|
+
var retryInterval = $.getOpt('chunkRetryInterval');
|
429
|
+
if(retryInterval !== undefined) {
|
430
|
+
setTimeout($.send, retryInterval);
|
431
|
+
} else {
|
432
|
+
$.send();
|
433
|
+
}
|
434
|
+
}
|
435
|
+
};
|
436
|
+
$.xhr.addEventListener("load", doneHandler, false);
|
437
|
+
$.xhr.addEventListener("error", doneHandler, false);
|
438
|
+
|
439
|
+
// Set up the basic query data from Resumable
|
440
|
+
var query = {
|
441
|
+
resumableChunkNumber: $.offset+1,
|
442
|
+
resumableChunkSize: $.getOpt('chunkSize'),
|
443
|
+
resumableCurrentChunkSize: $.endByte - $.startByte,
|
444
|
+
resumableTotalSize: $.fileObjSize,
|
445
|
+
resumableIdentifier: $.fileObj.uniqueIdentifier,
|
446
|
+
resumableFilename: $.fileObj.fileName,
|
447
|
+
resumableRelativePath: $.fileObj.relativePath
|
448
|
+
}
|
449
|
+
// Mix in custom data
|
450
|
+
var customQuery = $.getOpt('query');
|
451
|
+
if(typeof customQuery == "function") customQuery = customQuery($.fileObj, $);
|
452
|
+
$h.each(customQuery, function(k,v){
|
453
|
+
query[k] = v;
|
454
|
+
});
|
455
|
+
|
456
|
+
// Add data from header options
|
457
|
+
$h.each($.getOpt('headers'), function(k,v) {
|
458
|
+
$.xhr.setRequestHeader(k, v);
|
459
|
+
});
|
460
|
+
|
461
|
+
var func = ($.fileObj.file.slice ? 'slice' : ($.fileObj.file.mozSlice ? 'mozSlice' : ($.fileObj.file.webkitSlice ? 'webkitSlice' : 'slice'))),
|
462
|
+
bytes = $.fileObj.file[func]($.startByte,$.endByte),
|
463
|
+
data = null,
|
464
|
+
target = $.getOpt('target');
|
465
|
+
|
466
|
+
if ($.getOpt('method') === 'octet') {
|
467
|
+
// Add data from the query options
|
468
|
+
data = bytes;
|
469
|
+
var params = [];
|
470
|
+
$h.each(query, function(k,v){
|
471
|
+
params.push([encodeURIComponent(k), encodeURIComponent(v)].join('='));
|
472
|
+
});
|
473
|
+
target += '?' + params.join('&');
|
474
|
+
} else {
|
475
|
+
// Add data from the query options
|
476
|
+
data = new FormData();
|
477
|
+
$h.each(query, function(k,v){
|
478
|
+
data.append(k,v);
|
479
|
+
});
|
480
|
+
data.append($.getOpt('fileParameterName'), bytes);
|
481
|
+
}
|
482
|
+
|
483
|
+
$.xhr.open('POST', target);
|
484
|
+
$.xhr.send(data);
|
485
|
+
}
|
486
|
+
$.abort = function(){
|
487
|
+
// Abort and reset
|
488
|
+
if($.xhr) $.xhr.abort();
|
489
|
+
$.xhr = null;
|
490
|
+
}
|
491
|
+
$.status = function(){
|
492
|
+
// Returns: 'pending', 'uploading', 'success', 'error'
|
493
|
+
if(!$.xhr) {
|
494
|
+
return('pending');
|
495
|
+
} else if($.xhr.readyState<4) {
|
496
|
+
// Status is really 'OPENED', 'HEADERS_RECEIVED' or 'LOADING' - meaning that stuff is happening
|
497
|
+
return('uploading');
|
498
|
+
} else {
|
499
|
+
if($.xhr.status==200) {
|
500
|
+
// HTTP 200, perfect
|
501
|
+
return('success');
|
502
|
+
} else if($h.contains($.getOpt('permanentErrors'), $.xhr.status) || $.retries >= $.getOpt('maxChunkRetries')) {
|
503
|
+
// HTTP 415/500/501, permanent error
|
504
|
+
return('error');
|
505
|
+
} else {
|
506
|
+
// this should never happen, but we'll reset and queue a retry
|
507
|
+
// a likely case for this would be 503 service unavailable
|
508
|
+
$.abort();
|
509
|
+
return('pending');
|
510
|
+
}
|
511
|
+
}
|
512
|
+
}
|
513
|
+
$.message = function(){
|
514
|
+
return($.xhr ? $.xhr.responseText : '');
|
515
|
+
}
|
516
|
+
$.progress = function(relative){
|
517
|
+
if(typeof(relative)==='undefined') relative = false;
|
518
|
+
var factor = (relative ? ($.endByte-$.startByte)/$.fileObjSize : 1);
|
519
|
+
var s = $.status();
|
520
|
+
switch(s){
|
521
|
+
case 'success':
|
522
|
+
case 'error':
|
523
|
+
return(1*factor);
|
524
|
+
case 'pending':
|
525
|
+
return(0*factor);
|
526
|
+
default:
|
527
|
+
return($.loaded/($.endByte-$.startByte)*factor);
|
528
|
+
}
|
529
|
+
}
|
530
|
+
return(this);
|
531
|
+
}
|
532
|
+
|
533
|
+
// QUEUE
|
534
|
+
$.uploadNextChunk = function(){
|
535
|
+
var found = false;
|
536
|
+
|
537
|
+
// In some cases (such as videos) it's really handy to upload the first
|
538
|
+
// and last chunk of a file quickly; this let's the server check the file's
|
539
|
+
// metadata and determine if there's even a point in continuing.
|
540
|
+
if ($.getOpt('prioritizeFirstAndLastChunk')) {
|
541
|
+
$h.each($.files, function(file){
|
542
|
+
if(file.chunks.length && file.chunks[0].status()=='pending' && file.chunks[0].preprocessState === 0) {
|
543
|
+
file.chunks[0].send();
|
544
|
+
found = true;
|
545
|
+
return(false);
|
546
|
+
}
|
547
|
+
if(file.chunks.length>1 && file.chunks[file.chunks.length-1].status()=='pending' && file.chunks[0].preprocessState === 0) {
|
548
|
+
file.chunks[file.chunks.length-1].send();
|
549
|
+
found = true;
|
550
|
+
return(false);
|
551
|
+
}
|
552
|
+
});
|
553
|
+
if(found) return(true);
|
554
|
+
}
|
555
|
+
|
556
|
+
// Now, simply look for the next, best thing to upload
|
557
|
+
$h.each($.files, function(file){
|
558
|
+
$h.each(file.chunks, function(chunk){
|
559
|
+
if(chunk.status()=='pending' && chunk.preprocessState === 0) {
|
560
|
+
chunk.send();
|
561
|
+
found = true;
|
562
|
+
return(false);
|
563
|
+
}
|
564
|
+
});
|
565
|
+
if(found) return(false);
|
566
|
+
});
|
567
|
+
if(found) return(true);
|
568
|
+
|
569
|
+
// The are no more outstanding chunks to upload, check is everything is done
|
570
|
+
$h.each($.files, function(file){
|
571
|
+
outstanding = false;
|
572
|
+
$h.each(file.chunks, function(chunk){
|
573
|
+
var status = chunk.status();
|
574
|
+
if(status=='pending' || status=='uploading' || chunk.preprocessState === 1) {
|
575
|
+
outstanding = true;
|
576
|
+
return(false);
|
577
|
+
}
|
578
|
+
});
|
579
|
+
if(outstanding) return(false);
|
580
|
+
});
|
581
|
+
if(!outstanding) {
|
582
|
+
// All chunks have been uploaded, complete
|
583
|
+
$.fire('complete');
|
584
|
+
}
|
585
|
+
return(false);
|
586
|
+
};
|
587
|
+
|
588
|
+
|
589
|
+
// PUBLIC METHODS FOR RESUMABLE.JS
|
590
|
+
$.assignBrowse = function(domNodes, isDirectory){
|
591
|
+
if(typeof(domNodes.length)=='undefined') domNodes = [domNodes];
|
592
|
+
|
593
|
+
// We will create an <input> and overlay it on the domNode
|
594
|
+
// (crappy, but since HTML5 doesn't have a cross-browser.browse() method we haven't a choice.
|
595
|
+
// FF4+ allows click() for this though: https://developer.mozilla.org/en/using_files_from_web_applications)
|
596
|
+
$h.each(domNodes, function(domNode) {
|
597
|
+
var input;
|
598
|
+
if(domNode.tagName==='INPUT' && domNode.type==='file'){
|
599
|
+
input = domNode;
|
600
|
+
} else {
|
601
|
+
input = document.createElement('input');
|
602
|
+
input.setAttribute('type', 'file');
|
603
|
+
// Place <input /> with the dom node an position the input to fill the entire space
|
604
|
+
domNode.style.display = 'inline-block';
|
605
|
+
domNode.style.position = 'relative';
|
606
|
+
input.style.position = 'absolute';
|
607
|
+
input.style.top = input.style.left = input.style.bottom = input.style.right = 0;
|
608
|
+
input.style.opacity = 0;
|
609
|
+
input.style.cursor = 'pointer';
|
610
|
+
domNode.appendChild(input);
|
611
|
+
}
|
612
|
+
var maxFiles = $.getOpt('maxFiles');
|
613
|
+
if (typeof(maxFiles)==='undefined'||maxFiles!=1){
|
614
|
+
input.setAttribute('multiple', 'multiple');
|
615
|
+
} else {
|
616
|
+
input.removeAttribute('multiple');
|
617
|
+
}
|
618
|
+
if(isDirectory){
|
619
|
+
input.setAttribute('webkitdirectory', 'webkitdirectory');
|
620
|
+
} else {
|
621
|
+
input.removeAttribute('webkitdirectory');
|
622
|
+
}
|
623
|
+
// When new files are added, simply append them to the overall list
|
624
|
+
input.addEventListener('change', function(e){
|
625
|
+
appendFilesFromFileList(e.target.files);
|
626
|
+
e.target.value = '';
|
627
|
+
}, false);
|
628
|
+
});
|
629
|
+
};
|
630
|
+
$.assignDrop = function(domNodes){
|
631
|
+
if(typeof(domNodes.length)=='undefined') domNodes = [domNodes];
|
632
|
+
|
633
|
+
$h.each(domNodes, function(domNode) {
|
634
|
+
domNode.addEventListener('dragover', onDragOver, false);
|
635
|
+
domNode.addEventListener('drop', onDrop, false);
|
636
|
+
});
|
637
|
+
};
|
638
|
+
$.unAssignDrop = function(domNodes) {
|
639
|
+
if (typeof(domNodes.length) == 'undefined') domNodes = [domNodes];
|
640
|
+
|
641
|
+
$h.each(domNodes, function(domNode) {
|
642
|
+
domNode.removeEventListener('dragover', onDragOver);
|
643
|
+
domNode.removeEventListener('drop', onDrop);
|
644
|
+
});
|
645
|
+
};
|
646
|
+
$.isUploading = function(){
|
647
|
+
var uploading = false;
|
648
|
+
$h.each($.files, function(file){
|
649
|
+
$h.each(file.chunks, function(chunk){
|
650
|
+
if(chunk.status()=='uploading') {
|
651
|
+
uploading = true;
|
652
|
+
return(false);
|
653
|
+
}
|
654
|
+
});
|
655
|
+
if(uploading) return(false);
|
656
|
+
});
|
657
|
+
return(uploading);
|
658
|
+
}
|
659
|
+
$.upload = function(){
|
660
|
+
// Make sure we don't start too many uploads at once
|
661
|
+
if($.isUploading()) return;
|
662
|
+
// Kick off the queue
|
663
|
+
$.fire('uploadStart');
|
664
|
+
for (var num=1; num<=$.getOpt('simultaneousUploads'); num++) {
|
665
|
+
$.uploadNextChunk();
|
666
|
+
}
|
667
|
+
};
|
668
|
+
$.pause = function(){
|
669
|
+
// Resume all chunks currently being uploaded
|
670
|
+
$h.each($.files, function(file){
|
671
|
+
file.abort();
|
672
|
+
});
|
673
|
+
$.fire('pause');
|
674
|
+
};
|
675
|
+
$.cancel = function(){
|
676
|
+
$h.each($.files, function(file){
|
677
|
+
file.cancel();
|
678
|
+
});
|
679
|
+
$.fire('cancel');
|
680
|
+
};
|
681
|
+
$.progress = function(){
|
682
|
+
var totalDone = 0;
|
683
|
+
var totalSize = 0;
|
684
|
+
// Resume all chunks currently being uploaded
|
685
|
+
$h.each($.files, function(file){
|
686
|
+
totalDone += file.progress()*file.size;
|
687
|
+
totalSize += file.size;
|
688
|
+
});
|
689
|
+
return(totalSize>0 ? totalDone/totalSize : 0);
|
690
|
+
};
|
691
|
+
$.addFile = function(file){
|
692
|
+
appendFilesFromFileList([file]);
|
693
|
+
};
|
694
|
+
$.removeFile = function(file){
|
695
|
+
var files = [];
|
696
|
+
$h.each($.files, function(f,i){
|
697
|
+
if(f!==file) files.push(f);
|
698
|
+
});
|
699
|
+
$.files = files;
|
700
|
+
};
|
701
|
+
$.getFromUniqueIdentifier = function(uniqueIdentifier){
|
702
|
+
var ret = false;
|
703
|
+
$h.each($.files, function(f){
|
704
|
+
if(f.uniqueIdentifier==uniqueIdentifier) ret = f;
|
705
|
+
});
|
706
|
+
return(ret);
|
707
|
+
};
|
708
|
+
$.getSize = function(){
|
709
|
+
var totalSize = 0;
|
710
|
+
$h.each($.files, function(file){
|
711
|
+
totalSize += file.size;
|
712
|
+
});
|
713
|
+
return(totalSize);
|
714
|
+
};
|
715
|
+
|
716
|
+
return(this);
|
717
|
+
}
|
metadata
ADDED
@@ -0,0 +1,68 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: resumablejs-rails
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- beanie
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2013-05-13 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: railties
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - '>'
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '3.1'
|
20
|
+
- - <
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: '5'
|
23
|
+
type: :runtime
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
26
|
+
requirements:
|
27
|
+
- - '>'
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '3.1'
|
30
|
+
- - <
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '5'
|
33
|
+
description: resumable.js for the Rails Asset Pipeline
|
34
|
+
email:
|
35
|
+
- ich@abwesend.com
|
36
|
+
executables: []
|
37
|
+
extensions: []
|
38
|
+
extra_rdoc_files: []
|
39
|
+
files:
|
40
|
+
- lib/resumablejs-rails/version.rb
|
41
|
+
- lib/resumablejs-rails.rb
|
42
|
+
- vendor/assets/javascripts/resumable.js
|
43
|
+
- README.md
|
44
|
+
homepage: https://github.com/beanieboi/resumablejs-rails
|
45
|
+
licenses:
|
46
|
+
- MIT
|
47
|
+
metadata: {}
|
48
|
+
post_install_message:
|
49
|
+
rdoc_options: []
|
50
|
+
require_paths:
|
51
|
+
- lib
|
52
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
53
|
+
requirements:
|
54
|
+
- - '>='
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
version: '0'
|
57
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
requirements: []
|
63
|
+
rubyforge_project:
|
64
|
+
rubygems_version: 2.0.0
|
65
|
+
signing_key:
|
66
|
+
specification_version: 4
|
67
|
+
summary: resumable.js for the Rails Asset Pipeline
|
68
|
+
test_files: []
|