(function(){ /** * Promise-based API for uploading files via HTTP POST to a form handler URL. * * TODO fix the docs here * To upload a file: * * // list some details when the upload completes * function onComplete(file, upload) { * Ext.space.Logger.log("Upload finished: " + upload.url); * Ext.space.Logger.log("Upload size: " + upload.totalBytes); * Ext.space.Logger.log("Saved to: " + upload.fileName); * Ext.space.Logger.log("File path: " + file.path); * Ext.space.Logger.log("File name: " + file.name); * Ext.space.Logger.log("File size on disk: " + file.size); * } * * Ext.space.Uploads.upload({ url: "http://www.sencha.com/" }).then(onComplete); * * The `upload` objects involved here are instances of Ext.space.files.Upload; * the `file` object is an Ext.space.files.File. * * To get a list of all uploads currently in progress, plus up to the ten most * recently completed uploads: * * Ext.space.Uploads.getUploads().then(function(uploads) { * uploads.forEach(function(upload) { * Ext.space.Logger.log(upload.fileName); * }); * }); * * If you have a upload object and want to fetch the latest information about it, * you can get the progress of a single upload at a time: * * upload.getProgress().then(function(updatedUpload) { * Ext.space.Logger.log(updatedUpload.bytesUploaded + " bytes uploaded"); * }); * * Alternatively, you can wire up a progress event callback on the upload: * * upload.on("progress", function(updatedUpload) { * // gets called every time new progress is available * }); * * To cancel a upload in progress: * * upload.cancel().then(function() { * Ext.space.Logger.log("Canceled!"); * }); * * @aside guide file_locker * */Ext.define("Ext.space.Uploads", { singleton: true, /** * @private * Cache of the upload information returned by the native bridge */ uploads: null, /** * @private * Whether or not the upload manager has registered callbacks with the native bridge UploadManager#watchUploads */ watching: false, /** * @private */ constructor: function() { this.uploads = {}; }, /** * Upload a file. * * @param {Object} args Upload parameters * @param {String} args.url The URL where the file will be posted. * @param {String} args.fileFieldName The post field name to assign the file to. * @param {Object} args.params Optional POST parameters to be sent along with the file. * @param {Object} args.headers Optional HTTP headers to be sent along with the file. * @return {Ext.space.Promise} Promise that will resolve with the upload response */ upload: function(args) { var upload = new Ext.space.files.Upload(); var promise = upload.done; if (!args) { promise.reject("Missing upload arguments definition"); } else if (!args.key) { promise.reject("Missing file key"); } else if (!args.url) { promise.reject("Missing URL"); } else if (!args.fileFieldName) { promise.reject("Missing fileFieldName"); } else { var cmd = { command: "Files#uploadFile", key: args.key, postUrl: args.url, fileFieldName: args.fileFieldName, callbacks: { // TODO: when UploadManger is done, switch to this onStart method, remove onSuccess /* onStart: function(id) { if (id) { // cache a reference to the Upload object, so we can // continue to update it over time manager.uploads[id] = upload; } manager.watchUploads(); }, // no onSuccess callback because we'll let watchUploads do // the necessary notification // onSuccess: function(id) {}, */ onSuccess: function(response) { Ext.space.Logger.warn("Upload complete", response); if (response && !upload.response) { upload.response = response; } promise.fulfill(args.file, upload); }, onError: function(error) { promise.reject(error); } } }; if (args.params) { cmd.params = args.params; } if (args.headers) { cmd.headers = args.headers; } Ext.space.Communicator.send(cmd); } // TODO when UploadManger is done, just return upload return upload.done; }, /** * Retrieve the current status of all active uploads, plus the most recently * completed uploads. * * @return {Ext.space.Promise} Promise which will receive an array of * Ext.space.files.Upload objects */ getUploads: function(args) { var promise = new Ext.space.Promise(); var manager = this; function makeUpload(item) { var id = item.uploadId; if (manager.uploads[id]) { return manager.uploads[id]._updateWith(item); } else { manager.uploads[id] = new Ext.space.files.Upload(item); return manager.uploads[id]; } } Ext.space.Communicator.send({ command: "UploadManager#getUploads", callbacks: { onSuccess: function(responses) { if (Object.prototype.toString.call(responses) === "[object Array]") { // resolve with an array of Upload objects promise.fulfill(responses.map(makeUpload)); manager.watchUploads(); } else { // what happened? promise.reject("Malformed (non-Array) response from the native bridge"); } }, onError: function(error) { promise.reject(error); } } }); return promise; }, /** * Check a upload's progress (normally done via upload.getProgress()). * * @private * @param {Object} upload Upload to check * @return {Ext.space.Promise} Promise which will receive an up-to-date copy of the * Ext.space.files.Upload */ getProgress: function(upload) { var id, match, manager = this; var promise = new Ext.space.Promise(); if (!upload) { promise.reject("Missing upload"); } else { id = upload.uploadId; if (id && manager.uploads[id]) { if (manager.uploads[id].isComplete) { // if it's cached and complete, return it promise.fulfill(manager.uploads[id]); } else { // if it's cached and incomplete, get it from getUploads this.getUploads().then(function(uploads) { uploads.some(function(ul) { if (ul.uploadId === id) { match = ul; return true; } }); if (match) { promise.fulfill(match); } else { promise.reject("Upload " + id + " not found"); } }, function(error) { promise.reject(error); }); } } else { promise.reject("Upload " + id + " not found"); } } return promise; }, /** * Cancel a upload (normally done via upload.cancel()). * * @private * @param {Object} upload Upload to check * @return {Ext.space.Promise} Promise which will resolve when the upload is canceled. If * the upload is already done or canceled, it will reject. */ cancel: function(upload) { var promise = new Ext.space.Promise(), manager = this; if (!upload || !upload.uploadId) { promise.reject("Missing upload ID"); } else { Ext.space.Communicator.send({ command: "UploadManager#cancelUpload", uploadId: upload.uploadId, callbacks: { onSuccess: function() { manager.uploads[upload.uploadId].done.reject("Canceled"); promise.fulfill(true); }, onError: function(error) { promise.reject(error); } } }); } return promise; }, /** * Watch for updates coming in from the native bridge, to keep the internal * cache up to date * * @private */ watchUploads: function() { var manager = this, cache = this.uploads, activeCount = 0; function processItem(item) { var id = item.uploadId, alreadyComplete = !(id in cache) || cache[id].isComplete, justCompleted = !alreadyComplete && item.isComplete; // count the uploads still in progress to we know when to unwatch if (!item.isComplete) { activeCount++; } // create or update the cached upload object if (cache[id]) { cache[id]._updateWith(item); } else { cache[id] = new Ext.space.files.Upload(item); } // resolve the original promise with the final data if (justCompleted) { Ext.space.SecureFiles.getFile(cache[id].fileKey).then(function(file) { cache[id].done.fulfill(file, cache[id]); }); } } if (!manager.watching) { manager.watching = true; Ext.space.Communicator.send({ command: "UploadManager#watchUploads", callbacks: { onSuccess: function(responses) { activeCount = 0; if (Object.prototype.toString.call(responses) === "[object Array]") { responses.forEach(processItem); if (!activeCount) { manager.unwatchUploads(); } } }, onError: function(error) { manager.unwatchUploads(); } } }); } }, /** * Discontinue watching for upload updates from the native bridge * * @private */ unwatchUploads: function() { if (this.watching) { Ext.space.Communicator.send({ command: "UploadManager#unwatchUploads" }); this.watching = false; } }}); }());