kopia lustrzana https://github.com/c9/core
419 wiersze
14 KiB
JavaScript
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
|
||
|
});
|
||
|
}
|
||
|
});
|