periodic down synchronization

This commit is contained in:
benweet 2013-04-01 02:06:52 +01:00
parent dffc6e8829
commit fef879fe50
5 changed files with 470 additions and 188 deletions

View File

@ -1 +1 @@
CACHE MANIFEST # v4 CACHE: index.html css/bootstrap.css css/jgrowl.css css/main.css js/async-runner.js js/base64.js js/bootstrap.js js/gdrive.js js/jquery.jgrowl.js js/jquery.js js/jquery.layout.js js/jquery-ui.custom.js js/main.js js/Markdown.Converter.js js/Markdown.Editor.js js/Markdown.Sanitizer.js img/ajax-loader.gif img/gdrive.png img/glyphicons-halflings.png img/glyphicons-halflings-white.png img/stackedit-16.png img/stackedit-32.ico NETWORK: * CACHE MANIFEST # v5 CACHE: index.html css/bootstrap.css css/jgrowl.css css/main.css js/async-runner.js js/base64.js js/bootstrap.js js/gdrive.js js/jquery.jgrowl.js js/jquery.js js/jquery.layout.js js/jquery-ui.custom.js js/main.js js/Markdown.Converter.js js/Markdown.Editor.js js/Markdown.Sanitizer.js img/ajax-loader.gif img/gdrive.png img/glyphicons-halflings.png img/glyphicons-halflings-white.png img/stackedit-16.png img/stackedit-32.ico NETWORK: *

View File

@ -100,7 +100,7 @@
<div class="modal-header"> <div class="modal-header">
<button type="button" class="close" data-dismiss="modal" <button type="button" class="close" data-dismiss="modal"
aria-hidden="true">&times;</button> aria-hidden="true">&times;</button>
<h3>Remove file</h3> <h3>Remove</h3>
</div> </div>
<div class="modal-body"> <div class="modal-body">
<p>Are you sure you want to remove "<span class="file-title"></span>"? <p>Are you sure you want to remove "<span class="file-title"></span>"?

View File

@ -1,66 +1,101 @@
var ASYNC_TASK_DEFAULT_TIMEOUT = 30000;
/** /**
* Used to run any asynchronous tasks sequentially (ajax mainly) * Used to run any asynchronous tasks sequentially (ajax mainly)
* An asynchronous task must be created with: * An asynchronous task must be created with:
* - a required run() function that may call success() or error() * - a required run() function that may call success(), error() or retry()
* - an optional onSuccess() function * - an optional onSuccess() function
* - an optional onError() function * - an optional onError() function
* - an optional timeout property * - an optional timeout field (default is 30000)
*/ */
var asyncTaskRunner = (function() { var asyncTaskRunner = (function() {
var asyncTaskRunner = {}; var asyncTaskRunner = {};
var asyncTaskQueue = []; var asyncTaskQueue = [];
var currentTask = undefined; var currentTask = undefined;
var currentTaskStartTime = new Date().getTime(); var currentTaskRunning = false;
var currentTaskStartTime = currentTime;
// Run the next task in the queue if any and no other is running // Run the next task in the queue if any and no other is running
asyncTaskRunner.runTask = function() { asyncTaskRunner.runTask = function() {
// If there is a task currently running // If there is a task currently running
if(currentTask !== undefined) { if(currentTaskRunning !== false) {
// If the current task takes too long // If the current task takes too long
var timeout = currentTask.timeout || 30000; var timeout = currentTask.timeout || ASYNC_TASK_DEFAULT_TIMEOUT;
if(currentTaskStartTime + timeout < currentTime) { if(currentTaskStartTime + timeout < currentTime) {
currentTask.error(); currentTask.error();
} }
return; return;
} }
// If no task in the queue if(currentTask === undefined) {
if(asyncTaskQueue.length === 0) { // If no task in the queue
return; if(asyncTaskQueue.length === 0) {
return;
}
// Dequeue an enqueued task
currentTask = asyncTaskQueue.shift();
currentTaskStartTime = currentTime;
showWorkingIndicator(true);
// Set task attributes and functions
currentTask.finished = false;
currentTask.retryCounter = 0;
currentTask.finish = function() {
this.finished = true;
showWorkingIndicator(false);
currentTask = undefined;
currentTaskRunning = false;
asyncTaskRunner.runTask();
};
currentTask.success = function() {
if(this.finished === true) {
return;
}
try {
if(this.onSuccess) {
this.onSuccess();
}
} finally {
this.finish();
}
};
currentTask.error = function() {
if(this.finished === true) {
return;
}
try {
if(this.onError) {
this.onError();
}
} finally {
this.finish();
}
};
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++) + Math.random()) * 1000;
console.log(delay);
currentTaskStartTime = currentTime + delay;
currentTaskRunning = false;
asyncTaskRunner.runTask();
};
} }
currentTask = asyncTaskQueue.shift();
currentTaskStartTime = currentTime;
showWorkingIndicator(true);
// Set task attributes and functions // Run the task
currentTask.finished = false; if(currentTaskStartTime <= currentTime) {
currentTask.finish = function() { currentTaskRunning = true;
this.finished = true; currentTask.run();
showWorkingIndicator(false); }
currentTask = undefined;
asyncTaskRunner.runTask();
};
currentTask.success = function() {
if(this.finished === true) {
return;
}
if(this.onSuccess) {
this.onSuccess();
}
this.finish();
};
currentTask.error = function() {
if(this.finished === true) {
return;
}
if(this.onError) {
this.onError();
}
this.finish();
};
currentTask.run();
}; };
// Add a task in the queue // Add a task in the queue

