New asyncRunner

This commit is contained in:
benweet 2013-04-20 01:14:20 +01:00
parent 51ba5930af
commit 2177785ca5
13 changed files with 1251 additions and 1176 deletions

View File

@ -64,7 +64,7 @@
<li class="dropdown-submenu"><a href="#"><i
class="icon-gdrive"></i> Google Drive</a>
<ul class="dropdown-menu">
<li><a href="#" class="action-download-gdrive">Import
<li><a href="#" class="action-sync-import-gdrive">Import
from Google Drive</a></li>
<li><a href="#" data-toggle="modal"
data-target="#modal-upload-gdrive" class="action-reset-input">Export
@ -73,7 +73,7 @@
<li class="dropdown-submenu"><a href="#"><i
class="icon-dropbox"></i> Dropbox</a>
<ul class="dropdown-menu">
<li><a class="action-download-dropbox" href="#">Import
<li><a class="action-sync-import-dropbox" href="#">Import
from Dropbox</a></li>
<li><a href="#" data-toggle="modal"
data-target="#modal-upload-dropbox" class="action-reset-input">Export
@ -86,12 +86,14 @@
<li class="dropdown-submenu"><a href="#"><i
class="icon-share"></i> Publish on</a>
<ul class="dropdown-menu">
<li><a href="#" class="action-publish-blogger"><i
class="icon-blogger"></i> Blogger</a></li>
<li><a href="#" class="action-publish-dropbox"><i
class="icon-dropbox"></i> Dropbox</a></li>
<li><a href="#" class="action-publish-blogger"><i
class="icon-blogger"></i> Blogger</a></li>
<li><a href="#" class="action-publish-dropbox"><i
class="icon-dropbox"></i> Dropbox</a></li>
<li><a href="#" class="action-publish-github"><i
class="icon-github"></i> GitHub</a></li>
<li><a href="#" class="action-publish-gdrive"><i
class="icon-gdrive"></i> Google Drive</a></li>
</ul></li>
<li><a href="#" data-toggle="modal"
data-target="#modal-manage-publish" class="action-reset-input"><i
@ -182,7 +184,7 @@
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal"
aria-hidden="true">&times;</button>
<h3>Export</h3>
<h3>Google Drive export</h3>
</div>
<div class="modal-body">
<p>This will upload the current document into your Google Drive
@ -193,7 +195,7 @@
<div class="modal-footer">
<a href="#" class="btn" data-dismiss="modal">Cancel</a> <a href="#"
data-dismiss="modal"
class="btn btn-primary action-upload-gdrive-root">OK</a>
class="btn btn-primary action-sync-export-gdrive">OK</a>
</div>
</div>
@ -201,7 +203,7 @@
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal"
aria-hidden="true">&times;</button>
<h3>Export</h3>
<h3>Dropbox export</h3>
</div>
<div class="modal-body">
<p>This will upload the current document to your Dropbox account
@ -210,7 +212,7 @@
</p>
<div class="input-prepend">
<span class="add-on"><i class="icon-dropbox"></i></span><input
id="upload-dropbox-path" type="text" class="span5"
id="input-sync-export-dropbox-path" type="text" class="span5"
placeholder="/path/to/My Document.md"></input>
</div>
<br /> <br /> <b class="muted">NOTE:</b>
@ -223,7 +225,7 @@
</div>
<div class="modal-footer">
<a href="#" class="btn" data-dismiss="modal">Cancel</a> <a href="#"
data-dismiss="modal" class="btn btn-primary action-upload-dropbox">OK</a>
data-dismiss="modal" class="btn btn-primary action-sync-export-dropbox">OK</a>
</div>
</div>
@ -246,16 +248,16 @@
<p>Add a synchronized location manually:</p>
<div class="input-prepend input-append">
<span class="add-on" title="Google Drive"><i
class="icon-gdrive"></i></span><input id="manual-gdrive-fileid"
class="icon-gdrive"></i></span><input id="input-sync-manual-gdrive-id"
type="text" class="span5" placeholder="Google Drive file ID"></input>
<a class="btn action-manual-gdrive" title="Add location"
<a class="btn action-sync-manual-gdrive" title="Add location"
data-dismiss="modal"><i class="icon-ok"></i></a>
</div>
<div class="input-prepend input-append">
<span class="add-on" title="Dropbox"><i class="icon-dropbox"></i></span><input
id="manual-dropbox-path" type="text" class="span5"
id="input-sync-manual-dropbox-path" type="text" class="span5"
placeholder="Dropbox file path"></input> <a
class="btn action-manual-dropbox" title="Add location"
class="btn action-sync-manual-dropbox" title="Add location"
data-dismiss="modal"><i class="icon-ok"></i></a>
</div>
<p class="muted"><b>NOTE:</b> Adding a synchronized location will
@ -271,7 +273,9 @@
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal"
aria-hidden="true">&times;</button>
<h3>Publish on <span class="publish-provider-name"></span></h3>
<h3>
Publish on <span class="publish-provider-name"></span>
</h3>
</div>
<div class="modal-body">
<div class="form-horizontal">
@ -314,12 +318,29 @@
</div>
</div>
<div class="control-group modal-publish-dropbox">
<label class="control-label" for="input-publish-dropbox-path">File path</label>
<label class="control-label" for="input-publish-dropbox-path">File
path</label>
<div class="controls">
<input type="text" id="input-publish-dropbox-path"
placeholder="/path/to/My Document.html">
</div>
</div>
<div class="control-group modal-publish-gdrive">
<label class="control-label" for="input-publish-gdrive-fileid">File
ID (optional)</label>
<div class="controls">
<input type="text" id="input-publish-gdrive-fileid"
placeholder="File ID">
</div>
</div>
<div class="control-group modal-publish-gdrive">
<div class="controls muted">
<b>NOTE:</b> If no file ID
is supplied, the file will be created into your Google Drive root
folder. You can move the file within Google Drive afterwards.
</div>
</div>
<div class="control-group">
<div class="control-label">Format</div>
<div class="controls">

View File

