diff --git a/.gitignore b/.gitignore index bee4741..b40f966 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,6 @@ *.pyc *.pyo *.db +node_modules/* +bower_components/* +npm-debug.log \ No newline at end of file diff --git a/Gruntfile.js b/Gruntfile.js new file mode 100644 index 0000000..69ccc7e --- /dev/null +++ b/Gruntfile.js @@ -0,0 +1,81 @@ +/*global module:false*/ +module.exports = function(grunt) { + + // Project configuration. + grunt.initConfig({ + // Metadata. + pkg: grunt.file.readJSON('package.json'), + banner: '/*! <%= pkg.name %> - v<%= pkg.version %> - ' + + '<%= grunt.template.today("yyyy-mm-dd") %>\n' + + '<%= pkg.homepage ? "* " + pkg.homepage + "\\n" : "" %>' + + '* Copyright (c) <%= grunt.template.today("yyyy") %> <%= pkg.authors.join(", ") %>;' + + ' Licensed <%= pkg.license %> */\n', + // Task configuration. + concat: { + options: { + banner: '<%= banner %>', + stripBanners: true + }, + dist: { + src: ['<%= pkg.main %>'], + dest: 'dist/<%= pkg.name %>.js' + } + }, + uglify: { + options: { + banner: '<%= banner %>' + }, + dist: { + src: 'dist/<%= pkg.name %>.js', + dest: 'dist/<%= pkg.name %>.min.js' + } + }, + jshint: { + options: { + curly: true, + eqeqeq: true, + immed: true, + latedef: true, + newcap: true, + noarg: true, + sub: true, + undef: true, + unused: true, + boss: true, + eqnull: true, + browser: true, + globals: {} + }, + all: ['Gruntfile.js'] + }, + qunit: { + files: ['test/**/*.html'] + }, + watch: { + gruntfile: { + files: '<%= jshint.gruntfile.src %>', + tasks: ['jshint:gruntfile'] + }, + lib_test: { + files: '<%= jshint.lib_test.src %>', + tasks: ['jshint:lib_test', 'qunit'] + } + }, + bower_version: { + update: [] + } + }); + + // These plugins provide necessary tasks. + grunt.loadNpmTasks('grunt-contrib-concat'); + grunt.loadNpmTasks('grunt-contrib-uglify'); + grunt.loadNpmTasks('grunt-contrib-qunit'); + grunt.loadNpmTasks('grunt-contrib-jshint'); + grunt.loadNpmTasks('grunt-contrib-watch'); + grunt.loadNpmTasks('grunt-bower-version'); + + + // Default task. + grunt.registerTask('default', ['concat', 'uglify', 'bower_version']); + +}; diff --git a/bower.json b/bower.json index a4ceebd..49972bd 100644 --- a/bower.json +++ b/bower.json @@ -1,7 +1,7 @@ { "name": "mule-uploader", "main": "mule-uploader.js", - "version": "1.1.3", + "version": "1.2.0", "homepage": "https://github.com/cinely/mule-uploader", "authors": [ "gabipurcaru" @@ -26,5 +26,8 @@ "components", "test", "tests" - ] -} + ], + "dependencies": { + "crypto-js": "^3.1.6" + } +} \ No newline at end of file diff --git a/dist/mule-uploader.js b/dist/mule-uploader.js new file mode 100644 index 0000000..0b6cbf7 --- /dev/null +++ b/dist/mule-uploader.js @@ -0,0 +1,1288 @@ +/*! mule-uploader - v1.2.0 - 2016-09-28 +* https://github.com/cinely/mule-uploader +* Copyright (c) 2016 Gabi Purcaru; Licensed MIT */ +(function(namespace){ + + // AJAX helper. It takes an object that contains load_callback, error_callback, + // url, method, headers, state_change_callback, progress_callback + var XHR = function(args) { + // the user may or may not pass any headers + args.headers = args.headers || {}; + + // if no method is given, default to GET + args.method = args.method || "GET"; + + var xhr = new XMLHttpRequest(); + + // set the "load" callback if given + if(args.load_callback && typeof args.load_callback == 'function') { + xhr.addEventListener("load", args.load_callback, true); + } + + // set the "error" callback if given + if(args.error_callback && typeof args.error_callback == 'function') { + xhr.addEventListener("error", args.error_callback, true); + } + + // set the "readystatechange" callback if given + if(args.state_change_callback && typeof args.state_change_callback == 'function') { + xhr.addEventListener("readystatechange", args.state_change_callback); + } + + // set the "progress" callback if given + if(args.progress_callback && typeof args.progress_callback == 'function') { + xhr.upload.addEventListener("progress", args.progress_callback); + } + + // set the "timeout" callback if given + if(args.timeout_callback && typeof args.timeout_callback == 'function') { + xhr.addEventListener('timeout', args.timeout_callback); + } + + // adding extra params as needed + var url = args.url; + if(args.extra_params) { + for(var param_name in args.extra_params) { + if(args.extra_params.hasOwnProperty(param_name)) { + if(url.indexOf('?') !== -1) { + url += "&"; + } else { + url += "?"; + } + + url += encodeURIComponent(param_name) + "="; + url += encodeURIComponent(args.extra_params[param_name]); + } + } + } + + // open the xhr connection + xhr.open(args.method, url); + + // set the headers + for(var header in args.headers) { + if(args.headers.hasOwnProperty(header)) { + xhr.setRequestHeader(header, args.headers[header]); + } + } + + // send the ajax call + if(args.body) { + xhr.send(args.body); + } else { + xhr.send(); + } + return xhr; + }; + + namespace.mule_upload = function(settings) { + var debug = true; + + // custom logging function that prepends a text for easy identification; + // it is also toggled by the `debug` flag + var log = function() {}; + if(debug && console && console.log) { + log = function() { + var args = ["[MuleUploader]"]; + for(var i=0; i 0) { + return this.upload_file(this.input.files[0], false); + } else { + alert("No file selected"); + } + }; + + Uploader.prototype.upload_file = function(file, force) { + var u = this; + // the `onchange` event may be triggered multiple times, so we + // must ensure that the callback is only executed the first time + // also make sure the file is not already set. + if(u.get_state() != "waiting") { + return false; + } + + if (file) { + u.file = file; + } + + if (!u.file) { + return false; + } + + // we use the lastModifiedDate, the file name and size to uniquely + // identify a file. There may be false positives and negatives, + // but the chance for a false positive is basically zero + // some browsers don't report the last modified date, so we default + // to a blank date + u.file.lastModifiedDate = u.file.lastModifiedDate || new Date(0); + + if(u.file.size > u.settings.max_size) { + alert( + ["The maximum allowed file size is ", + (u.settings.max_size / GB), + "GB. Please select another file."].join('') + ); + return false; + } + + // check for accepted extensions, if applicable + if(u.settings.accepted_extensions) { + // get the file extension + var file_extension = file.name.split('.').pop(); + + // split the given extensions into an array + var extensions_array = u.settings.accepted_extensions.split(','); + + // and match the extension against the given extension list + var file_accepted = false; + for(var i=0; i 15000) { // 15s + log("Chunk Failed; retry"); + clearInterval(u._intervals[chunk]); + if(u.get_state() == "processing") { + xhr.abort(); + error_handler.call(xhr); + u._chunk_xhr[u._chunk_xhr.indexOf(xhr)] = null; + } + } + }, 4000); // every 4s + }); + }; + + // initiates the upload finish sequence + Uploader.prototype.finish_upload = function() { + var u = this; + + // make sure it's not triggered when not processing (e.g. multiple times) + if(u.get_state() != "processing") { + return; + } + + // change the upload's state + u.set_state("finishing"); + + u.settings.on_progress.call(u, u.file.size, u.file.size); // 100% done. + + + var handler = function(e) { + // i.e. if it's a 2XX response + if(e.target.status / 100 == 2) { + log("Finished file."); + u.set_state("finished"); + u.settings.on_progress.call(u, u.file.size, u.file.size); // it's 100% done + + // trigger the complete event callback + u.settings.on_complete.call(u); + } else if(e.target.status == 400 && + e.target.responseText.indexOf("EntityTooSmall") !== -1) { + // an "EntityTooSmall" error means that we missed a chunk + AmazonXHR.list(u.auth, u.file, u.settings.key, u.upload_id, u.settings.chunk_size, function(parts) { + u.update_chunks(parts); + var next_chunk = u.get_next_chunk(); + u.set_state("processing"); + u.upload_chunk(next_chunk); + }); + } else if(e.target.status == 404) { + // 404 = NoSuchUpload = check if already finished + // if so, start a new upload + u.cancel(function() { + u.upload_file(u.file, true); + }); + } else { + u.check_already_uploaded(function() { + handler({ + target: { + status: 200 + } + }); + }, function() { + handler({ + target: { + status: 404 + } + }); + }); + } + }; + + AmazonXHR.list(u.auth, u.file, u.settings.key, u.upload_id, u.settings.chunk_size, function(parts) { + var num_chunks = Math.ceil(u.file.size / u.settings.chunk_size); + + // check that we uploaded all the chunks; if we didn't, + // start uploading the missing ones + if(parts.length != num_chunks) { + u.update_chunks(parts); + var next_chunk = u.get_next_chunk(); + u.set_state("processing"); + u.upload_chunk(next_chunk); + return; + } + + AmazonXHR.finish(u.auth, u.file, u.settings.key, u.upload_id, parts, u.settings.chunk_size, handler, handler); + }); + }; + + + // notify the server that a chunk finished uploading. This is needed for + // upload resumes + Uploader.prototype.notify_chunk_uploaded = function(chunk) { + var u = this; + if(u.get_state() != "processing") { + return; + } + var key = u.settings.key; + var upload_id = u.upload_id; + var url = u.settings.ajax_base + '/chunk_loaded/'; + + var args = utils.extend_object(u.settings.extra_params || {}, { + chunk: chunk, + key: key, + upload_id: upload_id, + filename: u.file.name, + filesize: u.file.size, + content_type: u.file.type, + last_modified: u.file.lastModifiedDate.valueOf() + }); + + XHR({ + url:url, + extra_params:args + }); + + }; + + // check whether the file is already uploaded + Uploader.prototype.check_already_uploaded = function(callback, error_callback) { + var u = this; + var method = "HEAD"; + var path = "/" + u.settings.key; + var inner_handler = function(e) { + // the handler only checks for status code; + // if the HEAD returns 404, re-upload, + // else, it returns 200 and finish the upload + if(e.target.status / 100 == 2) { + log("Already Uploaded"); + callback(); + } else { + log("Error!"); + error_callback(); + } + }; + + if(!error_callback && typeof(error_callback) !== "function") { + error_callback = function() { + setTimeout(function() { + return u.check_already_uploaded(callback, error_callback); + }, 2500); + }; + } + + console.log(u.settings); + var host = "s3" + utils.region_string(u.settings.region) + ".amazonaws.com"; + var url = location.protocol + "//" + host + "/" + u.settings.bucket + "/" + path; + XHR({ + url: url, + method: method, + load_callback: inner_handler, + error_callback: error_callback + }); + }; + + // cancels an upload + Uploader.prototype.cancel = function(callback) { + // empty all fields, cancel all intervals, abort all xhr's + var u = this; + for(var i=0; i < u._chunk_xhr.length; i++) { + log("Abort chunk: " + u._chunk_xhr[i]); + u._chunk_xhr[i].abort(); + } + u._intervals = u._intervals || {}; + for(var x in u._intervals) { + if(u._intervals.hasOwnProperty(x)) { + clearInterval(u._intervals[x]); + } + } + callback = callback || function() {}; + u.set_state("canceled"); + u._chunk_xhr = u._chunk_xhr || []; + u.settings.on_progress.call(u, 0, 0); + u._chunk_xhr = null; + u._chunks = null; + u._uploading_chunks = null; + u._loaded_chunks = null; + u._start_fired = false; + u.upload_id = null; + u._progress = null; + u.set_state("waiting"); // wait for a new upload + callback(); + }; + + // updates the chunk history with the given chunks + Uploader.prototype.update_chunks = function(parts) { + var u = this; + var loaded = []; + var num_chunks = Math.ceil(u.file.size / u.settings.chunk_size); + + u._init_chunks(true); + u._uploading_chunks = []; + u._loaded_chunks = []; + + var i; + for(i=0; i < parts.length; i++) { + var part_number = parseInt(parts[i][0], 10); + u.add_loaded_chunk(part_number - 1); + u.set_chunk_finished(part_number - 1); + loaded.push(part_number - 1); + } + for(i=0; i < num_chunks; i++) { + if(loaded.indexOf(i) === -1) { + log("Chunk not uploaded: ", i); + u.set_progress(i, 0); + } + } + }; + + // returns true if a file is selected + Uploader.prototype.is_selected = function() { + return !!this.file; + }; + + // returns the uploader's state + Uploader.prototype.get_state = function() { + return this._state; + }; + + // sets the uploader's state + Uploader.prototype.set_state = function(state) { + this._state = state; + return state; + }; + + // set a chunk's progress + Uploader.prototype.set_progress = function(chunk, loaded) { + this.log_status(); + this._progress = this._progress || {}; + this._total_progress = (this._total_progress || 0 ) + loaded - (this._progress[chunk] || 0); + this._progress[chunk] = loaded; + this.settings.on_chunk_progress.call( + this, chunk, loaded, this.get_chunk_size(chunk)); + }; + + // gets the total bytes uploaded + Uploader.prototype.get_total_progress = function() { + return this._total_progress || 0; + }; + + // returns true if a chunk is already uploaded + Uploader.prototype.is_chunk_loaded = function(chunk) { + this._loaded_chunks = this._loaded_chunks || []; + return this._loaded_chunks.indexOf(chunk) !== -1; + }; + + // adds a chunk to the uploaded list + Uploader.prototype.add_loaded_chunk = function(chunk) { + this._loaded_chunks = this._loaded_chunks || []; + this._loaded_chunks.push(chunk); + this.set_progress(chunk, this.get_chunk_size(chunk)); + }; + + // returns true if the chunk is currently uploading + Uploader.prototype.get_chunk_uploading = function(chunk) { + this._uploading_chunks = this._uploading_chunks || []; + return this._uploading_chunks.indexOf(chunk) !== -1; + }; + + // sets whether a chunk is currently uploading or not + Uploader.prototype.set_chunk_uploading = function(chunk, val) { + if(typeof val == "undefined") { + val = true; + } + this._uploading_chunks = this._uploading_chunks || []; + if(val) { + this._uploading_chunks.push(chunk); + } else { + var list = []; + for(var i=0; i < this._uploading_chunks.length; i++) { + if(this._uploading_chunks[i] != chunk) { + list.push(this._uploading_chunks[i]); + } + } + this._uploading_chunks = list; + } + }; + + // initialize inner representation of chunks + Uploader.prototype._init_chunks = function(force) { + var u = this; + if(!u._chunks || force) { + u._chunks = []; + var num_chunks = Math.ceil(u.file.size / u.settings.chunk_size); + for(var i=0; i < num_chunks; i++) { + u._chunks.push(false); + } + } + }; + + // sets whether a chunk finished uploading + Uploader.prototype.set_chunk_finished = function(chunk, val) { + if(typeof val == "undefined") { + val = true; + } + var u = this; + u._init_chunks(); + u._chunks[chunk] = val; + }; + + // get next chunk to be uploaded; if all chunks are done, return -1 + Uploader.prototype.get_next_chunk = function(chunk) { + var u = this; + u._init_chunks(); + if(chunk && !u._chunks[chunk] && !u.get_chunk_uploading(chunk)) { + return chunk; + } + for(var i=0; i < u._chunks.length; i++) { + if(!u._chunks[i] && !u.get_chunk_uploading(i)) { + return i; + } + } + return -1; + }; + + // returns true if all chunks finished uploaded + Uploader.prototype.upload_finished = function() { + var u = this; + u._init_chunks(); + for(var i=0; i < u._chunks.length; i++) { + if(!u._chunks[i] || u.get_chunk_uploading(i)) { + return false; + } + } + return true; + }; + + Uploader.prototype.is_last_chunk = function(chunk) { + return Math.ceil(this.file.size / this.settings.chunk_size) - 1 == chunk; + }; + + Uploader.prototype.get_chunk_size = function(chunk) { + if(this.is_last_chunk(chunk)) { + return this.file.size % this.settings.chunk_size; + } else { + return this.settings.chunk_size; + } + }; + + Uploader.prototype.log_status = function() { + // log(this.get_total_progress() / this.file.size * 100); + }; + + Uploader.prototype.on_chunk_progress = function(f) { this.settings.on_chunk_progress = f; }; + Uploader.prototype.on_progress = function(f) { this.settings.on_progress = f; }; + Uploader.prototype.on_select = function(f) { this.settings.on_select = f; }; + Uploader.prototype.on_error = function(f) { this.settings.on_error = f; }; + Uploader.prototype.on_complete = function(f) { this.settings.on_complete = f; }; + Uploader.prototype.on_init = function(f) { this.settings.on_init = f; }; + Uploader.prototype.on_start = function(f) { this.settings.on_start = f; }; + Uploader.prototype.on_chunk_uploaded = function(f) { this.settings.on_chunk_uploaded = f; }; + + return new Uploader(settings); + }; + + + var AmazonXHR = function(settings) { + this.settings = settings; + }; + AmazonXHR.finish = function(auth, file, key, upload_id, parts, chunk_size, callback) { + var querystring = {"uploadId": upload_id}; + + // compose the CompleteMultipartUpload request for putting + // the chunks together + var data = ""; + for(var i=0; i"; + data += "" + parts[i][1] + ""; + data += ""; + } + data += ""; + + // firefox requires a small hack + if(navigator.userAgent.indexOf("Firefox") !== -1) { + data = new Blob([data]); + } + + return new AmazonXHR({ + auth: auth, + key: key, + method: "POST", + querystring: querystring, + headers: {}, + payload: data, + load_callback: callback + }).send(); + }; + AmazonXHR.list = function(auth, file, key, upload_id, chunk_size, callback, error_callback, marker) { + var querystring = {"uploadId": upload_id}; + if(marker) { + querystring['part-number-marker'] = marker; + } + return new AmazonXHR({ + auth: auth, + key: key, + method: "GET", + querystring: querystring, + headers: {}, + payload: "", + error_callback: error_callback, + load_callback: function(e) { + if(e.target.status === 404) { + // i.e. the file was already uploaded; start fresh + if(error_callback) { + error_callback(); + } + return; + } + + // process the parts, and return an array of + // [part_number, etag, size] through the given callback + window.debug = e; + var xml = e.target.responseXML; + var parts = []; + var xml_parts = xml.getElementsByTagName("Part"); + var num_chunks = Math.ceil(file.size / chunk_size); + for(var i=0; i < xml_parts.length; i++) { + var part_number = parseInt(xml_parts[i].getElementsByTagName("PartNumber")[0].textContent, 10); + var etag = xml_parts[i].getElementsByTagName("ETag")[0].textContent; + var size = parseInt(xml_parts[i].getElementsByTagName("Size")[0].textContent, 10); + + if(part_number != num_chunks && size != chunk_size) { + continue; // chunk corrupted + } else if(part_number == num_chunks && + size != file.size % chunk_size) { + continue; // final chunk corrupted + } + + parts.push([ + part_number, + etag, + size + ]); + } + var is_truncated = xml.getElementsByTagName("IsTruncated")[0].textContent; + if(is_truncated === "true") { + var part_marker = xml.getElementsByTagName("NextPartNumberMarker")[0].textContent; + AmazonXHR.list(auth, file, key, upload_id, chunk_size, function(new_parts) { + callback(parts.concat(new_parts)); + }, error_callback, part_marker); + } else { + callback(parts); + } + } + }).send(); + }; + + AmazonXHR.upload_chunk = function(auth, key, upload_id, chunk_num, chunk, callbacks, xhr_callback) { + var callback, error_callback, progress_callback, readystate_callback; + if(callbacks instanceof Object) { + callback = callbacks.load_callback; + error_callback = callbacks.error_callback; + progress_callback = callbacks.progress_callback; + readystate_callback = callbacks.state_change_callback; + } else { + callback = callbacks; + } + var querystring = { + partNumber: chunk_num + 1, + uploadId: upload_id + }; + return (new AmazonXHR({ + auth: auth, + key: key, + method: "PUT", + querystring: querystring, + headers: {}, + payload: chunk, + load_callback: callback, + error_callback: error_callback, + progress_callback: progress_callback, + state_change_callback: readystate_callback + })).send(xhr_callback); + }; + AmazonXHR.init = function(auth, key, file, callback) { + return new AmazonXHR({ + auth: auth, + key: key, + method: "POST", + querystring: { + "uploads": "" + }, + headers: { + "x-amz-acl": "public-read", + "Content-Disposition": "attachment; filename=" + encodeURIComponent(file.name), + "Content-Type": auth.content_type || "application/octet-stream" + }, + payload: "", + load_callback: callback + }).send(); + }; + AmazonXHR.prototype = { + send: function(callback) { + var self = this; + self.request_date = new Date(); + + self.headers = self.settings.headers; + self.headers['host'] = self.settings.auth.bucket + ".s3" + utils.region_string(self.settings.auth.region) + ".amazonaws.com"; + + var date_string = [ + self.settings.auth.date.getUTCFullYear(), + utils.zfill(self.settings.auth.date.getUTCMonth() + 1, 2), + utils.zfill(self.settings.auth.date.getUTCDate(), 2) + ].join(''); + + self.settings.querystring['X-Amz-Date'] = utils.uriencode(utils.iso8601(self.request_date)); + self.settings.querystring["X-Amz-Algorithm"] = "AWS4-HMAC-SHA256"; + self.settings.querystring["X-Amz-Expires"] = 86400; + self.settings.querystring["X-Amz-Credential"] = utils.uriencode([ + self.settings.auth.access_key, + "/" + date_string + "/", + self.settings.auth.region + "/s3/aws4_request" + ].join('')); + self.settings.querystring["X-Amz-SignedHeaders"] = ""; + + var header_keys = [] + for(var key in self.headers) { + header_keys.push(key); + } + header_keys.sort(); + self.settings.querystring["X-Amz-SignedHeaders"] = utils.uriencode(header_keys.join(';')); + + self.settings.querystring["X-Amz-Signature"] = self.get_authorization_header(); + + var url = location.protocol + "//" + self.headers['host'] + "/" + self.settings.key; + delete self.headers['host']; // keep this header only for hashing + + var first = true; + for(var key in self.settings.querystring) { + if(self.settings.querystring.hasOwnProperty(key)) { + if(first) { + url += "?"; + } + first = false; + url += key + "=" + self.settings.querystring[key] + "&"; + } + } + url = url.slice(0, -1); // remove extra ampersand + + var xhr = XHR({ + url: url, + method: self.settings.method, + headers: self.headers, + body: self.settings.payload, + + load_callback: self.settings.load_callback, + progress_callback: self.settings.progress_callback, + state_change_callback: self.settings.state_change_callback, + error_callback: self.settings.error_callback, + timeout_callback: self.settings.timeout_callback + }); + if(callback) { + callback(xhr); + } + }, + get_authorization_header: function() { + if(!this.settings.auth.date) { + throw "Invalid date given."; + } + + var header = ""; + + var header_keys = utils.get_sorted_keys(this.headers); + + // signed headers + var signed_headers = ""; + for(var i=0; i0?this.upload_file(this.input.files[0],!1):void alert("No file selected")},f.prototype.upload_file=function(a,e){var f=this;if("waiting"!=f.get_state())return!1;if(a&&(f.file=a),!f.file)return!1;if(f.file.lastModifiedDate=f.file.lastModifiedDate||new Date(0),f.file.size>f.settings.max_size)return alert(["The maximum allowed file size is ",f.settings.max_size/k,"GB. Please select another file."].join("")),!1;if(f.settings.accepted_extensions){for(var g=a.name.split(".").pop(),h=f.settings.accepted_extensions.split(","),i=!1,j=0;j15e3&&(h("Chunk Failed; retry"),clearInterval(b._intervals[a]),"processing"==b.get_state()&&(c.abort(),m.call(c),b._chunk_xhr[b._chunk_xhr.indexOf(c)]=null))},4e3)})},f.prototype.finish_upload=function(){var a=this;if("processing"==a.get_state()){a.set_state("finishing"),a.settings.on_progress.call(a,a.file.size,a.file.size);var b=function(d){d.target.status/100==2?(h("Finished file."),a.set_state("finished"),a.settings.on_progress.call(a,a.file.size,a.file.size),a.settings.on_complete.call(a)):400==d.target.status&&d.target.responseText.indexOf("EntityTooSmall")!==-1?c.list(a.auth,a.file,a.settings.key,a.upload_id,a.settings.chunk_size,function(b){a.update_chunks(b);var c=a.get_next_chunk();a.set_state("processing"),a.upload_chunk(c)}):404==d.target.status?a.cancel(function(){a.upload_file(a.file,!0)}):a.check_already_uploaded(function(){b({target:{status:200}})},function(){b({target:{status:404}})})};c.list(a.auth,a.file,a.settings.key,a.upload_id,a.settings.chunk_size,function(d){var e=Math.ceil(a.file.size/a.settings.chunk_size);if(d.length!=e){a.update_chunks(d);var f=a.get_next_chunk();return a.set_state("processing"),void a.upload_chunk(f)}c.finish(a.auth,a.file,a.settings.key,a.upload_id,d,a.settings.chunk_size,b,b)})}},f.prototype.notify_chunk_uploaded=function(a){var c=this;if("processing"==c.get_state()){var e=c.settings.key,f=c.upload_id,g=c.settings.ajax_base+"/chunk_loaded/",h=d.extend_object(c.settings.extra_params||{},{chunk:a,key:e,upload_id:f,filename:c.file.name,filesize:c.file.size,content_type:c.file.type,last_modified:c.file.lastModifiedDate.valueOf()});b({url:g,extra_params:h})}},f.prototype.check_already_uploaded=function(a,c){var e=this,f="HEAD",g="/"+e.settings.key,i=function(b){b.target.status/100==2?(h("Already Uploaded"),a()):(h("Error!"),c())};c||"function"==typeof c||(c=function(){setTimeout(function(){return e.check_already_uploaded(a,c)},2500)}),console.log(e.settings);var j="s3"+d.region_string(e.settings.region)+".amazonaws.com",k=location.protocol+"//"+j+"/"+e.settings.bucket+"/"+g;b({url:k,method:f,load_callback:i,error_callback:c})},f.prototype.cancel=function(a){for(var b=this,c=0;c",k=0;k",j+=""+f[k][0]+"",j+=""+f[k][1]+"",j+="";return j+="",navigator.userAgent.indexOf("Firefox")!==-1&&(j=new Blob([j])),new c({auth:a,key:d,method:"POST",querystring:i,headers:{},payload:j,load_callback:h}).send()},c.list=function(a,b,d,e,f,g,h,i){var j={uploadId:e};return i&&(j["part-number-marker"]=i),new c({auth:a,key:d,method:"GET",querystring:j,headers:{},payload:"",error_callback:h,load_callback:function(i){if(404===i.target.status)return void(h&&h());window.debug=i;for(var j=i.target.responseXML,k=[],l=j.getElementsByTagName("Part"),m=Math.ceil(b.size/f),n=0;n= 0.10.0" + }, + "dependencies" : { + "crypto-js" : "^3.1.6" + }, + "devDependencies": { + "grunt": "^1.0", + "grunt-bower-version": "^0.1.1", + "grunt-contrib-concat": "~1.0", + "grunt-contrib-jshint": "~1.0", + "grunt-contrib-qunit": "~1.2", + "grunt-contrib-uglify": "~2.0", + "grunt-contrib-watch": "~1.0" + }, + "scripts": { + "postversion": "grunt" + } +} diff --git a/mule-uploader.js b/src/mule-uploader.js similarity index 87% rename from mule-uploader.js rename to src/mule-uploader.js index d0f2c4b..4d84c0a 100644 --- a/mule-uploader.js +++ b/src/mule-uploader.js @@ -8,30 +8,7 @@ (function(namespace){ - /* - CryptoJS v3.1.2 - code.google.com/p/crypto-js - (c) 2009-2013 by Jeff Mott. All rights reserved. - code.google.com/p/crypto-js/wiki/License - */ - var CryptoJS=CryptoJS||function(h,s){var f={},t=f.lib={},g=function(){},j=t.Base={extend:function(a){g.prototype=this;var c=new g;a&&c.mixIn(a);c.hasOwnProperty("init")||(c.init=function(){c.$super.init.apply(this,arguments)});c.init.prototype=c;c.$super=this;return c},create:function(){var a=this.extend();a.init.apply(a,arguments);return a},init:function(){},mixIn:function(a){for(var c in a)a.hasOwnProperty(c)&&(this[c]=a[c]);a.hasOwnProperty("toString")&&(this.toString=a.toString)},clone:function(){return this.init.prototype.extend(this)}}, - q=t.WordArray=j.extend({init:function(a,c){a=this.words=a||[];this.sigBytes=c!=s?c:4*a.length},toString:function(a){return(a||u).stringify(this)},concat:function(a){var c=this.words,d=a.words,b=this.sigBytes;a=a.sigBytes;this.clamp();if(b%4)for(var e=0;e>>2]|=(d[e>>>2]>>>24-8*(e%4)&255)<<24-8*((b+e)%4);else if(65535>>2]=d[e>>>2];else c.push.apply(c,d);this.sigBytes+=a;return this},clamp:function(){var a=this.words,c=this.sigBytes;a[c>>>2]&=4294967295<< - 32-8*(c%4);a.length=h.ceil(c/4)},clone:function(){var a=j.clone.call(this);a.words=this.words.slice(0);return a},random:function(a){for(var c=[],d=0;d>>2]>>>24-8*(b%4)&255;d.push((e>>>4).toString(16));d.push((e&15).toString(16))}return d.join("")},parse:function(a){for(var c=a.length,d=[],b=0;b>>3]|=parseInt(a.substr(b, - 2),16)<<24-4*(b%8);return new q.init(d,c/2)}},k=v.Latin1={stringify:function(a){var c=a.words;a=a.sigBytes;for(var d=[],b=0;b>>2]>>>24-8*(b%4)&255));return d.join("")},parse:function(a){for(var c=a.length,d=[],b=0;b>>2]|=(a.charCodeAt(b)&255)<<24-8*(b%4);return new q.init(d,c)}},l=v.Utf8={stringify:function(a){try{return decodeURIComponent(escape(k.stringify(a)))}catch(c){throw Error("Malformed UTF-8 data");}},parse:function(a){return k.parse(unescape(encodeURIComponent(a)))}}, - x=t.BufferedBlockAlgorithm=j.extend({reset:function(){this._data=new q.init;this._nDataBytes=0},_append:function(a){"string"==typeof a&&(a=l.parse(a));this._data.concat(a);this._nDataBytes+=a.sigBytes},_process:function(a){var c=this._data,d=c.words,b=c.sigBytes,e=this.blockSize,f=b/(4*e),f=a?h.ceil(f):h.max((f|0)-this._minBufferSize,0);a=f*e;b=h.min(4*a,b);if(a){for(var m=0;mk;){var l;a:{l=u;for(var x=h.sqrt(l),w=2;w<=x;w++)if(!(l%w)){l=!1;break a}l=!0}l&&(8>k&&(j[k]=v(h.pow(u,0.5))),q[k]=v(h.pow(u,1/3)),k++);u++}var a=[],f=f.SHA256=g.extend({_doReset:function(){this._hash=new t.init(j.slice(0))},_doProcessBlock:function(c,d){for(var b=this._hash.words,e=b[0],f=b[1],m=b[2],h=b[3],p=b[4],j=b[5],k=b[6],l=b[7],n=0;64>n;n++){if(16>n)a[n]= - c[d+n]|0;else{var r=a[n-15],g=a[n-2];a[n]=((r<<25|r>>>7)^(r<<14|r>>>18)^r>>>3)+a[n-7]+((g<<15|g>>>17)^(g<<13|g>>>19)^g>>>10)+a[n-16]}r=l+((p<<26|p>>>6)^(p<<21|p>>>11)^(p<<7|p>>>25))+(p&j^~p&k)+q[n]+a[n];g=((e<<30|e>>>2)^(e<<19|e>>>13)^(e<<10|e>>>22))+(e&f^e&m^f&m);l=k;k=j;j=p;p=h+r|0;h=m;m=f;f=e;e=r+g|0}b[0]=b[0]+e|0;b[1]=b[1]+f|0;b[2]=b[2]+m|0;b[3]=b[3]+h|0;b[4]=b[4]+p|0;b[5]=b[5]+j|0;b[6]=b[6]+k|0;b[7]=b[7]+l|0},_doFinalize:function(){var a=this._data,d=a.words,b=8*this._nDataBytes,e=8*a.sigBytes; - d[e>>>5]|=128<<24-e%32;d[(e+64>>>9<<4)+14]=h.floor(b/4294967296);d[(e+64>>>9<<4)+15]=b;a.sigBytes=4*d.length;this._process();return this._hash},clone:function(){var a=g.clone.call(this);a._hash=this._hash.clone();return a}});s.SHA256=g._createHelper(f);s.HmacSHA256=g._createHmacHelper(f)})(Math); - (function(h){for(var s=CryptoJS,f=s.lib,g=f.WordArray,q=f.Hasher,f=s.algo,m=[],r=[],l=function(a){return 4294967296*(a-(a|0))|0},k=2,n=0;64>n;){var j;a:{j=k;for(var u=h.sqrt(j),t=2;t<=u;t++)if(!(j%t)){j=!1;break a}j=!0}j&&(8>n&&(m[n]=l(h.pow(k,0.5))),r[n]=l(h.pow(k,1/3)),n++);k++}var a=[],f=f.SHA256=q.extend({_doReset:function(){this._hash=new g.init(m.slice(0))},_doProcessBlock:function(c,d){for(var b=this._hash.words,e=b[0],f=b[1],g=b[2],j=b[3],h=b[4],m=b[5],n=b[6],q=b[7],p=0;64>p;p++){if(16>p)a[p]= - c[d+p]|0;else{var k=a[p-15],l=a[p-2];a[p]=((k<<25|k>>>7)^(k<<14|k>>>18)^k>>>3)+a[p-7]+((l<<15|l>>>17)^(l<<13|l>>>19)^l>>>10)+a[p-16]}k=q+((h<<26|h>>>6)^(h<<21|h>>>11)^(h<<7|h>>>25))+(h&m^~h&n)+r[p]+a[p];l=((e<<30|e>>>2)^(e<<19|e>>>13)^(e<<10|e>>>22))+(e&f^e&g^f&g);q=n;n=m;m=h;h=j+k|0;j=g;g=f;f=e;e=k+l|0}b[0]=b[0]+e|0;b[1]=b[1]+f|0;b[2]=b[2]+g|0;b[3]=b[3]+j|0;b[4]=b[4]+h|0;b[5]=b[5]+m|0;b[6]=b[6]+n|0;b[7]=b[7]+q|0},_doFinalize:function(){var a=this._data,d=a.words,b=8*this._nDataBytes,e=8*a.sigBytes; - d[e>>>5]|=128<<24-e%32;d[(e+64>>>9<<4)+14]=h.floor(b/4294967296);d[(e+64>>>9<<4)+15]=b;a.sigBytes=4*d.length;this._process();return this._hash},clone:function(){var a=q.clone.call(this);a._hash=this._hash.clone();return a}});s.SHA256=q._createHelper(f);s.HmacSHA256=q._createHmacHelper(f)})(Math); - (function(){var h=CryptoJS,s=h.enc.Utf8;h.algo.HMAC=h.lib.Base.extend({init:function(f,g){f=this._hasher=new f.init;"string"==typeof g&&(g=s.parse(g));var h=f.blockSize,m=4*h;g.sigBytes>m&&(g=f.finalize(g));g.clamp();for(var r=this._oKey=g.clone(),l=this._iKey=g.clone(),k=r.words,n=l.words,j=0;j>>2]|=a[c]<< - 24-8*(c%4);e.call(this,d,b)}else e.apply(this,arguments)}).prototype=b}})(); - + // AJAX helper. It takes an object that contains load_callback, error_callback, // url, method, headers, state_change_callback, progress_callback var XHR = function(args) {