Stackedit/js/synchronizer.js

322 lines
11 KiB
JavaScript
Raw Normal View History

2013-04-09 07:58:06 +00:00
define(["jquery", "core", "google-helper", "dropbox-helper"], function($, core, googleHelper, dropboxHelper) {
2013-04-01 16:46:48 +00:00
var synchronizer = {};
2013-04-02 18:42:47 +00:00
// Dependencies
var fileManager = undefined;
// Used to know the providers we are connected to
synchronizer.useGoogleDrive = false;
2013-04-07 15:22:13 +00:00
synchronizer.useDropbox = false;
2013-04-02 18:42:47 +00:00
var onSyncBegin = undefined;
var onSyncEnd = undefined;
var onQueueChanged = undefined;
2013-04-01 16:46:48 +00:00
// A synchronization queue containing fileIndex that has to be synchronized
var syncUpQueue = undefined;
2013-04-02 18:42:47 +00:00
synchronizer.init = function(fileManagerModule, options) {
fileManager = fileManagerModule;
onSyncBegin = options.onSyncBegin || core.doNothing;
onSyncEnd = options.onSyncEnd || core.doNothing;
onQueueChanged = options.onQueueChanged || core.doNothing;
2013-04-01 16:46:48 +00:00
syncUpQueue = ";";
// Load the queue from localStorage in case a previous synchronization
// was aborted
if (localStorage["sync.queue"]) {
syncUpQueue = localStorage["sync.queue"];
onQueueChanged();
2013-04-01 16:46:48 +00:00
}
if (localStorage["sync.current"]) {
this.addFileForUpload(localStorage["sync.current"]);
}
};
// Add a file to the synchronization queue
synchronizer.addFileForUpload = function(fileIndex) {
// Check that file has synchronized locations
if(localStorage[fileIndex + ".sync"].length === 1) {
return;
2013-04-01 16:46:48 +00:00
}
// Check that file is not in the queue
if (syncUpQueue.indexOf(";" + fileIndex + ";") !== -1) {
return;
}
syncUpQueue += fileIndex + ";";
localStorage["sync.queue"] = syncUpQueue;
onQueueChanged();
2013-04-01 16:46:48 +00:00
};
// Recursive function to upload a single file on multiple locations
2013-04-09 07:58:06 +00:00
function fileUp(fileSyncIndexList, content, contentCRC, title, titleCRC, callback) {
2013-04-01 16:46:48 +00:00
if (fileSyncIndexList.length === 0) {
localStorage.removeItem("sync.current");
// run the next file synchronization
syncUp(callback);
return;
}
var fileSyncIndex = fileSyncIndexList.pop();
2013-04-09 07:58:06 +00:00
// Skip if CRC has not changed
var syncContentCRC = localStorage[fileSyncIndex + ".contentCRC"];
var syncTitleCRC = localStorage[fileSyncIndex + ".titleCRC"];
if(contentCRC == syncContentCRC && (syncTitleCRC === undefined || titleCRC == syncTitleCRC)) {
fileUp(fileSyncIndexList, content, contentCRC, title, titleCRC, callback);
return;
}
2013-04-01 16:46:48 +00:00
// Try to find the provider
if (fileSyncIndex.indexOf(SYNC_PROVIDER_GDRIVE) === 0) {
var id = fileSyncIndex.substring(SYNC_PROVIDER_GDRIVE.length);
2013-04-09 07:58:06 +00:00
googleHelper.upload(id, undefined, title, content, function(result) {
2013-04-07 15:22:13 +00:00
if (result !== undefined) {
2013-04-09 07:58:06 +00:00
localStorage[fileSyncIndex + ".contentCRC"] = contentCRC;
localStorage[fileSyncIndex + ".titleCRC"] = titleCRC;
fileUp(fileSyncIndexList, content, contentCRC, title, titleCRC, callback);
2013-04-07 15:22:13 +00:00
return;
}
// If error we put the fileIndex back in the queue
synchronizer.addFileForUpload(localStorage["sync.current"]);
localStorage.removeItem("sync.current");
callback();
return;
});
} else if (fileSyncIndex.indexOf(SYNC_PROVIDER_DROPBOX) === 0) {
var path = fileSyncIndex.substring(SYNC_PROVIDER_DROPBOX.length);
path = decodeURIComponent(path);
2013-04-09 07:58:06 +00:00
dropboxHelper.upload(path, content, function(result) {
2013-04-02 18:42:47 +00:00
if (result !== undefined) {
2013-04-09 07:58:06 +00:00
localStorage[fileSyncIndex + ".contentCRC"] = contentCRC;
fileUp(fileSyncIndexList, content, contentCRC, title, titleCRC, callback);
2013-04-01 16:46:48 +00:00
return;
}
2013-04-02 18:42:47 +00:00
// If error we put the fileIndex back in the queue
synchronizer.addFileForUpload(localStorage["sync.current"]);
localStorage.removeItem("sync.current");
callback();
return;
2013-04-01 16:46:48 +00:00
});
} else {
2013-04-09 07:58:06 +00:00
fileUp(fileSyncIndexList, content, contentCRC, title, titleCRC, callback);
2013-04-01 16:46:48 +00:00
}
}
function syncUp(callback) {
// If nothing to synchronize
if (syncUpQueue.length === 1) {
callback();
return;
}
// Dequeue the fileIndex
var separatorPos = syncUpQueue.indexOf(";", 1);
var fileIndex = syncUpQueue.substring(1, separatorPos);
localStorage["sync.current"] = fileIndex;
syncUpQueue = syncUpQueue.substring(separatorPos);
localStorage["sync.queue"] = syncUpQueue;
onQueueChanged();
2013-04-01 16:46:48 +00:00
var content = localStorage[fileIndex + ".content"];
var title = localStorage[fileIndex + ".title"];
2013-04-09 07:58:06 +00:00
var contentCRC = core.crc32(content);
var titleCRC = core.crc32(title);
2013-04-01 16:46:48 +00:00
// Parse the list of synchronized locations associated to the file
var fileSyncIndexList = localStorage[fileIndex + ".sync"].split(";");
2013-04-09 07:58:06 +00:00
fileUp(fileSyncIndexList, content, contentCRC, title, titleCRC, callback);
2013-04-01 16:46:48 +00:00
};
function syncDownGdrive(callback) {
2013-04-02 18:42:47 +00:00
if (synchronizer.useGoogleDrive === false) {
2013-04-01 16:46:48 +00:00
callback();
return;
}
var lastChangeId = parseInt(localStorage[SYNC_PROVIDER_GDRIVE
+ "lastChangeId"]);
2013-04-09 07:58:06 +00:00
googleHelper.checkUpdates(lastChangeId, function(changes, newChangeId) {
2013-04-01 16:46:48 +00:00
if (changes === undefined) {
callback();
return;
}
2013-04-09 07:58:06 +00:00
googleHelper.downloadContent(changes, function(changes) {
2013-04-01 16:46:48 +00:00
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 fileIndex = fileManager.getFileIndexFromSync(fileSyncIndex);
2013-04-01 16:46:48 +00:00
// 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;
2013-04-02 18:42:47 +00:00
core.showMessage('"' + title + '" has been removed from Google Drive.');
2013-04-01 16:46:48 +00:00
continue;
}
var content = localStorage[fileIndex + ".content"];
var file = change.file;
var titleChanged = title != file.title;
var contentChanged = content != file.content;
// If file is in the upload queue we have a conflict
if ((titleChanged || contentChanged) && syncUpQueue.indexOf(";" + fileIndex + ";") !== -1) {
fileManager.createFile(title + " (backup)", content);
updateFileTitles = true;
2013-04-02 18:42:47 +00:00
core.showMessage('Conflict detected on "' + title + '". A backup has been created locally.');
2013-04-01 16:46:48 +00:00
}
// If file title changed
if(titleChanged) {
localStorage[fileIndex + ".title"] = file.title;
updateFileTitles = true;
2013-04-02 18:42:47 +00:00
core.showMessage('"' + title + '" has been renamed to "' + file.title + '" on Google Drive.');
2013-04-01 16:46:48 +00:00
}
// If file content changed
if(contentChanged) {
localStorage[fileIndex + ".content"] = file.content;
2013-04-02 18:42:47 +00:00
core.showMessage('"' + file.title + '" has been updated from Google Drive.');
2013-04-01 16:46:48 +00:00
if(fileIndex == localStorage["file.current"]) {
updateFileTitles = false; // Done by next function
2013-04-07 15:22:13 +00:00
fileManager.selectFile(); // Refresh editor
2013-04-01 16:46:48 +00:00
}
}
2013-04-09 07:58:06 +00:00
// Update file etag and CRCs
2013-04-01 16:46:48 +00:00
localStorage[fileSyncIndex + ".etag"] = file.etag;
2013-04-09 07:58:06 +00:00
var contentCRC = core.crc32(file.content);
localStorage[fileSyncIndex + ".contentCRC"] = contentCRC;
var titleCRC = core.crc32(file.title);
localStorage[fileSyncIndex + ".titleCRC"] = titleCRC;
// Synchronize file with others locations
2013-04-01 16:46:48 +00:00
synchronizer.addFileForUpload(fileIndex);
}
if(updateFileTitles) {
fileManager.updateFileTitles();
}
localStorage[SYNC_PROVIDER_GDRIVE
+ "lastChangeId"] = newChangeId;
callback();
});
});
}
2013-04-07 15:22:13 +00:00
function syncDownDropbox(callback) {
if (synchronizer.useDropbox === false) {
callback();
return;
}
var lastChangeId = localStorage[SYNC_PROVIDER_DROPBOX + "lastChangeId"];
2013-04-09 07:58:06 +00:00
dropboxHelper.checkUpdates(lastChangeId, function(changes, newChangeId) {
2013-04-07 15:22:13 +00:00
if (changes === undefined) {
callback();
return;
}
2013-04-09 07:58:06 +00:00
dropboxHelper.downloadContent(changes, function(changes) {
2013-04-07 15:22:13 +00:00
if (changes === undefined) {
callback();
return;
}
var updateFileTitles = false;
for ( var i = 0; i < changes.length; i++) {
var change = changes[i];
var fileSyncIndex = SYNC_PROVIDER_DROPBOX + encodeURIComponent(change.path.toLowerCase());
var fileIndex = fileManager.getFileIndexFromSync(fileSyncIndex);
// No file corresponding (this should never happen...)
if(fileIndex === undefined) {
// We can remove the stored version
localStorage.removeItem(fileSyncIndex + ".version");
continue;
}
var title = localStorage[fileIndex + ".title"];
// File deleted
if (change.wasRemoved === true) {
fileManager.removeSync(fileSyncIndex);
updateFileTitles = true;
core.showMessage('"' + title + '" has been removed from Dropbox.');
continue;
}
var content = localStorage[fileIndex + ".content"];
var file = change.stat;
var contentChanged = content != file.content;
// If file is in the upload queue we have a conflict
if (contentChanged && syncUpQueue.indexOf(";" + fileIndex + ";") !== -1) {
fileManager.createFile(title + " (backup)", content);
updateFileTitles = true;
core.showMessage('Conflict detected on "' + title + '". A backup has been created locally.');
}
// If file content changed
if(contentChanged) {
localStorage[fileIndex + ".content"] = file.content;
core.showMessage('"' + title + '" has been updated from Dropbox.');
if(fileIndex == localStorage["file.current"]) {
updateFileTitles = false; // Done by next function
fileManager.selectFile(); // Refresh editor
}
}
2013-04-09 07:58:06 +00:00
// Update file version and CRC
2013-04-07 15:22:13 +00:00
localStorage[fileSyncIndex + ".version"] = file.versionTag;
2013-04-09 07:58:06 +00:00
var contentCRC = core.crc32(file.content);
localStorage[fileSyncIndex + ".contentCRC"] = contentCRC;
// Synchronize file with others locations
2013-04-07 15:22:13 +00:00
synchronizer.addFileForUpload(fileIndex);
}
if(updateFileTitles) {
fileManager.updateFileTitles();
}
localStorage[SYNC_PROVIDER_DROPBOX
+ "lastChangeId"] = newChangeId;
callback();
});
});
}
2013-04-01 16:46:48 +00:00
function syncDown(callback) {
2013-04-07 15:22:13 +00:00
syncDownGdrive(function() {
syncDownDropbox(callback);
});
2013-04-01 16:46:48 +00:00
};
var syncRunning = false;
var lastSync = 0;
synchronizer.sync = function() {
// If sync is already running or timeout is not reached or offline
2013-04-02 18:42:47 +00:00
if (syncRunning || lastSync + SYNC_PERIOD > core.currentTime || core.isOffline) {
2013-04-01 16:46:48 +00:00
return;
}
syncRunning = true;
2013-04-02 18:42:47 +00:00
lastSync = core.currentTime;
onSyncBegin();
2013-04-01 16:46:48 +00:00
syncDown(function() {
syncUp(function() {
syncRunning = false;
onSyncEnd();
2013-04-01 16:46:48 +00:00
});
});
};
synchronizer.forceSync = function() {
lastSync = 0;
this.sync();
};
synchronizer.isRunning = function() {
return syncRunning;
};
2013-04-01 16:46:48 +00:00
synchronizer.isQueueEmpty = function() {
return syncUpQueue.length === 1;
};
2013-04-01 16:46:48 +00:00
return synchronizer;
2013-04-02 18:42:47 +00:00
});