@ -1,121 +1,185 @@
/**
* Used to run asynchronous tasks sequentially (ajax mainly)
* An asynchronous task must be created with:
* - a required run() function that may call success(), error() or retry()
* - an optional onSuccess() function
* - an optional onError() function
* - an optional timeout field (default is 30000)
* Used to run asynchronous tasks sequentially (ajax mainly) An asynchronous
* task is composed of different callback types: onRun, onSuccess, onError
*/
define(["underscore"], function() {
var asyncTaskRunner = {};
define([ "underscore" ], function() {
var asyncRunner = {};
// Dependencies
var core = undefined;
var asyncTaskQueue = [];
var taskQueue = [];
var currentTask = undefined;
var currentTaskRunning = false;
var currentTaskStartTime = 0;
// Run the next task in the queue if any and no other is running
asyncRunner.createTask = function() {
var task = {};
task.finished = false;
task.timeout = ASYNC_TASK_DEFAULT_TIMEOUT;
task.retryCounter = 0;
/**
* onRun callbacks are called by chain(). These callbacks have to call
* chain() themselves to chain with next callback or to finish the task
* and call onSuccess callbacks.
*/
// Run callbacks
task.runCallbacks = [];
task.onRun = function(callback) {
task.runCallbacks.push(callback);
};
/**
* onSuccess callbacks are called when every onRun callbacks have
* succeed.
*/
task.successCallbacks = [];
task.onSuccess = function(callback) {
task.successCallbacks.push(callback);
};
/**
* onError callbacks are called when error() is called in a onRun
* callback.
*/
task.errorCallbacks = [];
task.onError = function(callback) {
task.errorCallbacks.push(callback);
};
/**
* chain() calls the next onRun callback or the onSuccess callbacks when
* finished. The optional callback parameter can be used to add a onRun
* callback during execution.
*/
task.chain = function(callback) {
if (task.finished === true) {
return;
}
// If first execution
if (task.queue === undefined) {
// Create a copy of the onRun callbacks
task.queue = task.runCallbacks.slice();
}
// If a callback is passed as a parameter
if(callback !== undefined) {
callback();
return;
}
// If all callbacks have been run
if (task.queue.length === 0) {
// Run the onSuccess callbacks
runSafe(task, task.successCallbacks);
return;
}
// Run the next callback
var runCallback = task.queue.shift();
runCallback();
};
/**
* error() calls the onError callbacks.
*/
task.error = function(error) {
if (task.finished === true) {
return;
}
error = error || "Unknown error";
runSafe(task, task.errorCallbacks, error);
// Exit the current call stack
throw error;
};
/**
* retry() can be called in an onRun callback to restart the task
*/
task.retry = function() {
if (task.finished === true) {
return;
}
task.queue = undefined;
if (task.retryCounter === 5) {
task.error(new Error("Maximum retry number reached"));
return;
}
// Implement an exponential backoff
var delay = Math.pow(2, task.retryCounter++) * 1000;
currentTaskStartTime = core.currentTime + delay;
currentTaskRunning = false;
asyncRunner.runTask();
};
return task;
};
// Run the next task in the queue if any and no other running
function runTask() {
// If there is a task currently running
if(currentTaskRunning === true) {
if (currentTaskRunning === true) {
// If the current task takes too long
var timeout = currentTask.timeout || ASYNC_TASK_DEFAULT_TIMEOUT;
if(currentTaskStartTime + timeout < core.currentTime) {
currentTask.error();
if (currentTaskStartTime + currentTask.timeout < core.currentTime) {
var errorMsg = "A timeout occurred.";
core.showError(errorMsg);
currentTask.error(new Error(errorMsg));
}
return;
}
if(currentTask === undefined) {
if (currentTask === undefined) {
// If no task in the queue
if(asyncTaskQueue.length === 0) {
if (taskQueue.length === 0) {
return;
}
// Dequeue an enqueued task
currentTask = asyncTaskQueue.shift();
// Dequeue an enqueued task
currentTask = taskQueue.shift();
currentTaskStartTime = core.currentTime;
core.showWorkingIndicator(true);
// Set task attributes and functions
currentTask.finished = false;
currentTask.retryCounter = 0;
currentTask.success = function() {
runSafe(this.onSuccess);
};
currentTask.error = function() {
runSafe(this.onError);
};
currentTask.retry = function() {
if(this.finished === true) {
return;
}
if(currentTask.retryCounter === 5) {
this.error();
return;
}
// Implement an exponential backoff
var delay = Math.pow(2, currentTask.retryCounter++) * 1000;
currentTaskStartTime = core.currentTime + delay;
currentTaskRunning = false;
asyncTaskRunner.runTask();
};
}
// Run the task
if(currentTaskStartTime <= core.currentTime) {
if (currentTaskStartTime <= core.currentTime) {
currentTaskRunning = true;
currentTask.run();
currentTask.chain();
}
}
asyncTaskRunner.runTask = function() {
asyncRunner.runTask = function() {
// Use defer to avoid stack overflow
_.defer(runTask);
};
function runSafe(func) {
if(currentTask === undefined || currentTask.finished === true) {
return;
}
function runSafe(task, callbacks, param) {
try {
if(func) {
func();
}
_.each(callbacks, function(callback) {
callback(param);
});
} finally {
currentTask.finished = true;
currentTask = undefined;
currentTaskRunning = false;
if(asyncTaskQueue.length === 0) {
core.showWorkingIndicator(false);
task.finished = true;
if (currentTask === task) {
currentTask = undefined;
currentTaskRunning = false;
}
else {
asyncTaskRunner.runTask();
if (taskQueue.length === 0) {
core.showWorkingIndicator(false);
} else {
asyncRunner.runTask();
}
}
}
// Add a task into the queue
asyncTaskRunner.addTask = function(asyncTask) {
asyncTaskQueue.push(asyncTask);
asyncTaskRunner.runTask();
// Add a task to the queue
asyncRunner.addTask = function(task) {
taskQueue.push(task);
asyncRunner.runTask();
};
// Change current task timeout
asyncTaskRunner.setCurrentTaskTimeout = function(timeout) {
if(currentTask !== undefined) {
asyncRunner.setCurrentTaskTimeout = function(timeout) {
if (currentTask !== undefined) {
currentTask.timeout = timeout;
}
};
asyncTaskRunner.init = function(coreModule) {
asyncRunner.init = function(coreModule) {
core = coreModule;
};
return asyncTaskRunner;
return asyncRunner;
});

View File

@ -13,9 +13,9 @@ define(["jquery", "google-helper"], function($, googleHelper) {
bloggerProvider.publish = function(publishAttributes, title, content, callback) {
googleHelper.uploadBlogger(publishAttributes.blogUrl,
publishAttributes.blogId, publishAttributes.postId, title, content,
function(blogId, postId) {
if(blogId === undefined || postId === undefined) {
callback(true);
function(error, blogId, postId) {
if(error) {
callback(error);
return;
}
publishAttributes.blogId = blogId;
@ -27,10 +27,7 @@ define(["jquery", "google-helper"], function($, googleHelper) {
bloggerProvider.newPublishAttributes = function(event) {
var publishAttributes = {};
publishAttributes.blogUrl = core.getInputValue($("#input-publish-blogger-url"), event);
var postId = $("#input-publish-blogger-postid").val();
if(postId) {
publishAttributes.postId = postId;
}
publishAttributes.postId = $("#input-publish-blogger-postid").val() || undefined;
if(event.isPropagationStopped()) {
return undefined;
}

View File

@ -9,14 +9,16 @@ var GDRIVE_DEFAULT_FILE_TITLE = "New Markdown document";
var CHECK_ONLINE_PERIOD = 60000;
var AJAX_TIMEOUT = 10000;
var ASYNC_TASK_DEFAULT_TIMEOUT = 30000;
var AUTH_POPUP_TIMEOUT = 90000;
var ASYNC_TASK_LONG_TIMEOUT = 90000;
var SYNC_PERIOD = 180000;
var USER_IDLE_THRESHOLD = 300000;
var SYNC_PROVIDER_GDRIVE = "sync.gdrive.";
var SYNC_PROVIDER_DROPBOX = "sync.dropbox.";
var PROVIDER_TYPE_PUBLISH_FLAG = 1;
var PROVIDER_TYPE_SYNC_FLAG = 1;
var PROVIDER_TYPE_PUBLISH_FLAG = 2;
var PROVIDER_BLOGGER = "blogger";
var PROVIDER_DROPBOX = "dropbox";
var PROVIDER_GDRIVE = "gdrive";
var PROVIDER_GITHUB = "github";
// Use by Google's client.js

View File

@ -4,7 +4,7 @@ define(
"bootstrap", "jgrowl", "layout", "Markdown.Editor", "config",
"underscore" ],
function($, fileManager, googleHelper, dropboxHelper, githubHelper,
synchronizer, publisher, asyncTaskRunner) {
synchronizer, publisher, asyncRunner) {
var core = {};
@ -102,7 +102,7 @@ define(
$(".modal input[type=text]:not([disabled])").val("");
};
// Used by asyncTaskRunner
// Used by asyncRunner
core.showWorkingIndicator = function(show) {
if (show === false) {
$(".working-indicator").addClass("hide");
@ -115,6 +115,9 @@ define(
// Used to show a notification message
core.showMessage = function(msg, iconClass, options) {
if(!msg) {
return;
}
options = options || {};
iconClass = iconClass || "icon-info-sign";
$.jGrowl("<i class='icon-white " + iconClass + "'></i> " + _.escape(msg), options);
@ -466,7 +469,7 @@ define(
function setupLocalStorage() {
if (localStorage["file.list"] === undefined) {
localStorage["file.list"] = ";";
localStorage["version"] = "v1";
localStorage["version"] = "v2";
}
}
@ -482,22 +485,53 @@ define(
localStorage.removeItem("sync.current");
localStorage.removeItem("file.counter");
var fileIndexList = localStorage["file.list"].split(";");
for ( var i = 1; i < fileIndexList.length - 1; i++) {
var fileIndex = fileIndexList[i];
var fileIndexList = _.compact(localStorage["file.list"].split(";"));
_.each(fileIndexList, function(fileIndex) {
localStorage[fileIndex + ".publish"] = ";";
var fileSyncIndexList = localStorage[fileIndex + ".sync"].split(";");
for ( var j = 1; j < fileSyncIndexList.length - 1; j++) {
var fileSyncIndex = fileSyncIndexList[j];
var fileSyncIndexList = _.compact(localStorage[fileIndex + ".sync"].split(";"));
_.each(fileSyncIndexList, function(fileSyncIndex) {
localStorage[fileSyncIndex + ".contentCRC"] = "0";
// We store title CRC only for Google Drive synchronization
if(localStorage[fileSyncIndex + ".etag"] !== undefined) {
localStorage[fileSyncIndex + ".titleCRC"] = "0";
}
}
}
});
});
version = "v1";
}
// from v1 to v2
if(version == "v1") {
var SYNC_PROVIDER_GDRIVE = "sync." + PROVIDER_GDRIVE + ".";
var SYNC_PROVIDER_DROPBOX = "sync." + PROVIDER_DROPBOX + ".";
var fileIndexList = _.compact(localStorage["file.list"].split(";"));
_.each(fileIndexList, function(fileIndex) {
var fileSyncIndexList = _.compact(localStorage[fileIndex + ".sync"].split(";"));
_.each(fileSyncIndexList, function(fileSyncIndex) {
var syncAttributes = {};
if (fileSyncIndex.indexOf(SYNC_PROVIDER_GDRIVE) === 0) {
syncAttributes.provider = PROVIDER_GDRIVE;
syncAttributes.id = fileSyncIndex.substring(SYNC_PROVIDER_GDRIVE.length);
syncAttributes.etag = localStorage[fileSyncIndex + ".etag"];
syncAttributes.contentCRC = localStorage[fileSyncIndex + ".contentCRC"];
syncAttributes.titleCRC = localStorage[fileSyncIndex + ".titleCRC"];
}
else if (fileSyncIndex.indexOf(SYNC_PROVIDER_DROPBOX) === 0) {
syncAttributes.provider = PROVIDER_DROPBOX;
syncAttributes.path = decodeURIComponent(fileSyncIndex.substring(SYNC_PROVIDER_DROPBOX.length));
syncAttributes.version = localStorage[fileSyncIndex + ".version"];
syncAttributes.contentCRC = localStorage[fileSyncIndex + ".contentCRC"];
}
localStorage[fileSyncIndex] = JSON.stringify(syncAttributes);
localStorage.remoteItem(fileSyncIndex + ".etag");
localStorage.remoteItem(fileSyncIndex + ".version");
localStorage.remoteItem(fileSyncIndex + ".contentCRC");
localStorage.remoteItem(fileSyncIndex + ".titleCRC");
});
});
version = "v2";
}
localStorage["version"] = version;
}
@ -586,13 +620,13 @@ define(
core.resetModalInputs();
});
// Init asyncTaskRunner
asyncTaskRunner.init(core);
// Init asyncRunner
asyncRunner.init(core);
// Init helpers
googleHelper.init(core, fileManager);
dropboxHelper.init(core, fileManager);
githubHelper.init(core, fileManager);
googleHelper.init(core);
dropboxHelper.init(core);
githubHelper.init(core);
// Init synchronizer
synchronizer.init(core, fileManager);
@ -614,7 +648,7 @@ define(
return;
}
synchronizer.sync();
asyncTaskRunner.runTask();
asyncRunner.runTask();
checkOnline();
}, 1000);
};

View File

@ -1,8 +1,7 @@
define(["jquery", "async-runner"], function($, asyncTaskRunner) {
define(["jquery", "async-runner"], function($, asyncRunner) {
// Dependencies
var core = undefined;
var fileManager = undefined;
var client = undefined;
var authenticated = false;
@ -10,219 +9,194 @@ define(["jquery", "async-runner"], function($, asyncTaskRunner) {
var dropboxHelper = {};
// Try to connect dropbox by downloading client.js
function connect(callback) {
callback = callback || core.doNothing;
if(core.isOffline === true) {
client = undefined;
core.showMessage("Operation not available in offline mode.");
callback(true);
return;
}
if (client !== undefined) {
callback();
return;
}
$.ajax({
url : "lib/dropbox.min.js",
dataType : "script", timeout : AJAX_TIMEOUT
}).done(function() {
client = new Dropbox.Client({
key: DROPBOX_APP_KEY,
secret: DROPBOX_APP_SECRET
function connect(task) {
task.onRun(function() {
if(core.isOffline === true) {
client = undefined;
core.showMessage("Operation not available in offline mode.");
task.error();
return;
}
if (client !== undefined) {
task.chain();
return;
}
$.ajax({
url : "lib/dropbox.min.js",
dataType : "script", timeout : AJAX_TIMEOUT
}).done(function() {
client = new Dropbox.Client({
key: DROPBOX_APP_KEY,
secret: DROPBOX_APP_SECRET
});
client.authDriver(new Dropbox.Drivers.Popup({
receiverUrl: BASE_URL + "dropbox-oauth-receiver.html",
rememberUser: true
}));
task.chain();
}).fail(function() {
core.setOffline();
task.error(new Error("Network timeout"));
});
client.authDriver(new Dropbox.Drivers.Popup({
receiverUrl: BASE_URL + "dropbox-oauth-receiver.html",
rememberUser: true
}));
callback();
}).fail(function() {
core.setOffline();
callback(true);
});
}
// Try to authenticate with Oauth
function authenticate(callback, immediate) {
callback = callback || core.doNothing;
if (immediate === undefined) {
immediate = true;
}
connect(function(error) {
if (error) {
callback(error);
return;
}
function authenticate(task) {
task.onRun(function() {
if (authenticated === true) {
callback();
task.chain();
return;
}
if (immediate === false) {
core.showMessage("Please make sure the Dropbox authorization popup is not blocked by your browser.");
asyncTaskRunner.setCurrentTaskTimeout(AUTH_POPUP_TIMEOUT);
var immediate = true;
function localAuthenticate() {
if (immediate === false) {
core.showMessage("Please make sure the Dropbox authorization popup is not blocked by your browser.");
// If not immediate we add time for user to enter his credentials
task.timeout = ASYNC_TASK_LONG_TIMEOUT;
}
client.reset();
client.authenticate({interactive: !immediate}, function(error, client) {
// Success
if (client.authState === Dropbox.Client.DONE) {
authenticated = true;
task.chain();
return;
}
// If immediate did not work retry without immediate flag
if (immediate === true) {
immediate = false;
task.chain(localAuthenticate);
return;
}
// Error
var errorMsg = "Access to Dropbox account is not authorized.";
core.showError(errorMsg);
task.error(new Error(errorMsg));
});
}
client.authenticate({interactive: !immediate}, function(error, client) {
if (client.authState === Dropbox.Client.DONE) {
callback();
return;
}
// If immediate did not work retry without immediate flag
if (client !== undefined && immediate === true) {
authenticate(callback, false);
return;
}
// Notify error
callback(true);
});
task.chain(localAuthenticate);
});
}
dropboxHelper.upload = function(path, content, callback) {
callback = callback || core.doNothing;
var syncIndex = undefined;
var asyncTask = {};
asyncTask.run = function() {
authenticate(function(error) {
if (error) {
handleError(error, asyncTask, callback);
var result = undefined;
var task = asyncRunner.createTask();
connect(task);
authenticate(task);
task.onRun(function() {
client.writeFile(path, content, function(error, stat) {
if (!error) {
result = stat;
task.chain();
return;
}
client.writeFile(path, content, function(error, stat) {
if (!error) {
syncIndex = SYNC_PROVIDER_DROPBOX + encodeURIComponent(stat.path.toLowerCase());
localStorage[syncIndex + ".version"] = stat.versionTag;
asyncTask.success();
return;
}
// Handle error
if(error.status === Dropbox.ApiError.INVALID_PARAM) {
error = 'Could not upload document into path "' + path + '".';
}
handleError(error, asyncTask, callback);
});
// Handle error
if(error.status === Dropbox.ApiError.INVALID_PARAM) {
error = 'Could not upload document into path "' + path + '".';
}
handleError(error, task, callback);
});
};
asyncTask.onSuccess = function() {
callback(undefined, syncIndex);
};
asyncTask.onError = function() {
callback(true);
};
asyncTaskRunner.addTask(asyncTask);
});
task.onSuccess(function() {
callback(undefined, result);
});
task.onError(function(error) {
callback(error);
});
asyncRunner.addTask(task);
};
dropboxHelper.checkUpdates = function(lastChangeId, callback) {
callback = callback || core.doNothing;
var changes = [];
var newChangeId = lastChangeId || 0;
var asyncTask = {};
asyncTask.run = function() {
function retrievePageOfChanges(changeId) {
if(asyncTask.finished === true) {
return;
}
authenticate(function(error) {
var task = asyncRunner.createTask();
connect(task);
authenticate(task);
task.onRun(function() {
function retrievePageOfChanges() {
client.pullChanges(newChangeId, function(error, pullChanges) {
if (error) {
handleError(error, asyncTask, callback);
handleError(error, task, callback);
return;
}
client.pullChanges(changeId, function(error, pullChanges) {
if (error) {
handleError(error, asyncTask, callback);
return;
}
// Retrieve success
newChangeId = pullChanges.cursor();
if(pullChanges.changes !== undefined) {
for(var i=0; i<pullChanges.changes.length; i++) {
var item = pullChanges.changes[i];
var version = localStorage[SYNC_PROVIDER_DROPBOX
+ encodeURIComponent(item.path.toLowerCase()) + ".version"];
if(version && (item.wasRemoved || item.stat.versionTag != version)) {
changes.push(item);
}
// Retrieve success
newChangeId = pullChanges.cursor();
if(pullChanges.changes !== undefined) {
for(var i=0; i<pullChanges.changes.length; i++) {
var item = pullChanges.changes[i];
var version = localStorage[SYNC_PROVIDER_DROPBOX
+ encodeURIComponent(item.path.toLowerCase()) + ".version"];
if(version && (item.wasRemoved || item.stat.versionTag != version)) {
changes.push(item);
}
}
if (pullChanges.shouldPullAgain) {
retrievePageOfChanges(newChangeId);
} else {
asyncTask.success();
}
});
}
if (pullChanges.shouldPullAgain) {
task.chain(retrievePageOfChanges);
} else {
task.chain();
}
});
}
retrievePageOfChanges(newChangeId);
};
asyncTask.onSuccess = function() {
task.chain(retrievePageOfChanges);
});
task.onSuccess(function() {
callback(undefined, changes, newChangeId);
};
asyncTask.onError = function() {
callback(true);
};
asyncTaskRunner.addTask(asyncTask);
});
task.onError(function(error) {
callback(error);
});
asyncRunner.addTask(task);
};
dropboxHelper.downloadMetadata = function(paths, callback) {
callback = callback || core.doNothing;
result = result || [];
var path = paths.pop();
var asyncTask = {};
asyncTask.run = function() {
var result = [];
var task = asyncRunner.createTask();
connect(task);
authenticate(task);
task.onRun(function() {
function recursiveDownloadMetadata() {
if(asyncTask.finished === true) {
return;
}
if(paths.length === 0) {
asyncTask.success();
task.chain();
return;
}
authenticate(function(error) {
if (error) {
handleError(error, asyncTask, callback);
var path = paths.pop();
client.stat(path, function(error, stat) {
if(stat) {
result.push(stat);
task.chain(recursiveDownloadMetadata);
return;
}
client.stat(path, function(error, stat) {
if(stat) {
result.push(stat);
recursiveDownloadMetadata();
return;
}
handleError(error, asyncTask, callback);
});
handleError(error, task, callback);
});
}
recursiveDownloadMetadata();
};
asyncTask.onSuccess = function() {
task.chain(recursiveDownloadMetadata);
});
task.onSuccess(function() {
callback(undefined, result);
};
asyncTask.onError = function() {
callback(true);
};
asyncTaskRunner.addTask(asyncTask);
});
task.onError(function(error) {
callback(error);
});
asyncRunner.addTask(task);
};
dropboxHelper.downloadContent = function(objects, callback, result) {
dropboxHelper.downloadContent = function(objects, callback) {
callback = callback || core.doNothing;
result = result || [];
var asyncTask = {};
asyncTask.run = function() {
var result = [];
var task = asyncRunner.createTask();
connect(task);
authenticate(task);
task.onRun(function() {
function recursiveDownloadContent() {
if(asyncTask.finished === true) {
return;
}
if(objects.length === 0) {
asyncTask.success();
task.chain();
return;
}
var object = objects.pop();
result.push(object);
var file = undefined;
@ -235,44 +209,31 @@ define(["jquery", "async-runner"], function($, asyncTaskRunner) {
file = object.stat;
}
if(!file) {
recursiveDownloadContent();
task.chain(recursiveDownloadContent);
return;
}
authenticate(function(error) {
if (error) {
handleError(error, asyncTask, callback);
client.readFile(file.path, function(error, data) {
if(data) {
file.content = data;
recursiveDownloadContent();
return;
}
client.readFile(file.path, function(error, data) {
if(data) {
file.content = data;
recursiveDownloadContent();
return;
}
handleError(error, asyncTask, callback);
});
handleError(error, task, callback);
});
}
recursiveDownloadContent();
};
asyncTask.onSuccess = function() {
task.chain(recursiveDownloadContent);
});
task.onSuccess(function() {
callback(undefined, result);
};
asyncTask.onError = function() {
callback(true);
};
asyncTaskRunner.addTask(asyncTask);
});
task.onError(function(error) {
callback(error);
});
asyncRunner.addTask(task);
};
function handleError(error, asyncTask, callback) {
var errorMsg = undefined;
asyncTask.onError = function() {
if (errorMsg !== undefined) {
core.showError(errorMsg);
}
callback(errorMsg);
};
function handleError(error, task, callback) {
var errorMsg = true;
if (error) {
console.error(error);
// Try to analyze the error
@ -281,7 +242,20 @@ define(["jquery", "async-runner"], function($, asyncTaskRunner) {
} else if (error.status === Dropbox.ApiError.INVALID_TOKEN
|| error.status === Dropbox.ApiError.OAUTH_ERROR) {
authenticated = false;
errorMsg = "Access to Dropbox is not authorized.";
task.retry();
return;
} else if(error.status === Dropbox.ApiError.INVALID_PARAM && error.responseText
.indexOf("oauth_nonce") !== -1) {
// A bug I guess...
_.each(_.keys(localStorage), function(key) {
// We have to remove the Oauth cache from the localStorage
if(key.indexOf("dropbox-auth") === 0) {
localStorage.removeItem(key);
}
});
authenticated = false;
task.retry();
return;
} else if (error.status === Dropbox.ApiError.NETWORK_ERROR) {
client = undefined;
authenticated = false;
@ -290,31 +264,27 @@ define(["jquery", "async-runner"], function($, asyncTaskRunner) {
errorMsg = "Dropbox error ("
+ error.status + ").";
}
core.showError(errorMsg);
}
asyncTask.error();
task.error(new Error(errorMsg));
}
var pickerLoaded = false;
function loadPicker(callback) {
if (pickerLoaded === true) {
callback();
return;
}
connect(function(error) {
if (error) {
pickerLoaded = false;
callback(error);
function loadPicker(task) {
task.onRun(function() {
if (pickerLoaded === true) {
task.chain();
return;
}
$.ajax({
url : "https://www.dropbox.com/static/api/1/dropbox.js",
dataType : "script", timeout : AJAX_TIMEOUT
}).done(function() {
pickerLoaded = true;
callback();
task.chain();
}).fail(function() {
callback(true);
core.setOffline();
task.error();
});
});
}
@ -322,78 +292,40 @@ define(["jquery", "async-runner"], function($, asyncTaskRunner) {
dropboxHelper.picker = function(callback) {
callback = callback || core.doNothing;
var paths = [];
var asyncTask = {};
asyncTask.run = function() {
loadPicker(function(error) {
if (error) {
handleError(error, asyncTask, callback);
return;
var task = asyncRunner.createTask();
// Add some time for user to choose his files
task.timeout = ASYNC_TASK_LONG_TIMEOUT;
connect(task);
loadPicker(task);
task.onRun(function() {
var options = {};
options.multiselect = true;
options.linkType = "direct";
options.success = function(files) {
for(var i=0; i<files.length; i++) {
var path = files[i].link;
path = path.replace(/.*\/view\/[^\/]*/, "");
paths.push(decodeURI(path));
}
var options = {};
options.multiselect = true;
options.linkType = "direct";
options.success = function(files) {
for(var i=0; i<files.length; i++) {
var path = files[i].link;
path = path.replace(/.*\/view\/[^\/]*/, "");
paths.push(decodeURI(path));
}
asyncTask.success();
};
options.cancel = function() {
asyncTask.error();
};
Dropbox.choose(options);
core.showMessage("Please make sure the Dropbox chooser popup is not blocked by your browser.");
});
};
asyncTask.onSuccess = function() {
callback(undefined, paths);
};
asyncTask.onError = function() {
callback(true);
};
asyncTaskRunner.addTask(asyncTask);
};
dropboxHelper.importFiles = function(paths) {
dropboxHelper.downloadMetadata(paths, function(error, result) {
if(error) {
return;
}
dropboxHelper.downloadContent(result, function(error, result) {
if(error) {
return;
}
for(var i=0; i<result.length; i++) {
var file = result[i];
syncIndex = SYNC_PROVIDER_DROPBOX + encodeURIComponent(file.path.toLowerCase());
localStorage[syncIndex + ".version"] = file.versionTag;
var contentCRC = core.crc32(file.content);
localStorage[syncIndex + ".contentCRC"] = contentCRC;
var fileIndex = fileManager.createFile(file.name, file.content, [syncIndex]);
fileManager.selectFile(fileIndex);
core.showMessage('"' + file.name + '" imported successfully from Dropbox.');
}
});
task.chain();
};
options.cancel = function() {
task.chain();
};
Dropbox.choose(options);
core.showMessage("Please make sure the Dropbox chooser popup is not blocked by your browser.");
});
task.onSuccess(function() {
callback(undefined, paths);
});
task.onError(function(error) {
callback(error);
});
asyncRunner.addTask(task);
};
dropboxHelper.init = function(coreModule, fileManagerModule) {
dropboxHelper.init = function(coreModule) {
core = coreModule;
fileManager = fileManagerModule;
};
dropboxHelper.checkPath = function(path) {
if(!path.match(/^[^\\<>:"\|?\*]+$/)) {
core.showError('"' + path + '" contains invalid characters.');
return undefined;
}
if(path.indexOf("/") !== 0) {
return "/" + path;
}
return path;
};
return dropboxHelper;

View File

@ -2,16 +2,116 @@ define(["jquery", "dropbox-helper"], function($, dropboxHelper) {
// Dependencies
var core = undefined;
var fileManager = undefined;
var dropboxProvider = {
providerType: PROVIDER_TYPE_PUBLISH_FLAG,
providerType: PROVIDER_TYPE_SYNC_FLAG | PROVIDER_TYPE_PUBLISH_FLAG,
providerId: PROVIDER_DROPBOX,
providerName: "Dropbox",
defaultPublishFormat: "template"
};
function checkPath(path) {
if(path === undefined) {
return undefined;
}
if(!path.match(/^[^\\<>:"\|?\*]+$/)) {
core.showError('"' + path + '" contains invalid characters.');
return undefined;
}
if(path.indexOf("/") !== 0) {
return "/" + path;
}
return path;
}
function createSyncAttributes(path, versionTag, content) {
var syncAttributes = {};
syncAttributes.provider = PROVIDER_DROPBOX;
syncAttributes.path = path;
syncAttributes.version = versionTag;
syncAttributes.contentCRC = core.crc32(content);
var syncIndex = "sync." + PROVIDER_DROPBOX + "." + encodeURIComponent(file.path.toLowerCase());
localStorage[syncIndex] = JSON.stringify(syncAttributes);
return syncIndex;
}
function importFilesFromPaths(paths) {
dropboxHelper.downloadMetadata(paths, function(error, result) {
if(error) {
return;
}
dropboxHelper.downloadContent(result, function(error, result) {
if(error) {
return;
}
_.each(result, function(file) {
var syncIndex = createSyncAttributes(file.path, file.versionTag, file.content);
var fileIndex = fileManager.createFile(file.name, file.content, [syncIndex]);
fileManager.selectFile(fileIndex);
core.showMessage('"' + file.name + '" imported successfully from Dropbox.');
});
});
});
}
dropboxProvider.importFiles = function() {
dropboxHelper.picker(function(error, paths) {
if(error) {
return;
}
var importPaths = [];
_.each(paths, function(path) {
var syncIndex = "sync." + PROVIDER_DROPBOX + "." + encodeURIComponent(path.toLowerCase());
var fileIndex = fileManager.getFileIndexFromSync(syncIndex);
if(fileIndex !== undefined) {
var title = localStorage[fileIndex + ".title"];
core.showError('"' + title + '" was already imported');
return;
}
importPaths.push(path);
});
dropboxHelper.importFiles(importPaths);
});
};
function exportFileToPath(path, title, content, callback) {
path = dropboxHelper.checkPath(path);
if(path === undefined) {
callback(true);
return;
}
// Check that file is not synchronized with an other one
var syncIndex = "sync." + PROVIDER_DROPBOX + "." + encodeURIComponent(path.toLowerCase());
var fileIndex = fileManager.getFileIndexFromSync(syncIndex);
if(fileIndex !== undefined) {
var existingTitle = localStorage[fileIndex + ".title"];
core.showError('File path is already synchronized with "' + existingTitle + '"');
callback(true);
return;
}
dropboxHelper.upload(path, content, function(error, result) {
if (error) {
callback(error);
return;
}
syncIndex = createSyncAttributes(result.path, result.versionTag, content);
callback(undefined, syncIndex);
});
}
dropboxProvider.exportFile = function(event, title, content, callback) {
var path = core.getInputValue($("#input-sync-export-dropbox-path"), event);
exportFileToPath(path);
};
dropboxProvider.exportManual = function(event, title, content, callback) {
var path = core.getInputValue($("#input-sync-manual-dropbox-path"), event);
exportFileToPath(path);
};
dropboxProvider.publish = function(publishAttributes, title, content, callback) {
var path = dropboxHelper.checkPath(publishAttributes.path);
var path = checkPath(publishAttributes.path);
if(path === undefined) {
callback(true);
return;
@ -28,8 +128,9 @@ define(["jquery", "dropbox-helper"], function($, dropboxHelper) {
return publishAttributes;
};
dropboxProvider.init = function(coreModule) {
dropboxProvider.init = function(coreModule, fileManagerModule) {
core = coreModule;
fileManager = fileManagerModule;
};
return dropboxProvider;

View File

@ -40,7 +40,7 @@ define(["jquery", "google-helper", "dropbox-helper", "github-helper", "synchroni
// Update the file titles
fileManager.updateFileTitles();
refreshManageSync();
synchronizer.refreshManageSync();
publisher.notifyPublish();
// Recreate the editor
@ -186,14 +186,11 @@ define(["jquery", "google-helper", "dropbox-helper", "github-helper", "synchroni
localStorage[fileIndex + ".sync"] = localStorage[fileIndex + ".sync"].replace(";"
+ syncIndex + ";", ";");
if(fileManager.isCurrentFileIndex(fileIndex)) {
refreshManageSync();
synchronizer.refreshManageSync();
}
}
// Remove ETAG, version, CRCs (if any)
localStorage.removeItem(syncIndex + ".etag");
localStorage.removeItem(syncIndex + ".version");
localStorage.removeItem(syncIndex + ".contentCRC");
localStorage.removeItem(syncIndex + ".titleCRC");
// Remove sync attributes
localStorage.removeItem(syncIndex);
};
// Get the fileIndex associated to a syncIndex
@ -215,7 +212,7 @@ define(["jquery", "google-helper", "dropbox-helper", "github-helper", "synchroni
publisher.notifyPublish();
}
}
// Remove publish object
// Remove publish attributes
localStorage.removeItem(publishIndex);
};
@ -228,148 +225,6 @@ define(["jquery", "google-helper", "dropbox-helper", "github-helper", "synchroni
}).value();
};
function uploadGdrive(fileId, folderId) {
var fileIndex = fileManager.getCurrentFileIndex();
var content = localStorage[fileIndex + ".content"];
var title = localStorage[fileIndex + ".title"];
googleHelper.upload(fileId, folderId, title, content, function(syncIndex) {
if (syncIndex === undefined) {
return;
}
var contentCRC = core.crc32(content);
localStorage[syncIndex + ".contentCRC"] = contentCRC;
var titleCRC = core.crc32(title);
localStorage[syncIndex + ".titleCRC"] = titleCRC;
localStorage[fileIndex + ".sync"] += syncIndex + ";";
refreshManageSync();
fileManager.updateFileTitles();
core.showMessage('"' + title
+ '" will now be synchronized on Google Drive.');
});
}
function manualGdrive(fileId) {
if(!fileId) {
return;
}
// Check that file is not synchronized with an other one
var syncIndex = SYNC_PROVIDER_GDRIVE + fileId;
var fileIndex = fileManager.getFileIndexFromSync(syncIndex);
if(fileIndex !== undefined) {
var title = localStorage[fileIndex + ".title"];
core.showError('File ID is already synchronized with "' + title + '"');
return;
}
uploadGdrive(fileId);
}
function importGdrive(ids) {
if(ids === undefined) {
return;
}
var importIds = [];
for(var i=0; i<ids.length; i++) {
var fileId = ids[i];
var syncIndex = SYNC_PROVIDER_GDRIVE + fileId;
var fileIndex = fileManager.getFileIndexFromSync(syncIndex);
if(fileIndex !== undefined) {
var title = localStorage[fileIndex + ".title"];
core.showError('"' + title + '" was already imported');
continue;
}
importIds.push(fileId);
}
googleHelper.importFiles(importIds);
}
function manualDropbox(path) {
if(!path) {
return;
}
path = dropboxHelper.checkPath(path);
if(path === undefined) {
return;
}
// Check that file is not synchronized with an other one
var syncIndex = SYNC_PROVIDER_DROPBOX + encodeURIComponent(path.toLowerCase());
var fileIndex = fileManager.getFileIndexFromSync(syncIndex);
if(fileIndex !== undefined) {
var title = localStorage[fileIndex + ".title"];
core.showError('Path "' + path + '" is already synchronized with "' + title + '"');
return;
}
var fileIndex = fileManager.getCurrentFileIndex();
var content = localStorage[fileIndex + ".content"];
var title = localStorage[fileIndex + ".title"];
dropboxHelper.upload(path, content, function(error, syncIndex) {
if (error) {
return;
}
var contentCRC = core.crc32(content);
localStorage[syncIndex + ".contentCRC"] = contentCRC;
localStorage[fileIndex + ".sync"] += syncIndex + ";";
refreshManageSync();
fileManager.updateFileTitles();
core.showMessage('"' + title
+ '" will now be synchronized on Dropbox.');
});
}
function importDropbox(error, paths) {
if(error) {
return;
}
var importPaths = [];
for(var i=0; i<paths.length; i++) {
var filePath = paths[i];
var syncIndex = SYNC_PROVIDER_DROPBOX + encodeURIComponent(filePath.toLowerCase());
var fileIndex = fileManager.getFileIndexFromSync(syncIndex);
if(fileIndex !== undefined) {
var title = localStorage[fileIndex + ".title"];
core.showError('"' + title + '" was already imported');
continue;
}
importPaths.push(filePath);
}
dropboxHelper.importFiles(importPaths);
}
function refreshManageSync() {
var fileIndex = fileManager.getCurrentFileIndex();
var syncIndexList = _.compact(localStorage[fileIndex + ".sync"].split(";"));
$(".msg-no-sync, .msg-sync-list").addClass("hide");
$("#manage-sync-list .input-append").remove();
if (syncIndexList.length > 0) {
$(".msg-sync-list").removeClass("hide");
} else {
$(".msg-no-sync").removeClass("hide");
}
_.each(syncIndexList, function(syncIndex) {
var line = $("<div>").addClass("input-prepend input-append");
if (syncIndex.indexOf(SYNC_PROVIDER_GDRIVE) === 0) {
line.append($("<span>").addClass("add-on").prop("title", "Google Drive").html(
'<i class="icon-gdrive"></i>'));
line.append($("<input>").prop("type", "text").prop(
"disabled", true).addClass("span5").val(
syncIndex.substring(SYNC_PROVIDER_GDRIVE.length)));
}
else if (syncIndex.indexOf(SYNC_PROVIDER_DROPBOX) === 0) {
line.append($("<span>").addClass("add-on").prop("title", "Dropbox").html(
'<i class="icon-dropbox"></i>'));
line.append($("<input>").prop("type", "text").prop(
"disabled", true).addClass("span5").val(
decodeURIComponent(syncIndex.substring(SYNC_PROVIDER_DROPBOX.length))));
}
line.append($("<a>").addClass("btn").html(
'<i class="icon-trash"></i>').prop("title",
"Remove this location").click(function() {
fileManager.removeSync(syncIndex);
fileManager.updateFileTitles();
}));
$("#manage-sync-list").append(line);
});
}
fileManager.init = function(coreModule) {
core = coreModule;
@ -388,18 +243,29 @@ define(["jquery", "google-helper", "dropbox-helper", "github-helper", "synchroni
$(this).hide();
$("#file-title-input").show().focus();
});
$("#file-title-input").blur(function() {
var title = $.trim($(this).val());
function applyTitle(input) {
var title = $.trim(input.val());
var fileIndexTitle = fileManager.getCurrentFileIndex() + ".title";
if (title) {
var fileIndexTitle = fileManager.getCurrentFileIndex() + ".title";
if (title != localStorage[fileIndexTitle]) {
localStorage[fileIndexTitle] = title;
fileManager.updateFileTitles();
fileManager.saveFile();
}
}
$(this).hide();
input.hide().val(localStorage[fileIndexTitle]);
$("#file-title").show();
}
$("#file-title-input").blur(function() {
applyTitle($(this));
}).keyup(function(e) {
if (e.keyCode == 13) {
applyTitle($(this));
}
if (e.keyCode == 27) {
$(this).val("");
applyTitle($(this));
}
});
$(".action-download-md").click(
function() {
@ -422,38 +288,6 @@ define(["jquery", "google-helper", "dropbox-helper", "github-helper", "synchroni
+ core.encodeBase64(content);
window.open(uriContent, 'file');
});
// Synchronize actions
$(".action-upload-gdrive-root").click(function() {
uploadGdrive();
});
$(".action-upload-gdrive-select").click(function() {
// This action is not available because picker does not support
// folder selection
googleHelper.picker(function(ids) {
if(ids !== undefined && ids.length !== 0) {
uploadGdrive(undefined, ids[0]);
}
}, true);
});
$(".action-download-gdrive").click(function() {
googleHelper.picker(importGdrive);
});
$(".action-manual-gdrive").click(function(event) {
var fileId = core.getInputValue($("#manual-gdrive-fileid"), event);
manualGdrive(fileId);
});
$(".action-download-dropbox").click(function() {
dropboxHelper.picker(importDropbox);
});
$(".action-upload-dropbox").click(function(event) {
var path = core.getInputValue($("#upload-dropbox-path"), event);
manualDropbox(path);
});
$(".action-manual-dropbox").click(function(event) {
var path = core.getInputValue($("#manual-dropbox-path"), event);
manualDropbox(path);
});
};
return fileManager;

152
js/gdrive-provider.js Normal file
View File

@ -0,0 +1,152 @@
define(["jquery", "google-helper", "underscore"], function($, googleHelper) {
// Dependencies
var core = undefined;
var fileManager = undefined;
var gdriveProvider = {
providerType: PROVIDER_TYPE_SYNC_FLAG | PROVIDER_TYPE_PUBLISH_FLAG,
providerId: PROVIDER_GDRIVE,
providerName: "Gdrive",
defaultPublishFormat: "template"
};
function createSyncAttributes(id, etag, content, title) {
var syncAttributes = {};
syncAttributes.provider = PROVIDER_GDRIVE;
syncAttributes.id = id;
syncAttributes.etag = etag;
syncAttributes.contentCRC = core.crc32(content);
syncAttributes.titleCRC = core.crc32(title);
var syncIndex = "sync." + PROVIDER_GDRIVE + "." + file.id;
localStorage[syncIndex] = JSON.stringify(syncAttributes);
return syncIndex;
}
function importFilesFromIds(ids) {
googleHelper.downloadMetadata(ids, function(error, result) {
if(error) {
return;
}
googleHelper.downloadContent(result, function(error, result) {
if(error) {
return;
}
_.each(result, function(file) {
var syncIndex = createSyncAttributes(file.id, file.etag, file.content, file.title);
var fileIndex = fileManager.createFile(file.title, file.content, [syncIndex]);
fileManager.selectFile(fileIndex);
core.showMessage('"' + file.title + '" imported successfully from Google Drive.');
});
});
});
};
gdriveProvider.importFiles = function() {
googleHelper.picker(function(error, ids) {
if(ids === undefined) {
return;
}
var importIds = [];
_.each(ids, function(id) {
var syncIndex = "sync." + PROVIDER_GDRIVE + "." + id;
var fileIndex = fileManager.getFileIndexFromSync(syncIndex);
if(fileIndex !== undefined) {
var title = localStorage[fileIndex + ".title"];
core.showError('"' + title + '" was already imported');
return;
}
importIds.push(id);
});
importFilesFromIds(importIds);
});
};
gdriveProvider.exportFile = function(event, title, content, callback) {
googleHelper.upload(undefined, undefined, title, content, function(error, result) {
if (error) {
callback(error);
return;
}
var syncIndex = createSyncAttributes(result.id, result.etag, content, title);
callback(undefined, syncIndex);
});
};
gdriveProvider.exportManual = function(event, title, content, callback) {
var id = core.getInputValue($("#input-sync-manual-gdrive-id"), event);
if(!id) {
return;
}
// Check that file is not synchronized with an other one
var syncIndex = "sync." + PROVIDER_GDRIVE + "." + id;
var fileIndex = fileManager.getFileIndexFromSync(syncIndex);
if(fileIndex !== undefined) {
var existingTitle = localStorage[fileIndex + ".title"];
core.showError('File ID is already synchronized with "' + existingTitle + '"');
callback(true);
return;
}
googleHelper.upload(id, undefined, title, content, function(error, result) {
if (error) {
callback(error);
return;
}
var syncIndex = createSyncAttributes(result.id, result.etag, content, title);
callback(undefined, syncIndex);
});
};
gdriveProvider.publish = function(publishAttributes, title, content, callback) {
googleHelper.upload(publishAttributes.fileId, undefined, title, content, callback);
};
gdriveProvider.newPublishAttributes = function(event) {
var publishAttributes = {};
publishAttributes.fileId = $("#input-publish-gdrive-fileid").val() || undefined;
if(event.isPropagationStopped()) {
return undefined;
}
return publishAttributes;
};
gdriveProvider.init = function(coreModule, fileManagerModule) {
core = coreModule;
fileManager = fileManagerModule;
var state = localStorage["sync.gdrive.state"];
if(state === undefined) {
return;
}
localStorage.removeItem("sync.gdrive.state");
state = JSON.parse(state);
if (state.action == "create") {
googleHelper.upload(undefined, state.folderId, GDRIVE_DEFAULT_FILE_TITLE,
"", function(error, file) {
if(error) {
return;
}
var syncIndex = createSyncAttributes(file.id, file.etag, file.content, file.title);
var fileIndex = fileManager.createFile(file.title, file.content, [syncIndex]);
fileManager.selectFile(fileIndex);
core.showMessage('"' + file.title + '" created successfully on Google Drive.');
});
}
else if (state.action == "open") {
var importIds = [];
_.each(state.ids, function(id) {
var syncIndex = "sync." + PROVIDER_GDRIVE + "." + id;
var fileIndex = fileManager.getFileIndexFromSync(syncIndex);
if(fileIndex !== undefined) {
fileManager.selectFile(fileIndex);
}
else {
importIds.push(id);
}
});
importFilesFromIds(importIds);
}
};
return gdriveProvider;
});

View File

@ -1,4 +1,4 @@
define(["jquery", "async-runner"], function($, asyncTaskRunner) {
define(["jquery", "async-runner"], function($, asyncRunner) {
// Dependencies
var core = undefined;
@ -9,18 +9,17 @@ define(["jquery", "async-runner"], function($, asyncTaskRunner) {
var githubHelper = {};
// Try to connect github by downloading js file
function connect(callback) {
function connect(task) {
callback = callback || core.doNothing;
var asyncTask = {};
asyncTask.run = function() {
task.onRun(function() {
if(core.isOffline === true) {
connected = false;
core.showMessage("Operation not available in offline mode.");
asyncTask.error();
task.error();
return;
}
if (connected === true) {
asyncTask.success();
task.chain();
return;
}
$.ajax({
@ -28,159 +27,138 @@ define(["jquery", "async-runner"], function($, asyncTaskRunner) {
dataType : "script", timeout : AJAX_TIMEOUT
}).done(function() {
connected = true;
asyncTask.success();
task.chain();
}).fail(function() {
asyncTask.error();
core.setOffline();
task.error(new Error("Network timeout"));
});
};
asyncTask.onSuccess = function() {
callback();
};
asyncTask.onError = function() {
core.setOffline();
callback();
};
asyncTaskRunner.addTask(asyncTask);
});
}
// Try to authenticate with Oauth
function authenticate(callback, immediate) {
callback = callback || core.doNothing;
connect(function() {
if (connected === false) {
callback();
function authenticate(task) {
var authWindow = undefined;
var intervalId = undefined;
task.onRun(function() {
if (github !== undefined) {
task.chain();
return;
}
var intervalId = undefined;
var authWindow = undefined;
var token = localStorage["githubToken"];
var errorMsg = "Access to GitHub is not authorized.";
var asyncTask = {};
asyncTask.run = function() {
if (github !== undefined) {
asyncTask.success();
return;
}
if (token !== undefined) {
github = new Github({
token: token,
auth: "oauth"
});
asyncTask.success();
return;
}
if(immediate === true) {
core.showError();
asyncTask.error();
return;
}
// We add time for user to enter his credentials
asyncTask.timeout = AUTH_POPUP_TIMEOUT;
core.showMessage("Please make sure the Github authorization popup is not blocked by your browser.");
if(token !== undefined) {
github = new Github({
token: token,
auth: "oauth"
});
task.chain();
return;
}
core.showMessage("Please make sure the Github authorization popup is not blocked by your browser.");
// We add time for user to enter his credentials
task.timeout = ASYNC_TASK_LONG_TIMEOUT;
var code = undefined;
function getCode() {
localStorage.removeItem("githubCode");
authWindow = core.popupWindow(
'github-oauth-client.html?client_id=' + GITHUB_CLIENT_ID,
'stackedit-github-oauth', 960, 600);
authWindow.focus();
intervalId = setInterval(function() {
var code = localStorage["githubCode"];
if(authWindow.closed === true || code !== undefined) {
localStorage.removeItem("githubCode");
if(authWindow.closed === true) {
clearInterval(intervalId);
authWindow = undefined;
intervalId = undefined;
code = localStorage["githubCode"];
if(code === undefined) {
asyncTask.error();
task.error();
return;
}
$.getJSON(GATEKEEPER_URL + "authenticate/" + code, function(data) {
if(data.token !== undefined) {
localStorage["githubToken"] = data.token;
asyncTask.success();
}
else {
asyncTask.error();
}
});
localStorage.removeItem("githubCode");
task.chain(getToken);
}
}, 500);
};
asyncTask.onSuccess = function() {
if(intervalId !== undefined) {
clearInterval(intervalId);
}
if (github !== undefined) {
callback();
return;
}
authenticate(callback, true);
};
asyncTask.onError = function() {
if(intervalId !== undefined) {
clearInterval(intervalId);
}
if(authWindow !== undefined) {
authWindow.close();
}
core.showError(errorMsg);
callback();
};
asyncTaskRunner.addTask(asyncTask);
});
}
function getToken() {
$.getJSON(GATEKEEPER_URL + "authenticate/" + code, function(data) {
if(data.token !== undefined) {
token = data.token;
localStorage["githubToken"] = token;
github = new Github({
token: token,
auth: "oauth"
});
task.chain();
}
else {
task.error();
}
});
}
task.chain(getCode);
});
task.onError(function() {
if(intervalId !== undefined) {
clearInterval(intervalId);
}
if(authWindow !== undefined) {
authWindow.close();
}
});
}
githubHelper.upload = function(reponame, branch, path, content, commitMsg, callback) {
callback = callback || core.doNothing;
authenticate(function() {
if (github === undefined) {
callback("error");
return;
}
var error = undefined;
var asyncTask = {};
asyncTask.run = function() {
var task = asyncRunner.createTask();
connect(task);
authenticate(task);
task.onRun(function() {
var userLogin = undefined;
function getUserLogin() {
var user = github.getUser();
user.show(undefined, function(err, result) {
if(err) {
error = err.error;
asyncTask.error();
task.error(err);
return;
}
var repo = github.getRepo(result.login, reponame);
repo.write(branch, path, content, commitMsg, function(err) {
if(err) {
error = err.error;
asyncTask.error();
return;
}
asyncTask.success();
});
userLogin = result.login;
task.chain(write);
});
};
asyncTask.onSuccess = function() {
callback(error);
};
asyncTask.onError = function() {
if(error !== undefined) {
console.error(error);
}
var errorMsg = "Could not publish on GitHub.";
if(error === 401 || error === 403) {
}
function write() {
var repo = github.getRepo(userLogin, reponame);
repo.write(branch, path, content, commitMsg, function(err) {
if(err) {
task.error(err);
return;
}
task.chain();
});
}
task.chain(getUserLogin);
});
task.onSuccess(function() {
callback();
});
task.onError(function(err) {
var errorMsg = "Could not publish on GitHub.";
if(err !== undefined) {
console.error(err);
if(err.error === 401 || err.error === 403) {
github = undefined;
// Token must be renewed
localStorage.removeItem("githubToken");
errorMsg = "Access to GitHub is not authorized.";
}
else if(error === 0) {
else if(err.error === 0) {
connected = false;
github = undefined;
core.setOffline();
}
core.showError(errorMsg);
callback(error);
};
asyncTaskRunner.addTask(asyncTask);
}
core.showError(errorMsg);
callback(errorMsg);
});
asyncRunner.addTask(task);
};
githubHelper.init = function(coreModule) {

View File

@ -1,8 +1,7 @@
define(["jquery", "async-runner"], function($, asyncTaskRunner) {
define(["jquery", "async-runner"], function($, asyncRunner) {
// Dependencies
var core = undefined;
var fileManager = undefined;
var connected = false;
var authenticated = false;
@ -10,254 +9,224 @@ define(["jquery", "async-runner"], function($, asyncTaskRunner) {
var googleHelper = {};
// Try to connect Gdrive by downloading client.js
function connect(callback) {
callback = callback || core.doNothing;
var asyncTask = {};
asyncTask.run = function() {
function connect(task) {
task.onRun(function() {
if(core.isOffline === true) {
connected = false;
core.showMessage("Operation not available in offline mode.");
asyncTask.error();
task.error();
return;
}
if (connected === true) {
asyncTask.success();
task.chain();
return;
}
delayedFunction = function() {
asyncTask.success();
connected = true;
task.chain();
};
$.ajax({
url : "https://apis.google.com/js/client.js?onload=runDelayedFunction",
dataType : "script", timeout : AJAX_TIMEOUT
}).fail(function() {
asyncTask.error();
core.setOffline();
task.error(new Error("Network timeout"));
});
};
asyncTask.onSuccess = function() {
connected = true;
callback();
};
asyncTask.onError = function() {
core.setOffline();
callback();
};
asyncTaskRunner.addTask(asyncTask);
});
}
// Try to authenticate with Oauth
function authenticate(callback, immediate) {
callback = callback || core.doNothing;
if (immediate === undefined) {
immediate = true;
}
connect(function() {
if (connected === false) {
callback();
function authenticate(task) {
task.onRun(function() {
if (authenticated === true) {
task.chain();
return;
}
var asyncTask = {};
// If not immediate we add time for user to enter his credentials
if (immediate === false) {
asyncTask.timeout = AUTH_POPUP_TIMEOUT;
}
asyncTask.run = function() {
if (authenticated === true) {
asyncTask.success();
return;
}
var immediate = true;
function localAuthenticate() {
if (immediate === false) {
core.showMessage("Please make sure the Google authorization popup is not blocked by your browser.");
// If not immediate we add time for user to enter his credentials
task.timeout = ASYNC_TASK_LONG_TIMEOUT;
}
gapi.auth.authorize({ 'client_id' : GOOGLE_CLIENT_ID,
'scope' : GOOGLE_SCOPES, 'immediate' : immediate }, function(
authResult) {
gapi.client.load('drive', 'v2', function() {
if (!authResult || authResult.error) {
asyncTask.error();
// If immediate did not work retry without immediate flag
if (connected === true && immediate === true) {
immediate = false;
task.chain(localAuthenticate);
return;
}
// Error
var errorMsg = "Access to Google account is not authorized.";
core.showError(errorMsg);
task.error(new Error(errorMsg));
return;
}
// Success
authenticated = true;
asyncTask.success();
task.chain();
});
});
};
asyncTask.onSuccess = function() {
callback();
};
asyncTask.onError = function() {
// If immediate did not work retry without immediate flag
if (connected === true && immediate === true) {
authenticate(callback, false);
return;
}
callback();
};
asyncTaskRunner.addTask(asyncTask);
}
task.chain(localAuthenticate);
});
}
googleHelper.upload = function(fileId, parentId, title, content, callback) {
callback = callback || core.doNothing;
authenticate(function() {
if (connected === false) {
callback();
return;
var result = undefined;
var task = asyncRunner.createTask();
connect(task);
authenticate(task);
task.onRun(function() {
var boundary = '-------314159265358979323846';
var delimiter = "\r\n--" + boundary + "\r\n";
var close_delim = "\r\n--" + boundary + "--";
var contentType = 'text/x-markdown';
var metadata = { title : title, mimeType : contentType };
if (parentId !== undefined) {
// Specify the directory
metadata.parents = [ { kind : 'drive#fileLink',
id : parentId } ];
}
var path = '/upload/drive/v2/files';
var method = 'POST';
var etag = undefined;
if (fileId !== undefined) {
// If it's an update
path += "/" + fileId;
method = 'PUT';
etag = localStorage[SYNC_PROVIDER_GDRIVE
+ fileId + ".etag"];
}
var headers = { 'Content-Type' : 'multipart/mixed; boundary="'
+ boundary + '"', };
if(etag !== undefined) {
headers["If-Match"] = etag;
}
var fileSyncIndex = undefined;
var asyncTask = {};
asyncTask.run = function() {
var boundary = '-------314159265358979323846';
var delimiter = "\r\n--" + boundary + "\r\n";
var close_delim = "\r\n--" + boundary + "--";
var base64Data = core.encodeBase64(content);
var multipartRequestBody = delimiter
+ 'Content-Type: application/json\r\n\r\n'
+ JSON.stringify(metadata) + delimiter + 'Content-Type: '
+ contentType + '\r\n'
+ 'Content-Transfer-Encoding: base64\r\n' + '\r\n'
+ base64Data + close_delim;
var contentType = 'text/x-markdown';
var metadata = { title : title, mimeType : contentType };
if (parentId !== undefined) {
// Specify the directory
metadata.parents = [ { kind : 'drive#fileLink',
id : parentId } ];
var request = gapi.client
.request({
'path' : path,
'method' : method,
'params' : { 'uploadType' : 'multipart', },
'headers' : headers,
'body' : multipartRequestBody, });
request.execute(function(response) {
if (response && response.id) {
// Upload success
result = response;
task.chain();
return;
}
var path = '/upload/drive/v2/files';
var method = 'POST';
var etag = undefined;
if (fileId !== undefined) {
// If it's an update
path += "/" + fileId;
method = 'PUT';
etag = localStorage[SYNC_PROVIDER_GDRIVE
+ fileId + ".etag"];
}
var headers = { 'Content-Type' : 'multipart/mixed; boundary="'
+ boundary + '"', };
if(etag !== undefined) {
headers["If-Match"] = etag;
}
var base64Data = core.encodeBase64(content);
var multipartRequestBody = delimiter
+ 'Content-Type: application/json\r\n\r\n'
+ JSON.stringify(metadata) + delimiter + 'Content-Type: '
+ contentType + '\r\n'
+ 'Content-Transfer-Encoding: base64\r\n' + '\r\n'
+ base64Data + close_delim;
var request = gapi.client
.request({
'path' : path,
'method' : method,
'params' : { 'uploadType' : 'multipart', },
'headers' : headers,
'body' : multipartRequestBody, });
request.execute(function(response) {
if (response && response.id) {
// Upload success
fileSyncIndex = SYNC_PROVIDER_GDRIVE + response.id;
localStorage[fileSyncIndex + ".etag"] = response.etag;
asyncTask.success();
return;
var error = response.error;
// Handle error
if(error !== undefined && fileId !== undefined) {
if(error.code === 404) {
error = 'File ID "' + fileId + '" does not exist on Google Drive.';
}
var error = response.error;
// Handle error
if(error !== undefined && fileId !== undefined) {
if(error.code === 404) {
error = 'File ID "' + fileId + '" does not exist on Google Drive.';
}
else if(error.code === 412) {
// We may have missed a file update
localStorage.removeItem("sync.gdrive.lastChangeId");
error = 'Conflict on file ID "' + fileId + '". Please restart the synchronization.';
}
else if(error.code === 412) {
// We may have missed a file update
localStorage.removeItem("sync.gdrive.lastChangeId");
error = 'Conflict on file ID "' + fileId + '". Please restart the synchronization.';
}
handleError(error, asyncTask, callback);
});
};
asyncTask.onSuccess = function() {
callback(fileSyncIndex);
};
asyncTask.onError = function() {
callback();
};
asyncTaskRunner.addTask(asyncTask);
}
handleError(error, task, callback);
});
});
task.onSuccess(function() {
callback(undefined, result);
});
task.onError(function(error) {
callback(error);
});
asyncRunner.addTask(task);
};
googleHelper.checkUpdates = function(lastChangeId, callback) {
callback = callback || core.doNothing;
authenticate(function() {
if (connected === false) {
callback();
return;
}
var changes = [];
var newChangeId = lastChangeId || 0;
var task = asyncRunner.createTask();
connect(task);
authenticate(task);
task.onRun(function() {
var nextPageToken = undefined;
function retrievePageOfChanges() {
var request = undefined;
if(nextPageToken === undefined) {
request = gapi.client.drive.changes
.list({ 'startChangeId' : newChangeId + 1 });
}
else {
request = gapi.client.drive.changes
.list({ 'pageToken' : nextPageToken });
}
var changes = [];
var newChangeId = lastChangeId || 0;
function retrievePageOfChanges(request) {
var nextPageToken = undefined;
var asyncTask = {};
asyncTask.run = function() {
request.execute(function(response) {
if (response && response.largestChangeId) {
// Retrieve success
newChangeId = response.largestChangeId;
nextPageToken = response.nextPageToken;
if (response.items !== undefined) {
for ( var i = 0; i < response.items.length; i++) {
var item = response.items[i];
var etag = localStorage[SYNC_PROVIDER_GDRIVE
+ item.fileId + ".etag"];
if (etag
&& (item.deleted === true || item.file.etag != etag)) {
changes.push(item);
}
}
}
asyncTask.success();
return;
}
request.execute(function(response) {
if (!response || !response.largestChangeId) {
// Handle error
handleError(response.error, asyncTask, callback);
});
};
asyncTask.onSuccess = function() {
if (nextPageToken !== undefined) {
request = gapi.client.drive.changes
.list({ 'pageToken' : nextPageToken });
retrievePageOfChanges(request);
} else {
callback(changes, newChangeId);
handleError(response.error, task, callback);
return;
}
};
asyncTask.onError = function() {
callback();
};
asyncTaskRunner.addTask(asyncTask);
// Retrieve success
newChangeId = response.largestChangeId;
nextPageToken = response.nextPageToken;
if (response.items !== undefined) {
for ( var i = 0; i < response.items.length; i++) {
var item = response.items[i];
var etag = localStorage[SYNC_PROVIDER_GDRIVE
+ item.fileId + ".etag"];
if (etag
&& (item.deleted === true || item.file.etag != etag)) {
changes.push(item);
}
}
}
if (nextPageToken !== undefined) {
task.chain(retrievePageOfChanges);
}
else {
task.chain();
}
});
}
var initialRequest = gapi.client.drive.changes
.list({ 'startChangeId' : newChangeId + 1 });
retrievePageOfChanges(initialRequest);
task.chain(retrievePageOfChanges);
});
task.onSuccess(function() {
callback(undefined, changes, newChangeId);
});
task.onError(function(error) {
callback(error);
});
asyncRunner.addTask(task);
};
googleHelper.downloadMetadata = function(ids, callback, result) {
googleHelper.downloadMetadata = function(ids, callback) {
callback = callback || core.doNothing;
result = result || [];
if(ids.length === 0) {
callback(result);
return;
}
authenticate(function() {
if (connected === false) {
callback();
return;
}
var id = ids.pop();
var asyncTask = {};
asyncTask.run = function() {
var result = [];
var task = asyncRunner.createTask();
connect(task);
authenticate(task);
task.onRun(function() {
function recursiveDownloadMetadata() {
if(ids.length === 0) {
task.chain();
return;
}
var id = ids.pop();
var token = gapi.auth.getToken();
var headers = {
Authorization : token ? "Bearer " + token.access_token: null
@ -269,7 +238,7 @@ define(["jquery", "async-runner"], function($, asyncTaskRunner) {
timeout : AJAX_TIMEOUT
}).done(function(data, textStatus, jqXHR) {
result.push(data);
asyncTask.success();
task.chain(recursiveDownloadMetadata);
}).fail(function(jqXHR) {
var error = {
code: jqXHR.status,
@ -279,51 +248,49 @@ define(["jquery", "async-runner"], function($, asyncTaskRunner) {
if(error.code === 404) {
error = 'File ID "' + id + '" does not exist on Google Drive.';
}
handleError(error, asyncTask, callback);
handleError(error, task, callback);
});
};
asyncTask.onSuccess = function() {
googleHelper.downloadMetadata(ids, callback, result);
};
asyncTask.onError = function() {
callback();
};
asyncTaskRunner.addTask(asyncTask);
}
task.chain(recursiveDownloadMetadata);
});
task.onSuccess(function() {
callback(undefined, result);
});
task.onError(function(error) {
callback(error);
});
asyncRunner.addTask(task);
};
googleHelper.downloadContent = function(objects, callback, result) {
googleHelper.downloadContent = function(objects, callback) {
callback = callback || core.doNothing;
result = result || [];
if(objects.length === 0) {
callback(result);
return;
}
var object = objects.pop();
result.push(object);
var file = undefined;
// object may be a file
if(object.kind == "drive#file") {
file = object;
}
// object may be a change
else if(object.kind == "drive#change") {
file = object.file;
}
if(!file) {
this.downloadContent(objects, callback, result);
return;
}
authenticate(function() {
if (connected === false) {
callback();
return;
}
var asyncTask = {};
asyncTask.run = function() {
var result = [];
var task = asyncRunner.createTask();
// Add some time for user to choose his files
task.timeout = ASYNC_TASK_LONG_TIMEOUT;
connect(task);
authenticate(task);
task.onRun(function() {
function recursiveDownloadContent() {
if(objects.length === 0) {
task.chain();
return;
}
var object = objects.pop();
result.push(object);
var file = undefined;
// object may be a file
if(object.kind == "drive#file") {
file = object;
}
// object may be a change
else if(object.kind == "drive#change") {
file = object.file;
}
if(!file) {
task.chain(recursiveDownloadContent);
return;
}
var token = gapi.auth.getToken();
var headers = {
Authorization : token ? "Bearer " + token.access_token: null
@ -335,35 +302,29 @@ define(["jquery", "async-runner"], function($, asyncTaskRunner) {
timeout : AJAX_TIMEOUT
}).done(function(data, textStatus, jqXHR) {
file.content = data;
asyncTask.success();
task.chain(recursiveDownloadContent);
}).fail(function(jqXHR) {
var error = {
code: jqXHR.status,
message: jqXHR.statusText
};
// Handle error
handleError(error, asyncTask, callback);
handleError(error, task, callback);
});
};
asyncTask.onSuccess = function() {
googleHelper.downloadContent(objects, callback, result);
};
asyncTask.onError = function() {
callback();
};
asyncTaskRunner.addTask(asyncTask);
}
task.chain(recursiveDownloadContent);
});
task.onSuccess(function() {
callback(undefined, result);
});
task.onError(function(error) {
callback(error);
});
asyncRunner.addTask(task);
};
function handleError(error, asyncTask, callback, serviceName) {
serviceName = serviceName || "Google Drive";
function handleError(error, task, callback) {
var errorMsg = undefined;
asyncTask.onError = function() {
if (errorMsg !== undefined) {
core.showError(errorMsg);
}
callback();
};
if (error) {
console.error(error);
// Try to analyze the error
@ -371,69 +332,55 @@ define(["jquery", "async-runner"], function($, asyncTaskRunner) {
errorMsg = error;
}
else if (error.code >= 500 && error.code < 600) {
errorMsg = serviceName + " is not accessible.";
// Retry as described in Google's best practices
asyncTask.retry();
task.retry();
return;
} else if (error.code === 401 || error.code === 403) {
authenticated = false;
errorMsg = "Access to " + serviceName + " is not authorized.";
task.retry();
return;
} else if (error.code <= 0) {
connected = false;
authenticated = false;
core.setOffline();
} else {
errorMsg = serviceName + " error (" + error.code + ": "
errorMsg = "Google error (" + error.code + ": "
+ error.message + ").";
}
core.showError(errorMsg);
}
asyncTask.error();
task.error(new Error(errorMsg));
}
var pickerLoaded = false;
function loadPicker(callback) {
connect(function() {
if (connected === false) {
pickerLoaded = false;
callback();
function loadPicker(task) {
task.onRun(function() {
if (pickerLoaded === true) {
task.chain();
return;
}
var asyncTask = {};
asyncTask.run = function() {
if (pickerLoaded === true) {
asyncTask.success();
return;
}
$.ajax({
url : "//www.google.com/jsapi",
data : {key: GOOGLE_KEY},
dataType : "script", timeout : AJAX_TIMEOUT
}).done(function() {
asyncTask.success();
}).fail(function() {
asyncTask.error();
});
};
asyncTask.onSuccess = function() {
google.load('picker', '1', {callback: callback});
}
$.ajax({
url : "//www.google.com/jsapi",
data : {key: GOOGLE_KEY},
dataType : "script", timeout : AJAX_TIMEOUT
}).done(function() {
google.load('picker', '1', {callback: task.chain});
pickerLoaded = true;
};
asyncTask.onError = function() {
}).fail(function() {
core.setOffline();
callback();
};
asyncTaskRunner.addTask(asyncTask);
task.error(new Error("Network timeout"));
});
});
}
googleHelper.picker = function(callback) {
callback = callback || core.doNothing;
loadPicker(function() {
if (pickerLoaded === false) {
callback();
return;
}
var ids = [];
var picker = undefined;
var task = asyncRunner.createTask();
connect(task);
loadPicker(task);
task.onRun(function() {
var view = new google.picker.View(google.picker.ViewId.DOCS);
view.setMimeTypes("text/x-markdown,text/plain");
var pickerBuilder = new google.picker.PickerBuilder();
@ -449,182 +396,122 @@ define(["jquery", "async-runner"], function($, asyncTaskRunner) {
pickerBuilder.setCallback(function(data) {
if (data.action == google.picker.Action.PICKED ||
data.action == google.picker.Action.CANCEL) {
var ids = [];
if(data.action == google.picker.Action.PICKED) {
for(var i=0; i<data.docs.length; i++) {
ids.push(data.docs[i].id);
}
}
$(".modal-backdrop, .picker").remove();
callback(ids);
task.chain();
}
});
var picker = pickerBuilder.build();
picker = pickerBuilder.build();
$("body").append($("<div>").addClass("modal-backdrop").click(function() {
picker.setVisible(false);
$(".modal-backdrop, .picker").remove();
callback();
task.chain();
}));
picker.setVisible(true);
});
};
googleHelper.importFiles = function(ids) {
googleHelper.downloadMetadata(ids, function(result) {
if(result === undefined) {
return;
}
googleHelper.downloadContent(result, function(result) {
if(result === undefined) {
return;
}
for(var i=0; i<result.length; i++) {
var file = result[i];
fileSyncIndex = SYNC_PROVIDER_GDRIVE + file.id;
localStorage[fileSyncIndex + ".etag"] = file.etag;
var contentCRC = core.crc32(file.content);
localStorage[fileSyncIndex + ".contentCRC"] = contentCRC;
var titleCRC = core.crc32(file.title);
localStorage[fileSyncIndex + ".titleCRC"] = titleCRC;
var fileIndex = fileManager.createFile(file.title, file.content, [fileSyncIndex]);
fileManager.selectFile(fileIndex);
core.showMessage('"' + file.title + '" imported successfully from Google Drive.');
}
});
task.onSuccess(function() {
callback(undefined, ids);
});
task.onError(function(error) {
if(picker !== undefined) {
picker.setVisible(false);
$(".modal-backdrop, .picker").remove();
task.chain();
}
callback(error);
});
asyncRunner.addTask(task);
};
googleHelper.uploadBlogger = function(blogUrl, blogId, postId, title, content, callback) {
authenticate(function() {
if (connected === false) {
callback();
return;
}
var asyncTask = {};
asyncTask.run = function() {
var token = gapi.auth.getToken();
var headers = {
Authorization : token ? "Bearer " + token.access_token: null
var task = asyncRunner.createTask();
connect(task);
authenticate(task);
task.onRun(function() {
var token = gapi.auth.getToken();
var headers = {
Authorization : token ? "Bearer " + token.access_token: null
};
function publish() {
var url = "https://www.googleapis.com/blogger/v3/blogs/" + blogId + "/posts/";
var data = {
kind: "blogger#post",
blog: { id: blogId },
title: title,
content: content
};
function getBlogId(localCallback) {
$.ajax({
url : "https://www.googleapis.com/blogger/v3/blogs/byurl",
data: { url: blogUrl },
headers : headers,
dataType : "json",
timeout : AJAX_TIMEOUT
}).done(function(blog, textStatus, jqXHR) {
blogId = blog.id;
localCallback();
}).fail(function(jqXHR) {
var error = {
code: jqXHR.status,
message: jqXHR.statusText
};
// Handle error
if(error.code === 404) {
error = 'Blog "' + blogUrl + '" not found on Blogger.';
}
handleError(error, asyncTask, callback, "Blogger");
});
var type = "POST";
// If it's an update
if(postId !== undefined) {
url += postId;
data.id = postId;
type = "PUT";
}
function publish() {
var url = "https://www.googleapis.com/blogger/v3/blogs/" + blogId + "/posts/";
var data = {
kind: "blogger#post",
blog: { id: blogId },
title: title,
content: content
$.ajax({
url : url,
data: JSON.stringify(data),
headers : headers,
type: type,
contentType: "application/json",
dataType : "json",
timeout : AJAX_TIMEOUT
}).done(function(post, textStatus, jqXHR) {
postId = post.id;
task.chain();
}).fail(function(jqXHR) {
var error = {
code: jqXHR.status,
message: jqXHR.statusText
};
var type = "POST";
// If it's an update
if(postId !== undefined) {
url += postId;
data.id = postId;
type = "PUT";
// Handle error
if(error.code === 404 && postId !== undefined) {
error = 'Post ' + postId + ' not found on Blogger.';
}
$.ajax({
url : url,
data: JSON.stringify(data),
headers : headers,
type: type,
contentType: "application/json",
dataType : "json",
timeout : AJAX_TIMEOUT
}).done(function(post, textStatus, jqXHR) {
postId = post.id;
asyncTask.success();
}).fail(function(jqXHR) {
var error = {
code: jqXHR.status,
message: jqXHR.statusText
};
// Handle error
if(error.code === 404 && postId !== undefined) {
error = 'Post ' + postId + ' not found on Blogger.';
}
handleError(error, asyncTask, callback, "Blogger");
});
handleError(error, task, callback);
});
}
function getBlogId() {
if(blogId !== undefined) {
task.chain(publish);
}
if(blogId === undefined) {
getBlogId(publish);
}
else {
publish();
}
};
asyncTask.onSuccess = function() {
callback(blogId, postId);
};
asyncTask.onError = function() {
callback();
};
asyncTaskRunner.addTask(asyncTask);
$.ajax({
url : "https://www.googleapis.com/blogger/v3/blogs/byurl",
data: { url: blogUrl },
headers : headers,
dataType : "json",
timeout : AJAX_TIMEOUT
}).done(function(blog, textStatus, jqXHR) {
blogId = blog.id;
task.chain(publish);
}).fail(function(jqXHR) {
var error = {
code: jqXHR.status,
message: jqXHR.statusText
};
// Handle error
if(error.code === 404) {
error = 'Blog "' + blogUrl + '" not found on Blogger.';
}
handleError(error, task, callback);
});
}
task.chain(getBlogId);
});
task.onSuccess = function() {
callback(undefined, blogId, postId);
};
task.onError = function(error) {
callback(error);
};
asyncRunner.addTask(task);
};
googleHelper.init = function(coreModule, fileManagerModule) {
googleHelper.init = function(coreModule) {
core = coreModule;
fileManager = fileManagerModule;
var state = localStorage["sync.gdrive.state"];
if(state === undefined) {
return;
}
localStorage.removeItem("sync.gdrive.state");
state = JSON.parse(state);
if (state.action == "create") {
googleHelper.upload(undefined, state.folderId, GDRIVE_DEFAULT_FILE_TITLE,
"", function(fileSyncIndex) {
if(fileSyncIndex === undefined) {
return;
}
var contentCRC = core.crc32("");
localStorage[fileSyncIndex + ".contentCRC"] = contentCRC;
var titleCRC = core.crc32(GDRIVE_DEFAULT_FILE_TITLE);
localStorage[fileSyncIndex + ".titleCRC"] = titleCRC;
var fileIndex = fileManager.createFile(GDRIVE_DEFAULT_FILE_TITLE, "", [fileSyncIndex]);
fileManager.selectFile(fileIndex);
core.showMessage('"' + GDRIVE_DEFAULT_FILE_TITLE + '" created successfully on Google Drive.');
});
}
else if (state.action == "open") {
var ids = [];
for(var i=0; i<state.ids.length; i++) {
var id = state.ids[i];
var fileSyncIndex = SYNC_PROVIDER_GDRIVE + id;
var fileIndex = fileManager.getFileIndexFromSync(fileSyncIndex);
if(fileIndex !== undefined) {
fileManager.selectFile(fileIndex);
} else {
ids.push(id);
}
}
googleHelper.importFiles(ids);
}
};
return googleHelper;

View File

@ -1,4 +1,4 @@
define(["jquery", "github-provider", "blogger-provider", "dropbox-provider", "underscore"], function($) {
define(["jquery", "github-provider", "blogger-provider", "dropbox-provider", "gdrive-provider", "underscore"], function($) {
// Dependencies
var core = undefined;
@ -183,8 +183,7 @@ define(["jquery", "github-provider", "blogger-provider", "dropbox-provider", "un
$(".msg-no-publish").removeClass("hide");
}
_.each(publishIndexList, function(publishIndex) {
var serializedObject = localStorage[publishIndex];
var publishAttributes = JSON.parse(serializedObject);
var publishAttributes = JSON.parse(localStorage[publishIndex]);
var publishDesc = JSON.stringify(publishAttributes).replace(/{|}|"/g, "");
lineElement = $(_.template(lineTemplate, {
provider: providerMap[publishAttributes.provider],
@ -203,7 +202,7 @@ define(["jquery", "github-provider", "blogger-provider", "dropbox-provider", "un
// Init each provider
_.each(providerMap, function(provider) {
provider.init(core);
provider.init(core, fileManager);
// Publish provider button
$(".action-publish-" + provider.providerId).click(function() {
initNewLocation(provider);

View File

@ -1,10 +1,16 @@
define(["jquery", "google-helper", "dropbox-helper"], function($, googleHelper, dropboxHelper) {
define(["jquery", "google-helper", "dropbox-helper", "dropbox-provider", "gdrive-provider"], function($, googleHelper, dropboxHelper) {
var synchronizer = {};
// Dependencies
var core = undefined;
var fileManager = undefined;
// Create a map with providerName: providerObject
var providerMap = _.chain(arguments)
.map(function(argument) {
return argument && argument.providerType & PROVIDER_TYPE_SYNC_FLAG && [argument.providerId, argument];
}).compact().object().value();
// Used to know the providers we are connected to
synchronizer.useGoogleDrive = false;
synchronizer.useDropbox = false;
@ -52,15 +58,12 @@ define(["jquery", "google-helper", "dropbox-helper"], function($, googleHelper,
}
// Dequeue a synchronized location
var fileSyncIndex = uploadFileSyncIndexList.pop();
if(!fileSyncIndex) {
locationUp(callback);
return;
}
// Skip if CRC has not changed
var syncIndex = uploadFileSyncIndexList.pop();
var syncAttributes = JSON.parse(localStorage[fileSyncIndex]);
=
var syncContentCRC = localStorage[fileSyncIndex + ".contentCRC"];
var syncTitleCRC = localStorage[fileSyncIndex + ".titleCRC"];
// Skip if CRC has not changed
if(uploadContentCRC == syncContentCRC && (syncTitleCRC === undefined || uploadTitleCRC == syncTitleCRC)) {
locationUp(callback);
return;
@ -74,17 +77,15 @@ define(["jquery", "google-helper", "dropbox-helper"], function($, googleHelper,
// Try to find the provider
if (fileSyncIndex.indexOf(SYNC_PROVIDER_GDRIVE) === 0) {
var id = fileSyncIndex.substring(SYNC_PROVIDER_GDRIVE.length);
googleHelper.upload(id, undefined, uploadTitle, uploadContent, function(result) {
if (result !== undefined) {
localStorage[fileSyncIndex + ".contentCRC"] = uploadContentCRC;
localStorage[fileSyncIndex + ".titleCRC"] = uploadTitleCRC;
locationUp(callback);
googleHelper.upload(id, undefined, uploadTitle, uploadContent, function(error, result) {
if(error) {
// If error we abort the synchronization (retry later)
callback(error);
return;
}
// If error we abort the synchronization (retry later)
callback("abort");
return;
localStorage[fileSyncIndex + ".contentCRC"] = uploadContentCRC;
localStorage[fileSyncIndex + ".titleCRC"] = uploadTitleCRC;
locationUp(callback);
});
} else if (fileSyncIndex.indexOf(SYNC_PROVIDER_DROPBOX) === 0) {
var path = fileSyncIndex.substring(SYNC_PROVIDER_DROPBOX.length);
@ -130,7 +131,7 @@ define(["jquery", "google-helper", "dropbox-helper"], function($, googleHelper,
uploadTitleCRC = core.crc32(uploadTitle);
// Parse the list of synchronized locations associated to the document
uploadFileSyncIndexList = fileSyncIndexes.split(";");
uploadFileSyncIndexList = _.compact(fileSyncIndexes.split(";"));
locationUp(callback);
}
@ -156,14 +157,14 @@ define(["jquery", "google-helper", "dropbox-helper"], function($, googleHelper,
}
var lastChangeId = parseInt(localStorage[SYNC_PROVIDER_GDRIVE
+ "lastChangeId"]);
googleHelper.checkUpdates(lastChangeId, function(changes, newChangeId) {
if (changes === undefined) {
callback("error");
googleHelper.checkUpdates(lastChangeId, function(error, changes, newChangeId) {
if (error) {
callback(error);
return;
}
googleHelper.downloadContent(changes, function(changes) {
if (changes === undefined) {
callback("error");
googleHelper.downloadContent(changes, function(error, changes) {
if (error) {
callback(error);
return;
}
var updateFileTitles = false;
@ -336,10 +337,83 @@ define(["jquery", "google-helper", "dropbox-helper"], function($, googleHelper,
});
};
// Used to populate the "Manage synchronization" dialog
var lineTemplate = ['<div class="input-prepend input-append">',
'<span class="add-on" title="<%= provider.providerName %>">',
'<i class="icon-<%= provider.providerId %>"></i></span>',
'<input class="span5" type="text" value="<%= syncDesc %>" disabled />',
'</div>'].join("");
var removeButtonTemplate = '<a class="btn" title="Remove this location"><i class="icon-trash"></i></a>';
synchronizer.refreshManageSync = function() {
var fileIndex = fileManager.getCurrentFileIndex();
var syncIndexList = _.compact(localStorage[fileIndex + ".sync"].split(";"));
$(".msg-no-sync, .msg-sync-list").addClass("hide");
$("#manage-sync-list .input-append").remove();
if (syncIndexList.length > 0) {
$(".msg-sync-list").removeClass("hide");
} else {
$(".msg-no-sync").removeClass("hide");
}
_.each(syncIndexList, function(syncIndex) {
var syncAttributes = JSON.parse(localStorage[syncIndex]);
var syncDesc = syncAttributes.id || syncAttributes.path;
lineElement = $(_.template(lineTemplate, {
provider: providerMap[syncAttributes.provider],
syncDesc: syncDesc
}));
lineElement.append($(removeButtonTemplate).click(function() {
fileManager.removeSync(syncIndex);
fileManager.updateFileTitles();
}));
$("#manage-sync-list").append(lineElement);
});
};
synchronizer.init = function(coreModule, fileManagerModule) {
core = coreModule;
fileManager = fileManagerModule;
// Init each provider
_.each(providerMap, function(provider) {
provider.init(core, fileManager);
// Provider's import button
$(".action-sync-import-" + provider.providerId).click(function(event) {
provider.importFiles(event);
});
// Provider's export button
$(".action-sync-export-" + provider.providerId).click(function(event) {
var fileIndex = fileManager.getCurrentFileIndex();
var title = localStorage[fileIndex + ".title"];
var content = localStorage[fileIndex + ".content"];
provider.exportFile(event, title, content, function(error, syncIndex) {
if(error) {
return;
}
localStorage[fileIndex + ".sync"] += syncIndex + ";";
synchronizer.refreshManageSync();
fileManager.updateFileTitles();
core.showMessage('"' + title
+ '" will now be synchronized on ' + provider.providerName + '.');
});
});
// Provider's manual sync button
$(".action-sync-manual-" + provider.providerId).click(function(event) {
var fileIndex = fileManager.getCurrentFileIndex();
var title = localStorage[fileIndex + ".title"];
var content = localStorage[fileIndex + ".content"];
provider.exportManual(event, title, content, function(error, syncIndex) {
if(error) {
return;
}
localStorage[fileIndex + ".sync"] += syncIndex + ";";
synchronizer.refreshManageSync();
fileManager.updateFileTitles();
core.showMessage('"' + title
+ '" will now be synchronized on ' + provider.providerName + '.');
});
});
});
synchronizer.updateSyncButton();
$(".action-force-sync").click(function() {
if(!$(this).hasClass("disabled")) {