c9-core/plugins/c9.ide.upload/upload_manager.js

419 wiersze
14 KiB
JavaScript

define(function(require, module, exports) {
"use strict";
main.consumes = ["Plugin", "fs", "vfs"];
main.provides = ["upload.manager"];
return main;
/**
* Browser notes:
*
* 1. '/' in file names are replaces by ':'
* 2. Drag and Drop sets e.files (FileList)
* 3. Chrome also sets e.items in drag and drop, which supports folders
* 4. Firefox does not support folder upload in any way
* 5. Safari also doesn't support folder upload
* 6. Folders will show up in a FileList but are almost impossible to destinguish from files
*/
function main(options, imports, register) {
var fs = imports.fs;
var vfs = imports.vfs;
var Plugin = imports.Plugin;
var UploadBatch = require("./batch");
var UploadJob = require("./upload_job");
var path = require("path");
/***** Initialization *****/
var STATE_NEW = "new";
var STATE_UPLOADING = "uploading";
var STATE_PAUSED = "paused";
var STATE_RESUME = "resume";
var STATE_DONE = "done";
var STATE_ERROR = "error";
/***** Methods *****/
var plugin = new Plugin("Ajax.org", main.consumes);
var emit = plugin.getEmitter();
var jobs, concurrentUploads, timer;
var loaded = false;
function load() {
if (loaded) return false;
loaded = true;
jobs = [];
concurrentUploads = options.concurrentUploads || 6;
}
function isSupported() {
return (window.FormData);
}
function upload(targetPath, batch, dialog, callback) {
forEach(batch.getRoots(), function(root, next) {
fs.exists(path.join(targetPath, root), function(exists) {
if (!exists)
return uploadFiles(root, false, next);
getAction(batch, root, function(action) {
switch (action) {
case "replace":
uploadFiles(root, true, next);
break;
case "no-replace":
batch.removeRoot(root);
return next();
case "stop":
return callback();
default:
throw new TypeError("Invalid replace action: " + action);
}
});
});
}, callback);
var toAll = false;
var action = "";
function getAction(batch, root, callback) {
if (toAll) return callback(action);
dialog(batch, targetPath, root, function(_action, _toAll) {
toAll = _toAll;
action = _action;
callback(action);
});
}
function uploadFiles(root, doOverwrite, callback) {
var files = batch.subTree(root);
var uploaded = 0;
files.forEach(function(file) {
var job = uploadFile(file, path.join(targetPath, file.fullPath));
file.job = job;
job.checkOverwrite = doOverwrite && !toAll;
job.on("changeState", function(e) {
if (e.state == STATE_DONE || e.state == STATE_ERROR) {
uploaded++;
if (uploaded == files.length)
emit("batchDone", { batch: batch });
}
});
});
callback();
}
}
function _createJob(file, fullPath) {
var job = new UploadJob(file, fullPath, plugin, options.workerPrefix);
job.vfs = vfs;
return job;
}
function uploadFile(file, fullPath) {
var job = _createJob(file, fullPath);
job.on("changeState", _checkAsync);
jobs.push(job);
emit("addJob", { job: job });
// async to give caller a chance to attach event listeners
_checkAsync();
return job;
}
function batchFromInput(inputEl, callback) {
return UploadBatch.fromInput(inputEl, callback);
}
function batchFromDrop(dropEvent, callback) {
return UploadBatch.fromDrop(dropEvent, callback);
}
function batchFromFileApi(entries, callback) {
return UploadBatch.fromFileApi(entries, callback);
}
function jobById(id) {
for (var i = 0; i < jobs.length; i++) {
var job = jobs[i];
if (job.id == id) {
return job;
}
}
}
function checkSync() {
var wip = [];
var done = [];
var candidates = [];
for (var i = 0; i < jobs.length; i++) {
var job = jobs[i];
switch (job.state) {
case STATE_DONE:
case STATE_ERROR:
done.push(job);
jobs.splice(i, 1);
i--;
break;
case STATE_RESUME:
candidates.push(job);
break;
case STATE_NEW:
candidates.unshift(job);
break;
case STATE_UPLOADING:
wip.push(job);
break;
default:
break;
}
}
if (done.length) {
setTimeout(function() {
done.forEach(function(job) {
emit("removeJob", { job: job });
});
}, 0);
}
for (var i = wip.length; i < concurrentUploads; i++) {
var job = candidates.pop();
if (!job)
break;
job._startUpload();
}
timer = null;
}
function _checkAsync() {
if (!timer)
timer = setTimeout(checkSync, 100);
}
function forEach(list, onEntry, callback) {
(function loop(i) {
if (i >= list.length)
return callback();
onEntry(list[i], function(err) {
if (err) return callback(err);
loop(i + 1);
});
})(0);
}
/***** Lifecycle *****/
plugin.on("load", function() {
load();
});
plugin.on("enable", function() {
});
plugin.on("disable", function() {
});
plugin.on("unload", function() {
loaded = false;
});
/***** Register and define API *****/
/**
* Object representing the upload of a single file.
* @class upload.Job
* @extends Object
*/
/**
* Aborts the upload of the file.
* @method cancel
*/
/**
* The full path to the file to be uploaded.
* @property {String} fullPath
* @readonly
*/
/**
* An instance of the HTML5 File API.
* @property {File} file
* @readonly
*/
/**
* The upload manager responsible for this job.
* @property {upload.manager} manager
* @readonly
*/
/**
* Retrieves the state of the upload of the file.
* The value will be one of the following values:
*
* * "new"
* * "uploading"
* * "paused"
* * "resume"
* * "done"
* * "error"
*
* @property {String} state
* @readonly
*/
/**
* A value between 0 and 1 specifying the portion of the file that has
* been uploaded.
* @property {Number} progress
* @readonly
*/
/**
* Fires when the state of the job changes.
* @event changeState
* @param {Object} e
* @param {String} e.state The state of the job. See also {@link #state}.
*/
/**
* Fires when the progress of the file upload changes.
* @event progress
* @param {Object} e
* @param {Number} e.progress The progress of the job. See also {@link upload.Job#property-progress}
*/
/**
* Object representing the upload of a set of files or folders.
* @class upload.Batch
* @extends Object
*/
/**
* The upload manager handles all file uploads. It keeps a list of all
* scheduled jobs and tracks their progress. The upload manager does not
* depend on any UI.
* @singleton
*/
plugin.freezePublicAPI({
/**
* Specifies whether the browser supports folder uploads
* @property {Boolean} SUPPORT_FOLDER_UPLOAD
* @readonly
*/
SUPPORT_FOLDER_UPLOAD: UploadBatch.SUPPORT_FOLDER_UPLOAD,
/**
* Array of all active upload jobs
* @property {upload.Job[]} jobs
* @readonly
*/
get jobs() { return jobs; },
_events: [
/**
* Fires when an upload is started. Passes a Job instance
* @event addJob
* @param {Object} e
* @param {upload.Job} e.job
*/
"addJob",
/**
* Fires when a job has finished uploading of failed to
* upload. Passes the Job object
* @event removeJob
* @param {Object} e
* @param {upload.Job} e.job
*/
"removeJob",
/**
* Fires when all files of an upload batch are uploaded
* Passes the batch object.
* @event batchDone
* @param {Object} e
* @param {upload.Batch} e.batch
*/
"batchDone"
],
/**
* Checks whether file upload API is supported
*
* @returns {Boolean} whether the browser supports file uploads
*/
isSupported: isSupported,
/**
* Uploads a batch of files to the server.
*
* @param {String} targetPath Path on the server where to store
* the files
* @param {upload.Batch} batch the batch of files to upload
* @param dialog {function()}
* @param {Function} callback The callback is called when all
* file uploads have been scheduled. It will not wait for the
* upload to complete
*/
upload: upload,
/**
* Upload a single file
*
* @param {File} file The file object from the file HTML5 API
* @param {String} fullPath Target path of the file
* @returns {upload.Job} the upload job to track the upload
*/
uploadFile: uploadFile,
/**
* Extract the batch of files to upload from a file upload
* input element.
*
* @param {HTMLInputElement} inputEl The file upload input
* element.
* @param {Function} callback Callback returns the Batch object
* @return {upload.Batch}
*/
batchFromInput: batchFromInput,
/**
* Extract the batch of files to upload from a HTML5 native
* drop event.
*
* @param {DragEvent} dropEvent native DOM drop event
* @param {Function} callback Callback returns the Batch object
* @return {upload.Batch}
*/
batchFromDrop: batchFromDrop,
/**
* Extract the batch of files to upload from a HTML5 FILE API
* directory entry.
*
* @param {Object} entries HTML5 file API entries
* @param {Function} callback Callback returns the Batch object
* @return {upload.Batch}
*/
batchFromFileApi: batchFromFileApi,
/**
* Find an upload job by its ID
*
* @param {Number} id The job id
* @return {upload.Job} the associated job
*/
jobById: jobById,
});
register(null, {
"upload.manager": plugin
});
}
});