Stackedit/js/synchronizer.js

427 lines
14 KiB
JavaScript
Raw Normal View History

2013-04-20 00:14:20 +00:00
define(["jquery", "google-helper", "dropbox-helper", "dropbox-provider", "gdrive-provider"], function($, googleHelper, dropboxHelper) {
2013-04-01 16:46:48 +00:00
var synchronizer = {};
2013-04-02 18:42:47 +00:00
// Dependencies
2013-04-10 18:14:59 +00:00
var core = undefined;
2013-04-02 18:42:47 +00:00
var fileManager = undefined;
2013-04-20 00:14:20 +00:00
// 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();
2013-04-02 18:42:47 +00:00
// 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
2013-04-10 18:14:59 +00:00
// Used to know if user can force synchronization
var uploadPending = false;
2013-04-10 23:13:31 +00:00
// Allows external modules to update uploadPending flag
2013-04-10 18:14:59 +00:00
synchronizer.notifyChange = function(fileIndex) {
// Check that file has synchronized locations
2013-04-10 23:13:31 +00:00
if(localStorage[fileIndex + ".sync"].length !== 1) {
uploadPending = true;
synchronizer.updateSyncButton();
}
};
// Used to enable/disable the synchronization button
synchronizer.updateSyncButton = function() {
if(syncRunning === true || uploadPending === false || core.isOffline) {
$(".action-force-sync").addClass("disabled");
}
else {
$(".action-force-sync").removeClass("disabled");
2013-04-01 16:46:48 +00:00
}
};
2013-04-10 23:13:31 +00:00
// Force the synchronization
synchronizer.forceSync = function() {
lastSync = 0;
synchronizer.sync();
};
2013-04-01 16:46:48 +00:00
// Recursive function to upload a single file on multiple locations
2013-04-10 18:14:59 +00:00
var uploadFileSyncIndexList = [];
var uploadContent = undefined;
var uploadContentCRC = undefined;
var uploadTitle = undefined;
var uploadTitleCRC = undefined;
function locationUp(callback) {
// No more synchronized location for this document
if (uploadFileSyncIndexList.length === 0) {
fileUp(callback);
return;
}
// Dequeue a synchronized location
2013-04-20 00:14:20 +00:00
var syncIndex = uploadFileSyncIndexList.pop();
var syncAttributes = JSON.parse(localStorage[fileSyncIndex]);
=
2013-04-09 07:58:06 +00:00
var syncContentCRC = localStorage[fileSyncIndex + ".contentCRC"];
var syncTitleCRC = localStorage[fileSyncIndex + ".titleCRC"];
2013-04-20 00:14:20 +00:00
// Skip if CRC has not changed
2013-04-10 18:14:59 +00:00
if(uploadContentCRC == syncContentCRC && (syncTitleCRC === undefined || uploadTitleCRC == syncTitleCRC)) {
locationUp(callback);
2013-04-09 07:58:06 +00:00
return;
}
2013-04-10 18:14:59 +00:00
// If upload is going to run, go for an other upload cycle at the end
uploadCycle = true;
// When page is refreshed, this flag is false but should be true here
uploadPending = true;
2013-04-09 07:58:06 +00:00
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-20 00:14:20 +00:00
googleHelper.upload(id, undefined, uploadTitle, uploadContent, function(error, result) {
if(error) {
// If error we abort the synchronization (retry later)
callback(error);
2013-04-07 15:22:13 +00:00
return;
}
2013-04-20 00:14:20 +00:00
localStorage[fileSyncIndex + ".contentCRC"] = uploadContentCRC;
localStorage[fileSyncIndex + ".titleCRC"] = uploadTitleCRC;
locationUp(callback);
2013-04-07 15:22:13 +00:00
});
} else if (fileSyncIndex.indexOf(SYNC_PROVIDER_DROPBOX) === 0) {
var path = fileSyncIndex.substring(SYNC_PROVIDER_DROPBOX.length);
path = decodeURIComponent(path);
2013-04-16 15:02:24 +00:00
dropboxHelper.upload(path, uploadContent, function(error, result) {
if (error) {
// If error we abort the synchronization (retry later)
callback(error);
2013-04-01 16:46:48 +00:00
return;
}
2013-04-16 15:02:24 +00:00
localStorage[fileSyncIndex + ".contentCRC"] = uploadContentCRC;
locationUp(callback);
2013-04-01 16:46:48 +00:00
});
} else {
2013-04-10 18:14:59 +00:00
// This should never happen
console.error("Invalid fileSyncIndex: " + fileSyncIndex);
callback("error");
2013-04-01 16:46:48 +00:00
}
}
2013-04-10 18:14:59 +00:00
// Recursive function to upload multiple files
var uploadFileIndexList = [];
function fileUp(callback) {
// No more fileIndex to synchronize
if (uploadFileIndexList.length === 0) {
syncUp(callback);
return;
}
// Dequeue a fileIndex
var fileIndex = uploadFileIndexList.pop();
var fileSyncIndexes = localStorage[fileIndex + ".sync"];
if(!fileIndex || fileSyncIndexes.length === 1) {
fileUp(callback);
2013-04-01 16:46:48 +00:00
return;
}
2013-04-10 18:14:59 +00:00
// Get document title/content
uploadContent = localStorage[fileIndex + ".content"];
uploadContentCRC = core.crc32(uploadContent);
uploadTitle = localStorage[fileIndex + ".title"];
uploadTitleCRC = core.crc32(uploadTitle);
2013-04-01 16:46:48 +00:00
2013-04-10 18:14:59 +00:00
// Parse the list of synchronized locations associated to the document
2013-04-20 00:14:20 +00:00
uploadFileSyncIndexList = _.compact(fileSyncIndexes.split(";"));
2013-04-10 18:14:59 +00:00
locationUp(callback);
}
2013-04-01 16:46:48 +00:00
2013-04-10 18:14:59 +00:00
// Used to upload document changes from local storage
var uploadCycle = false;
function syncUp(callback) {
if(uploadCycle === true) {
// New upload cycle
uploadCycle = false;
uploadFileIndexList = localStorage["file.list"].split(";");
fileUp(callback);
}
else {
callback();
}
}
2013-04-01 16:46:48 +00:00
2013-04-10 18:14:59 +00:00
// Used to download file changes from Google Drive
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-20 00:14:20 +00:00
googleHelper.checkUpdates(lastChangeId, function(error, changes, newChangeId) {
if (error) {
callback(error);
2013-04-01 16:46:48 +00:00
return;
}
2013-04-20 00:14:20 +00:00
googleHelper.downloadContent(changes, function(error, changes) {
if (error) {
callback(error);
2013-04-01 16:46:48 +00:00
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-12 09:49:40 +00:00
// No file corresponding (file may have been deleted locally)
2013-04-01 16:46:48 +00:00
if(fileIndex === undefined) {
2013-04-12 09:49:40 +00:00
fileManager.removeSync(fileSyncIndex);
2013-04-01 16:46:48 +00:00
continue;
}
2013-04-10 18:14:59 +00:00
var localTitle = localStorage[fileIndex + ".title"];
2013-04-01 16:46:48 +00:00
// File deleted
if (change.deleted === true) {
fileManager.removeSync(fileSyncIndex);
updateFileTitles = true;
2013-04-10 18:14:59 +00:00
core.showMessage('"' + localTitle + '" has been removed from Google Drive.');
2013-04-01 16:46:48 +00:00
continue;
}
2013-04-11 22:38:41 +00:00
var localTitleChanged = localStorage[fileSyncIndex + ".titleCRC"] != core.crc32(localTitle);
2013-04-10 18:14:59 +00:00
var localContent = localStorage[fileIndex + ".content"];
2013-04-11 22:38:41 +00:00
var localContentChanged = localStorage[fileSyncIndex + ".contentCRC"] != core.crc32(localContent);
2013-04-01 16:46:48 +00:00
var file = change.file;
2013-04-10 18:14:59 +00:00
var fileTitleChanged = localTitle != file.title;
var fileContentChanged = localContent != file.content;
// Conflict detection
if ((fileTitleChanged === true && localTitleChanged === true)
|| (fileContentChanged === true && localContentChanged === true)) {
fileManager.createFile(localTitle + " (backup)", localContent);
2013-04-01 16:46:48 +00:00
updateFileTitles = true;
2013-04-10 18:14:59 +00:00
core.showMessage('Conflict detected on "' + localTitle + '". A backup has been created locally.');
2013-04-01 16:46:48 +00:00
}
// If file title changed
2013-04-10 18:14:59 +00:00
if(fileTitleChanged) {
2013-04-01 16:46:48 +00:00
localStorage[fileIndex + ".title"] = file.title;
updateFileTitles = true;
2013-04-10 18:14:59 +00:00
core.showMessage('"' + localTitle + '" has been renamed to "' + file.title + '" on Google Drive.');
2013-04-01 16:46:48 +00:00
}
// If file content changed
2013-04-10 18:14:59 +00:00
if(fileContentChanged) {
2013-04-01 16:46:48 +00:00
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-13 18:11:54 +00:00
if(fileManager.isCurrentFileIndex(fileIndex)) {
2013-04-01 16:46:48 +00:00
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-10 18:14:59 +00:00
localStorage[fileSyncIndex + ".contentCRC"] = core.crc32(file.content);
localStorage[fileSyncIndex + ".titleCRC"] = core.crc32(file.title);
2013-04-01 16:46:48 +00:00
}
if(updateFileTitles) {
fileManager.updateFileTitles();
}
localStorage[SYNC_PROVIDER_GDRIVE
+ "lastChangeId"] = newChangeId;
callback();
});
});
}
2013-04-10 18:14:59 +00:00
// Used to download file changes from Dropbox
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-16 15:02:24 +00:00
dropboxHelper.checkUpdates(lastChangeId, function(error, changes, newChangeId) {
if (error) {
callback(error);
2013-04-07 15:22:13 +00:00
return;
}
2013-04-16 15:02:24 +00:00
dropboxHelper.downloadContent(changes, function(error, changes) {
if (error) {
callback(error);
2013-04-07 15:22:13 +00:00
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);
2013-04-12 09:49:40 +00:00
// No file corresponding (file may have been deleted locally)
2013-04-07 15:22:13 +00:00
if(fileIndex === undefined) {
2013-04-12 09:49:40 +00:00
fileManager.removeSync(fileSyncIndex);
2013-04-07 15:22:13 +00:00
continue;
}
2013-04-10 18:14:59 +00:00
var localTitle = localStorage[fileIndex + ".title"];
2013-04-07 15:22:13 +00:00
// File deleted
if (change.wasRemoved === true) {
fileManager.removeSync(fileSyncIndex);
updateFileTitles = true;
2013-04-10 18:14:59 +00:00
core.showMessage('"' + localTitle + '" has been removed from Dropbox.');
2013-04-07 15:22:13 +00:00
continue;
}
2013-04-10 18:14:59 +00:00
var localContent = localStorage[fileIndex + ".content"];
2013-04-11 22:38:41 +00:00
var localContentChanged = localStorage[fileSyncIndex + ".contentCRC"] != core.crc32(localContent);
2013-04-07 15:22:13 +00:00
var file = change.stat;
2013-04-10 18:14:59 +00:00
var fileContentChanged = localContent != file.content;
// Conflict detection
if (fileContentChanged === true && localContentChanged === true) {
fileManager.createFile(localTitle + " (backup)", localContent);
2013-04-07 15:22:13 +00:00
updateFileTitles = true;
2013-04-10 18:14:59 +00:00
core.showMessage('Conflict detected on "' + localTitle + '". A backup has been created locally.');
2013-04-07 15:22:13 +00:00
}
// If file content changed
2013-04-10 18:14:59 +00:00
if(fileContentChanged) {
2013-04-07 15:22:13 +00:00
localStorage[fileIndex + ".content"] = file.content;
2013-04-10 18:14:59 +00:00
core.showMessage('"' + localTitle + '" has been updated from Dropbox.');
2013-04-13 18:11:54 +00:00
if(fileManager.isCurrentFileIndex(fileIndex)) {
2013-04-07 15:22:13 +00:00
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-10 18:14:59 +00:00
localStorage[fileSyncIndex + ".contentCRC"] = core.crc32(file.content);
2013-04-07 15:22:13 +00:00
}
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-10 18:14:59 +00:00
uploadCycle = true;
2013-04-02 18:42:47 +00:00
lastSync = core.currentTime;
2013-04-10 18:14:59 +00:00
synchronizer.updateSyncButton();
function isError(error) {
if(error !== undefined) {
syncRunning = false;
synchronizer.updateSyncButton();
return true;
}
return false;
}
2013-04-01 16:46:48 +00:00
2013-04-10 18:14:59 +00:00
syncDown(function(error) {
if(isError(error)) {
return;
}
syncUp(function(error) {
if(isError(error)) {
return;
}
2013-04-01 16:46:48 +00:00
syncRunning = false;
2013-04-10 18:14:59 +00:00
uploadPending = false;
2013-04-01 16:46:48 +00:00
});
});
};
2013-04-20 00:14:20 +00:00
// 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);
});
};
2013-04-10 18:14:59 +00:00
synchronizer.init = function(coreModule, fileManagerModule) {
core = coreModule;
fileManager = fileManagerModule;
2013-04-20 00:14:20 +00:00
// 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 + '.');
});
});
});
2013-04-10 18:14:59 +00:00
synchronizer.updateSyncButton();
$(".action-force-sync").click(function() {
if(!$(this).hasClass("disabled")) {
synchronizer.forceSync();
}
});
};
2013-04-01 16:46:48 +00:00
return synchronizer;
2013-04-02 18:42:47 +00:00
});