View File

@ -1,10 +1,11 @@
var GOOGLE_CLIENT_ID = '241271498917-jpto9lls9fqnem1e4h6ppds9uob8rpvu.apps.googleusercontent.com'; var GOOGLE_CLIENT_ID = '241271498917-jpto9lls9fqnem1e4h6ppds9uob8rpvu.apps.googleusercontent.com';
var SCOPES = [ 'https://www.googleapis.com/auth/drive.install', var SCOPES = [ 'https://www.googleapis.com/auth/drive.install',
'https://www.googleapis.com/auth/drive.file' ]; 'https://www.googleapis.com/auth/drive.file' ];
var AUTH_POPUP_TIMEOUT = 90000;
var gdriveDelayedFunction = undefined; var gdriveDelayedFunction = undefined;
function runGdriveDelayedFunction() { function runGdriveDelayedFunction() {
if(gdriveDelayedFunction !== undefined) { if (gdriveDelayedFunction !== undefined) {
gdriveDelayedFunction(); gdriveDelayedFunction();
} }
} }
@ -13,30 +14,31 @@ var gdrive = (function($) {
var connected = false; var connected = false;
var authenticated = false; var authenticated = false;
var doNothing = function() {}; var doNothing = function() {
};
var gdrive = {}; var gdrive = {};
// Try to connect Gdrive by downloading client.js // Try to connect Gdrive by downloading client.js
function connect(callback) { function connect(callback) {
callback = callback || doNothing; callback = callback || doNothing;
var asyncTask = {}; var asyncTask = {};
asyncTask.run = function() { asyncTask.run = function() {
if(connected === true) { if (connected === true) {
asyncTask.success(); asyncTask.success();
return; return;
} }
gdriveDelayedFunction = function() { gdriveDelayedFunction = function() {
asyncTask.success(); asyncTask.success();
}; };
$.ajax({ $
url: "https://apis.google.com/js/client.js?onload=runGdriveDelayedFunction", .ajax(
dataType: "script", {
timeout: 5000 url : "https://apis.google.com/js/client.js?onload=runGdriveDelayedFunction",
}) dataType : "script", timeout : AJAX_TIMEOUT }).fail(
.fail(function() { function() {
asyncTask.error(); asyncTask.error();
}); });
}; };
asyncTask.onSuccess = function() { asyncTask.onSuccess = function() {
gdriveDelayedFunction = undefined; gdriveDelayedFunction = undefined;
@ -50,31 +52,35 @@ var gdrive = (function($) {
}; };
asyncTaskRunner.addTask(asyncTask); asyncTaskRunner.addTask(asyncTask);
} }
// Try to authenticate with Oauth // Try to authenticate with Oauth
function authenticate(callback, immediate) { function authenticate(callback, immediate) {
callback = callback || doNothing; callback = callback || doNothing;
if(immediate === undefined) { if (immediate === undefined) {
immediate = true; immediate = true;
} }
connect(function() { connect(function() {
if(connected === false) { if (connected === false) {
callback(); callback();
return; return;
} }
var asyncTask = {}; var asyncTask = {};
// If not immediate we add time for user to enter his credentials // If not immediate we add time for user to enter his credentials
if(immediate === false) { if (immediate === false) {
asyncTask.timeout = 90000; asyncTask.timeout = AUTH_POPUP_TIMEOUT;
} }
asyncTask.run = function() { asyncTask.run = function() {
if(authenticated === true) { if (authenticated === true) {
asyncTask.success(); asyncTask.success();
return; return;
} }
gapi.auth.authorize({ 'client_id' : GOOGLE_CLIENT_ID, 'scope' : SCOPES, if (immediate === false) {
'immediate' : immediate }, function(authResult) { showMessage("Please make sure the Google authorization popup is not blocked by your browser...");
}
gapi.auth.authorize({ 'client_id' : GOOGLE_CLIENT_ID,
'scope' : SCOPES, 'immediate' : immediate }, function(
authResult) {
if (!authResult || authResult.error) { if (!authResult || authResult.error) {
asyncTask.error(); asyncTask.error();
return; return;
@ -90,7 +96,7 @@ var gdrive = (function($) {
}; };
asyncTask.onError = function() { asyncTask.onError = function() {
// If immediate did not work retry without immediate flag // If immediate did not work retry without immediate flag
if(connected === true && immediate === true) { if (connected === true && immediate === true) {
authenticate(callback, false); authenticate(callback, false);
return; return;
} }
@ -100,14 +106,45 @@ var gdrive = (function($) {
}); });
} }
function handleError(response, asyncTask, callback) {
var errorMsg = undefined;
if (response && response.error) {
var error = response.error;
// Try to analyze the error
if (error.code >= 500 && error.code < 600) {
errorMsg = "Google Drive is not accessible.";
// Retry as described in Google's best practices
asyncTask.retry();
return;
} else if (error.code === 401) {
authenticated = false;
errorMsg = "Access to Google Drive is not authorized.";
} else if (error.code === -1) {
connected = false;
authenticated = false;
onOffline();
} else {
errorMsg = "Google Drive error (" + error.code + ": "
+ error.message + ").";
}
}
asyncTask.onError = function() {
if (errorMsg !== undefined) {
showError(errorMsg);
}
callback();
};
asyncTask.error();
}
function upload(fileId, parentId, title, content, callback) { function upload(fileId, parentId, title, content, callback) {
callback = callback || doNothing; callback = callback || doNothing;
authenticate(function() { authenticate(function() {
if(connected === false) { if (connected === false) {
callback(); callback();
return; return;
} }
var fileIndex = undefined; var fileIndex = undefined;
var asyncTask = {}; var asyncTask = {};
asyncTask.run = function() { asyncTask.run = function() {
@ -117,13 +154,14 @@ var gdrive = (function($) {
var contentType = 'text/x-markdown'; var contentType = 'text/x-markdown';
var metadata = { title : title, mimeType : contentType }; var metadata = { title : title, mimeType : contentType };
if (parentId) { if (parentId !== undefined) {
// Specify the directory // Specify the directory
metadata.parents = [ { kind : 'drive#fileLink', id : parentId } ]; metadata.parents = [ { kind : 'drive#fileLink',
id : parentId } ];
} }
var path = '/upload/drive/v2/files'; var path = '/upload/drive/v2/files';
var method = 'POST'; var method = 'POST';
if (fileId) { if (fileId !== undefined) {
// If it's an update // If it's an update
path += "/" + fileId; path += "/" + fileId;
method = 'PUT'; method = 'PUT';
@ -133,59 +171,151 @@ var gdrive = (function($) {
var multipartRequestBody = delimiter var multipartRequestBody = delimiter
+ 'Content-Type: application/json\r\n\r\n' + 'Content-Type: application/json\r\n\r\n'
+ JSON.stringify(metadata) + delimiter + 'Content-Type: ' + JSON.stringify(metadata) + delimiter + 'Content-Type: '
+ contentType + '\r\n' + 'Content-Transfer-Encoding: base64\r\n' + contentType + '\r\n'
+ '\r\n' + base64Data + close_delim; + 'Content-Transfer-Encoding: base64\r\n' + '\r\n'
+ base64Data + close_delim;
var request = gapi.client.request({ var request = gapi.client
'path' : path, .request({
'method' : method, 'path' : path,
'params' : { 'uploadType' : 'multipart', }, 'method' : method,
'headers' : { 'Content-Type' : 'multipart/mixed; boundary="' 'params' : { 'uploadType' : 'multipart', },
+ boundary + '"', }, 'body' : multipartRequestBody, }); 'headers' : { 'Content-Type' : 'multipart/mixed; boundary="'
request.execute(function(file) { + boundary + '"', }, 'body' : multipartRequestBody, });
if(file.id) { request.execute(function(response) {
if (response && response.id) {
// Upload success // Upload success
fileIndex = SYNC_PROVIDER_GDRIVE + file.id; fileIndex = SYNC_PROVIDER_GDRIVE + response.id;
localStorage[fileIndex + ".etag"] = file.etag; localStorage[fileIndex + ".etag"] = response.etag;
asyncTask.success(); asyncTask.success();
return return;
} }
// Upload failed, try to analyse // If file has been removed from Google Drive
if(file.error.code === 401) { if(fileId !== undefined && response.error.code === 404) {
showError("Google Drive is not accessible."); showMessage('"' + title + '" has been removed from Google Drive.');
fileManager.removeSync(SYNC_PROVIDER_GDRIVE + fileId);
fileManager.updateFileTitles();
// Avoid error analyzed by handleError
response = undefined;
} }
else { // Handle error
connected = false; handleError(response, asyncTask, callback);
authenticated = false;
onOffline();
}
asyncTask.error();
}); });
}; };
asyncTask.onSuccess = function() { asyncTask.onSuccess = function() {
callback(fileIndex); callback(fileIndex);
}; };
asyncTask.onError = function() { asyncTaskRunner.addTask(asyncTask);
});
}
gdrive.checkUpdates = function(lastChangeId, callback) {
callback = callback || doNothing;
authenticate(function() {
if (connected === false) {
callback(); callback();
return;
}
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;
}
// Handle error
handleError(response, asyncTask, callback);
});
};
asyncTask.onSuccess = function() {
if (nextPageToken !== undefined) {
request = gapi.client.drive.changes
.list({ 'pageToken' : nextPageToken });
retrievePageOfChanges(request);
} else {
callback(changes, newChangeId);
}
};
asyncTaskRunner.addTask(asyncTask);
}
var initialRequest = gapi.client.drive.changes
.list({ 'startChangeId' : newChangeId + 1 });
retrievePageOfChanges(initialRequest);
});
};
gdrive.downloadContent = function(objects, callback, result) {
callback = callback || 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 === undefined) {
this.downloadContent(objects, callback, result);
return;
}
authenticate(function() {
if (connected === false) {
callback();
return;
}
var asyncTask = {};
asyncTask.run = function() {
var accessToken = gapi.auth.getToken().access_token;
$
.ajax(
{
url : file.downloadUrl,
headers : { "Authorization" : "Bearer "
+ accessToken }, dataType : "text",
timeout : AJAX_TIMEOUT }).done(
function(data, textStatus, jqXHR) {
file.content = data;
asyncTask.success();
}).fail(function() {
asyncTask.error();
});
};
asyncTask.onSuccess = function() {
gdrive.downloadContent(objects, callback, result);
}; };
asyncTaskRunner.addTask(asyncTask); asyncTaskRunner.addTask(asyncTask);
}); });
}
;
gdrive.init = function() {
try {
var state = JSON.parse(decodeURI((/state=(.+?)(&|$)/
.exec(location.search) || [ , null ])[1]));
if (state.action == 'create') {
upload(undefined, state.folderId,
fileManager.currentFile, fileManager.content, function(
fileIndex) {
console.log(fileIndex);
});
}
} catch (e) {
}
}; };
gdrive.createFile = function(title, content, callback) { gdrive.createFile = function(title, content, callback) {
@ -195,6 +325,20 @@ var gdrive = (function($) {
gdrive.updateFile = function(id, title, content, callback) { gdrive.updateFile = function(id, title, content, callback) {
upload(id, undefined, title, content, callback); upload(id, undefined, title, content, callback);
}; };
gdrive.init = function() {
try {
var state = JSON.parse(decodeURI((/state=(.+?)(&|$)/
.exec(location.search) || [ , null ])[1]));
if (state.action == 'create') {
upload(undefined, state.folderId, fileManager.currentFile,
fileManager.content, function(fileIndex) {
console.log(fileIndex);
});
}
} catch (e) {
}
};
return gdrive; return gdrive;
})(jQuery); })(jQuery);

View File

@ -18,6 +18,8 @@ function showWorkingIndicator(show) {
} }
} }
var AJAX_TIMEOUT = 5000;
var CHECK_ONLINE_PERIOD = 60000;
var offline = false; var offline = false;
var offlineTime = currentTime; var offlineTime = currentTime;
function onOffline() { function onOffline() {
@ -36,21 +38,24 @@ function onOnline() {
'jGrowl.beforeClose'); 'jGrowl.beforeClose');
} }
function autoClean() { function checkOnline() {
// Try to reconnect if we are offline but we have some network // Try to reconnect if we are offline but we have some network
if (offline === true && navigator.onLine === true if (offline === true && navigator.onLine === true
&& offlineTime + 60000 < currentTime) { && offlineTime + CHECK_ONLINE_PERIOD < currentTime) {
offlineTime = currentTime; offlineTime = currentTime;
// Try to download anything to test the connection // Try to download anything to test the connection
$.ajax( $.ajax(
{ url : "https://apis.google.com/js/client.js", timeout : 5000, { url : "https://apis.google.com/js/client.js",
dataType : "script" }).done(function() { timeout : AJAX_TIMEOUT, dataType : "script" }).done(function() {
onOnline(); onOnline();
}); });
} }
} }
var SYNC_DOWN_PERIOD = 60000;
var SYNC_PROVIDER_GDRIVE = "sync.gdrive."; var SYNC_PROVIDER_GDRIVE = "sync.gdrive.";
var syncGoogleDrive = false;
var synchronizer = (function($) { var synchronizer = (function($) {
var synchronizer = {}; var synchronizer = {};
@ -76,14 +81,13 @@ var synchronizer = (function($) {
} }
}; };
// Recursive function to run synchronization of a single file on multiple // Recursive function to upload a single file on multiple locations
// locations function fileUp(fileSyncIndexList, content, title) {
function sync(fileSyncIndexList, content, title) {
if (fileSyncIndexList.length === 0) { if (fileSyncIndexList.length === 0) {
localStorage.removeItem("sync.current"); localStorage.removeItem("sync.current");
running = false; uploadRunning = false;
// run the next file synchronization // run the next file synchronization
synchronizer.run(); synchronizer.syncUp();
return; return;
} }
var fileSyncIndex = fileSyncIndexList.pop(); var fileSyncIndex = fileSyncIndexList.pop();
@ -92,28 +96,28 @@ var synchronizer = (function($) {
if (fileSyncIndex.indexOf(SYNC_PROVIDER_GDRIVE) === 0) { if (fileSyncIndex.indexOf(SYNC_PROVIDER_GDRIVE) === 0) {
var id = fileSyncIndex.substring(SYNC_PROVIDER_GDRIVE.length); var id = fileSyncIndex.substring(SYNC_PROVIDER_GDRIVE.length);
gdrive.updateFile(id, title, content, function(result) { gdrive.updateFile(id, title, content, function(result) {
if (!result && offline) { if (result === undefined && offline === true) {
// If we detect offline mode we put the fileIndex back in // If we detect offline mode we put the fileIndex back in
// the queue // the queue
synchronizer.addFile(localStorage["sync.current"]); synchronizer.addFile(localStorage["sync.current"]);
localStorage.removeItem("sync.current"); localStorage.removeItem("sync.current");
running = false; uploadRunning = false;
return; return;
} }
sync(fileSyncIndexList, content, title); fileUp(fileSyncIndexList, content, title);
}); });
} else { } else {
sync(fileSyncIndexList, content, title); fileUp(fileSyncIndexList, content, title);
} }
} }
var running = false; var uploadRunning = false;
synchronizer.run = function() { synchronizer.syncUp = function() {
// If synchronization is already running or nothing to synchronize // If syncUp is already running or nothing to synchronize or offline
if (running || syncQueue.length === 1 || offline) { if (uploadRunning || syncQueue.length === 1 || offline) {
return; return;
} }
running = true; uploadRunning = true;
// Dequeue the fileIndex // Dequeue the fileIndex
var separatorPos = syncQueue.indexOf(";", 1); var separatorPos = syncQueue.indexOf(";", 1);
@ -127,7 +131,101 @@ var synchronizer = (function($) {
// Parse the list of synchronized locations associated to the file // Parse the list of synchronized locations associated to the file
var fileSyncIndexList = localStorage[fileIndex + ".sync"].split(";"); var fileSyncIndexList = localStorage[fileIndex + ".sync"].split(";");
sync(fileSyncIndexList, content, title); fileUp(fileSyncIndexList, content, title);
};
function syncDownGdrive(callback) {
if (syncGoogleDrive === false) {
callback();
return;
}
var lastChangeId = parseInt(localStorage[SYNC_PROVIDER_GDRIVE
+ "lastChangeId"]);
gdrive.checkUpdates(lastChangeId, function(changes, newChangeId) {
if (changes === undefined) {
callback();
return;
}
gdrive.downloadContent(changes, function(changes) {
if (changes === undefined) {
callback();
return;
}
var updateFileTitles = false;
for ( var i = 0; i < changes.length; i++) {
var change = changes[i];
var fileSyncIndex = SYNC_PROVIDER_GDRIVE + change.fileId;
var fileIndexList = localStorage["file.list"].split(";");
var fileIndex = undefined;
// Look for local file associated to this synchronized location
for ( var i = 1; i < fileIndexList.length - 1; i++) {
var tempFileIndex = fileIndexList[i];
var sync = localStorage[tempFileIndex + ".sync"];
if (sync.indexOf(";" + fileSyncIndex + ";") !== -1) {
fileIndex = tempFileIndex;
break;
}
}
// No file corresponding (this should never happen...)
if(fileIndex === undefined) {
// We can remove the stored etag
localStorage.removeItem(fileSyncIndex + ".etag");
continue;
}
var title = localStorage[fileIndex + ".title"];
// File deleted
if (change.deleted === true) {
fileManager.removeSync(fileSyncIndex);
updateFileTitles = true;
showMessage('"' + title + '" has been removed from Google Drive.');
continue;
}
var content = localStorage[fileIndex + ".content"];
var file = change.file;
// File title changed
if(title != file.title) {
localStorage[fileIndex + ".title"] = file.title;
updateFileTitles = true;
showMessage('"' + title + '" has been renamed to "' + file.title + '" on Google Drive.');
}
// File content changed
if(content != file.content) {
localStorage[fileIndex + ".content"] = file.content;
showMessage('"' + file.title + '" has been updated from Google Drive.');
if(fileIndex == localStorage["file.current"]) {
updateFileTitles = false; // Done by next function
fileManager.selectFile();
}
}
// Update file etag
localStorage[fileSyncIndex + ".etag"] = file.etag;
// Synchronize file to others locations
synchronizer.addFile(fileIndex);
}
if(updateFileTitles) {
fileManager.updateFileTitles();
}
localStorage[SYNC_PROVIDER_GDRIVE
+ "lastChangeId"] = newChangeId;
callback();
});
});
}
var downloadRunning = false;
var lastSyncDown = 0;
synchronizer.syncDown = function() {
// If syncDown is already running or timeout is not reached or offline
if (downloadRunning || lastSyncDown + SYNC_DOWN_PERIOD > currentTime
|| offline) {
return;
}
downloadRunning = true;
lastSyncDown = currentTime;
syncDownGdrive(function() {
downloadRunning = false;
});
}; };
return synchronizer; return synchronizer;
@ -143,19 +241,21 @@ var fileManager = (function($) {
synchronizer.init(); synchronizer.init();
fileManager.selectFile(); fileManager.selectFile();
// Save file automatically and synchronize // Do periodic stuff
window.setInterval(function() { window.setInterval(function() {
currentTime = new Date().getTime(); currentTime = new Date().getTime();
fileManager.saveFile(); fileManager.saveFile();
synchronizer.run(); synchronizer.syncDown();
synchronizer.syncUp();
asyncTaskRunner.runTask(); asyncTaskRunner.runTask();
autoClean(); checkOnline();
}, 1000); }, 1000);
$(".action-create-file").click(function() { $(".action-create-file").click(function() {
fileManager.saveFile(); fileManager.saveFile();
fileManager.createFile(); fileManager.createFile();
fileManager.selectFile(); fileManager.selectFile();
$("#file-title").click();
}); });
$(".action-remove-file").click(function() { $(".action-remove-file").click(function() {
fileManager.deleteFile(); fileManager.deleteFile();
@ -172,8 +272,7 @@ var fileManager = (function($) {
var fileIndexTitle = localStorage["file.current"] + ".title"; var fileIndexTitle = localStorage["file.current"] + ".title";
if (title != localStorage[fileIndexTitle]) { if (title != localStorage[fileIndexTitle]) {
localStorage[fileIndexTitle] = title; localStorage[fileIndexTitle] = title;
updateFileDescList(); fileManager.updateFileTitles();
updateFileTitleUI();
save = true; save = true;
} }
} }
@ -205,23 +304,18 @@ var fileManager = (function($) {
localStorage["file.counter"] = 0; localStorage["file.counter"] = 0;
localStorage["file.list"] = ";"; localStorage["file.list"] = ";";
} }
updateFileDescList();
// If no file create one // If no file create one
if (fileDescList.length === 0) { if (localStorage["file.list"].length === 1) {
this.createFile(); this.createFile();
updateFileDescList();
} }
// If no default file take first one // Update the file titles
if (!localStorage["file.current"]) { this.updateFileTitles();
localStorage["file.current"] = fileDescList[0].index; // Update the editor
}
// Update the editor and the file title
var fileIndex = localStorage["file.current"]; var fileIndex = localStorage["file.current"];
$("#wmd-input").val(localStorage[fileIndex + ".content"]); $("#wmd-input").val(localStorage[fileIndex + ".content"]);
core.createEditor(function() { core.createEditor(function() {
save = true; save = true;
}); });
updateFileTitleUI();
}; };
fileManager.createFile = function(title) { fileManager.createFile = function(title) {
@ -242,18 +336,18 @@ var fileManager = (function($) {
fileManager.deleteFile = function() { fileManager.deleteFile = function() {
var fileIndex = localStorage["file.current"]; var fileIndex = localStorage["file.current"];
// Remove synchronized locations // Remove synchronized locations
var fileSyncIndexList = localStorage[fileIndex + ".sync"].split(";"); var fileSyncIndexList = localStorage[fileIndex + ".sync"].split(";");
for ( var i = 1; i < fileSyncIndexList.length - 1; i++) { for ( var i = 1; i < fileSyncIndexList.length - 1; i++) {
var sync = fileSyncIndexList[i]; var fileSyncIndex = fileSyncIndexList[i];
fileManager.removeSync(sync); fileManager.removeSync(fileSyncIndex);
} }
localStorage.removeItem(fileIndex + ".sync"); localStorage.removeItem(fileIndex + ".sync");
localStorage.removeItem("file.current"); localStorage.removeItem("file.current");
localStorage["file.list"] = localStorage["file.list"].replace(";" localStorage["file.list"] = localStorage["file.list"].replace(";"
+ fileIndex + ";", ";"); + fileIndex + ";", ";");
localStorage.removeItem(fileIndex + ".title"); localStorage.removeItem(fileIndex + ".title");
localStorage.removeItem(fileIndex + ".content"); localStorage.removeItem(fileIndex + ".content");
}; };
@ -267,34 +361,8 @@ var fileManager = (function($) {
save = false; save = false;
} }
}; };
// Remove a synchronization location associated to the file fileManager.updateFileTitles = function() {
fileManager.removeSync = function(sync) {
var fileIndexSync = localStorage["file.current"] + ".sync";
localStorage[fileIndexSync] = localStorage[fileIndexSync].replace(";"
+ sync + ";", ";");
localStorage.removeItem(sync + ".etag");
};
function uploadGdrive() {
$(".file-sync-indicator").removeClass("hide");
var fileIndex = localStorage["file.current"];
var content = localStorage[fileIndex + ".content"];
var title = localStorage[fileIndex + ".title"];
gdrive.createFile(title, content, function(fileSyncIndex) {
if (fileSyncIndex) {
localStorage[fileIndex + ".sync"] += fileSyncIndex + ";";
updateFileTitleUI();
showMessage('The file "' + title
+ '" will now be synchronized on Google Drive.');
} else {
showError("Error while creating file on Google Drive.");
}
});
}
function updateFileDescList() {
fileDescList = []; fileDescList = [];
$("#file-selector").empty(); $("#file-selector").empty();
var fileIndexList = localStorage["file.list"].split(";"); var fileIndexList = localStorage["file.list"].split(";");
@ -310,20 +378,24 @@ var fileManager = (function($) {
return 1; return 1;
return 0; return 0;
}); });
}
;
function updateFileTitleUI() { // If no default file take first one
if (!localStorage["file.current"]) {
localStorage["file.current"] = fileDescList[0].index;
}
syncGoogleDrive = false;
function composeTitle(fileIndex) { function composeTitle(fileIndex) {
var result = localStorage[fileIndex + ".title"]; var result = localStorage[fileIndex + ".title"];
var sync = localStorage[fileIndex + ".sync"]; var sync = localStorage[fileIndex + ".sync"];
if (sync.indexOf(SYNC_PROVIDER_GDRIVE) !== -1) { if (sync.indexOf(";" + SYNC_PROVIDER_GDRIVE) !== -1) {
syncGoogleDrive = true;
result = '<i class="icon-gdrive"></i> ' + result; result = '<i class="icon-gdrive"></i> ' + result;
} }
return result; return result;
} }
// Update the editor and the file title // Update the the file title and the file selector
var fileIndex = localStorage["file.current"]; var fileIndex = localStorage["file.current"];
var title = localStorage[fileIndex + ".title"]; var title = localStorage[fileIndex + ".title"];
document.title = "StackEdit - " + title; document.title = "StackEdit - " + title;
@ -348,8 +420,37 @@ var fileManager = (function($) {
} }
$("#file-selector").append(li); $("#file-selector").append(li);
} }
};
// Remove a synchronized location
fileManager.removeSync = function(fileSyncIndex) {
var fileIndexList = localStorage["file.list"].split(";");
// Look for local files associated to this synchronized location
for ( var i = 1; i < fileIndexList.length - 1; i++) {
var fileIndexSync = fileIndexList[i] + ".sync";
localStorage[fileIndexSync] = localStorage[fileIndexSync].replace(";"
+ fileSyncIndex + ";", ";");
}
// Remove etag
localStorage.removeItem(fileSyncIndex + ".etag");
};
function uploadGdrive() {
$(".file-sync-indicator").removeClass("hide");
var fileIndex = localStorage["file.current"];
var content = localStorage[fileIndex + ".content"];
var title = localStorage[fileIndex + ".title"];
gdrive.createFile(title, content, function(fileSyncIndex) {
if (fileSyncIndex) {
localStorage[fileIndex + ".sync"] += fileSyncIndex + ";";
fileManager.updateFileTitles();
showMessage('The file "' + title
+ '" will now be synchronized on Google Drive.');
} else {
showError("Error while creating file on Google Drive.");
}
});
} }
;
function refreshManageSync() { function refreshManageSync() {
var fileIndex = localStorage["file.current"]; var fileIndex = localStorage["file.current"];
@ -362,23 +463,24 @@ var fileManager = (function($) {
$(".msg-no-sync").removeClass("hide"); $(".msg-no-sync").removeClass("hide");
} }
for ( var i = 1; i < fileSyncIndexList.length - 1; i++) { for ( var i = 1; i < fileSyncIndexList.length - 1; i++) {
var sync = fileSyncIndexList[i]; var fileSyncIndex = fileSyncIndexList[i];
(function(sync) { (function(fileSyncIndex) {
var line = $("<div>").addClass("input-append"); var line = $("<div>").addClass("input-append");
if (sync.indexOf(SYNC_PROVIDER_GDRIVE) === 0) { if (fileSyncIndex.indexOf(SYNC_PROVIDER_GDRIVE) === 0) {
line.append($("<input>").prop("type", "text").prop( line.append($("<input>").prop("type", "text").prop(
"disabled", true).addClass("span5").val( "disabled", true).addClass("span5").val(
"Google Drive, FileID=" "Google Drive, FileID="
+ sync.substring(SYNC_PROVIDER_GDRIVE.length))); + fileSyncIndex.substring(SYNC_PROVIDER_GDRIVE.length)));
line.append($("<a>").addClass("btn").html( line.append($("<a>").addClass("btn").html(
'<i class="icon-trash"></i>').prop("title", "Remove this synchronized location").click(function() { '<i class="icon-trash"></i>').prop("title",
fileManager.removeSync(sync); "Remove this synchronized location").click(function() {
updateFileTitleUI(); fileManager.removeSync(fileSyncIndex);
fileManager.updateFileTitles();
refreshManageSync(); refreshManageSync();
})); }));
} }
$("#manage-sync-list").append(line); $("#manage-sync-list").append(line);
})(sync); })(fileSyncIndex);
} }
} }
@ -493,6 +595,7 @@ var core = (function($) {
$(function() { $(function() {
$.jGrowl.defaults.life = 5000;
$.jGrowl.defaults.closer = false; $.jGrowl.defaults.closer = false;
$.jGrowl.defaults.closeTemplate = ''; $.jGrowl.defaults.closeTemplate = '';
$.jGrowl.defaults.position = 'bottom-right'; $.jGrowl.defaults.position = 'bottom-right';