Stackedit/js/synchronizer.js

362 lines
13 KiB
JavaScript
Raw Normal View History

2013-05-26 22:59:03 +00:00
define([
"jquery",
2013-05-27 19:45:33 +00:00
"underscore",
2013-05-26 22:59:03 +00:00
"core",
"utils",
2013-06-10 21:22:32 +00:00
"extensionMgr",
"fileSystem",
"fileMgr",
2013-06-22 23:48:57 +00:00
"classes/Provider",
2013-06-10 21:22:32 +00:00
"providers/dropboxProvider",
"providers/gdriveProvider"
2013-06-22 23:48:57 +00:00
], function($, _, core, utils, extensionMgr, fileSystem, fileMgr, Provider) {
2013-04-01 16:46:48 +00:00
2013-05-29 19:55:23 +00:00
var synchronizer = {};
2013-04-01 16:46:48 +00:00
2013-05-29 19:55:23 +00:00
// Create a map with providerId: providerModule
var providerMap = _.chain(arguments).map(function(argument) {
2013-06-22 23:48:57 +00:00
return argument instanceof Provider && [
2013-05-29 19:55:23 +00:00
argument.providerId,
argument
];
}).compact().object().value();
2013-04-01 16:46:48 +00:00
2013-05-29 19:55:23 +00:00
// Retrieve sync locations from localStorage
_.each(fileSystem, function(fileDesc) {
2013-06-03 22:19:52 +00:00
_.each(utils.retrieveIndexArray(fileDesc.fileIndex + ".sync"), function(syncIndex) {
try {
var syncAttributes = JSON.parse(localStorage[syncIndex]);
// Store syncIndex
syncAttributes.syncIndex = syncIndex;
// Replace provider ID by provider module in attributes
var provider = providerMap[syncAttributes.provider];
if(!provider) {
throw new Error("Invalid provider ID: " + syncAttributes.provider);
}
syncAttributes.provider = provider;
2013-06-03 22:19:52 +00:00
fileDesc.syncLocations[syncIndex] = syncAttributes;
}
catch(e) {
// localStorage can be corrupted
extensionMgr.onError(e);
// Remove sync location
utils.removeIndexFromArray(fileDesc.fileIndex + ".sync", syncIndex);
localStorage.removeItem(syncIndex);
}
2013-05-29 19:55:23 +00:00
});
});
2013-07-21 14:38:53 +00:00
// Returns true if at least one file has synchronized location
synchronizer.hasSync = function() {
return _.some(providerMap, function(provider) {
return fileMgr.hasSync(provider);
});
};
2013-04-01 16:46:48 +00:00
2013-07-20 01:08:17 +00:00
/***************************************************************************
* Standard synchronization
**************************************************************************/
2013-05-29 19:55:23 +00:00
// Recursive function to upload a single file on multiple locations
var uploadSyncAttributesList = [];
var uploadContent = undefined;
var uploadContentCRC = undefined;
var uploadTitle = undefined;
var uploadTitleCRC = undefined;
function locationUp(callback) {
2013-04-01 16:46:48 +00:00
2013-05-29 19:55:23 +00:00
// No more synchronized location for this document
if(uploadSyncAttributesList.length === 0) {
fileUp(callback);
return;
}
2013-05-29 19:55:23 +00:00
// Dequeue a synchronized location
var syncAttributes = uploadSyncAttributesList.pop();
2013-06-16 18:29:54 +00:00
2013-07-20 01:08:17 +00:00
// Skip real time synchronized location
if(syncAttributes.isRealtime === true) {
locationUp(callback);
return;
}
2013-05-29 19:55:23 +00:00
// Use the specified provider to perform the upload
syncAttributes.provider.syncUp(uploadContent, uploadContentCRC, uploadTitle, uploadTitleCRC, syncAttributes, function(error, uploadFlag) {
if(uploadFlag === true) {
// If uploadFlag is true, request another upload cycle
uploadCycle = true;
}
if(error) {
callback(error);
return;
}
if(uploadFlag) {
// Update syncAttributes in localStorage
utils.storeAttributes(syncAttributes);
}
locationUp(callback);
});
}
2013-04-10 18:14:59 +00:00
2013-05-29 19:55:23 +00:00
// Recursive function to upload multiple files
var uploadFileList = [];
function fileUp(callback) {
// No more fileDesc to synchronize
if(uploadFileList.length === 0) {
syncUp(callback);
return;
}
// Dequeue a fileDesc to synchronize
var fileDesc = uploadFileList.pop();
uploadSyncAttributesList = _.values(fileDesc.syncLocations);
if(uploadSyncAttributesList.length === 0) {
fileUp(callback);
return;
}
// Get document title/content
uploadContent = fileDesc.content;
uploadContentCRC = utils.crc32(uploadContent);
uploadTitle = fileDesc.title;
uploadTitleCRC = utils.crc32(uploadTitle);
locationUp(callback);
}
// Entry point for up synchronization (upload changes)
var uploadCycle = false;
function syncUp(callback) {
if(uploadCycle === true) {
// New upload cycle
uploadCycle = false;
uploadFileList = _.values(fileSystem);
fileUp(callback);
}
else {
callback();
}
}
// Recursive function to download changes from multiple providers
var providerList = [];
function providerDown(callback) {
if(providerList.length === 0) {
callback();
return;
}
var provider = providerList.pop();
// Check that provider has files to sync
if(!fileMgr.hasSync(provider)) {
providerDown(callback);
return;
}
// Perform provider's syncDown
provider.syncDown(function(error) {
if(error) {
callback(error);
return;
}
providerDown(callback);
});
}
// Entry point for down synchronization (download changes)
function syncDown(callback) {
providerList = _.values(providerMap);
providerDown(callback);
}
// Main entry point for synchronization
var syncRunning = false;
synchronizer.sync = function() {
2013-06-10 22:32:34 +00:00
// If sync is already running or offline
if(syncRunning || core.isOffline) {
return false;
2013-05-29 19:55:23 +00:00
}
syncRunning = true;
extensionMgr.onSyncRunning(true);
uploadCycle = true;
function isError(error) {
if(error !== undefined) {
syncRunning = false;
extensionMgr.onSyncRunning(false);
return true;
}
return false;
}
syncDown(function(error) {
if(isError(error)) {
return;
}
syncUp(function(error) {
if(isError(error)) {
return;
}
syncRunning = false;
extensionMgr.onSyncRunning(false);
extensionMgr.onSyncSuccess();
});
});
2013-06-10 22:32:34 +00:00
return true;
2013-05-29 19:55:23 +00:00
};
2013-07-18 23:30:28 +00:00
2013-07-20 01:08:17 +00:00
/***************************************************************************
* Realtime synchronization
**************************************************************************/
var realtimeFileDesc = undefined;
var realtimeSyncAttributes = undefined;
var isOnline = true;
// Determines if open file has real time sync location and tries to start
// real time sync
function onFileOpen(fileDesc) {
2013-07-20 01:08:17 +00:00
realtimeFileDesc = _.some(fileDesc.syncLocations, function(syncAttributes) {
realtimeSyncAttributes = syncAttributes;
return syncAttributes.isRealtime;
}) ? fileDesc : undefined;
tryStartRealtimeSync();
}
2013-07-20 01:08:17 +00:00
// Tries to start/stop real time sync on online/offline event
function onOfflineChanged(isOfflineParam) {
if(isOfflineParam === false) {
isOnline = true;
tryStartRealtimeSync();
}
else {
synchronizer.tryStopRealtimeSync();
isOnline = false;
}
}
2013-07-20 01:08:17 +00:00
// Starts real time synchronization if:
// 1. current file has real time sync location
// 2. we are online
function tryStartRealtimeSync() {
if(realtimeFileDesc !== undefined && isOnline === true) {
2013-07-21 22:18:35 +00:00
realtimeSyncAttributes.provider.startRealtimeSync(realtimeFileDesc, realtimeSyncAttributes);
2013-07-20 01:08:17 +00:00
}
}
// Stops previously started synchronization if any
synchronizer.tryStopRealtimeSync = function() {
if(realtimeFileDesc !== undefined && isOnline === true) {
realtimeSyncAttributes.provider.stopRealtimeSync();
}
};
// Triggers realtime synchronization from extensionMgr events
2013-07-18 23:30:28 +00:00
if(viewerMode === false) {
extensionMgr.addHookCallback("onFileOpen", onFileOpen);
2013-07-20 01:08:17 +00:00
extensionMgr.addHookCallback("onFileClosed", synchronizer.tryStopRealtimeSync);
extensionMgr.addHookCallback("onOfflineChanged", onOfflineChanged);
2013-07-18 23:30:28 +00:00
}
2013-07-20 01:08:17 +00:00
/***************************************************************************
* Initialize module
**************************************************************************/
2013-05-29 19:55:23 +00:00
// Initialize the export dialog
function initExportDialog(provider) {
// Reset fields
utils.resetModalInputs();
// Load preferences
2013-06-03 22:19:52 +00:00
var exportPreferences = utils.retrieveIgnoreError(provider.providerId + ".exportPreferences");
if(exportPreferences) {
2013-05-29 19:55:23 +00:00
_.each(provider.exportPreferencesInputIds, function(inputId) {
utils.setInputValue("#input-sync-export-" + inputId, exportPreferences[inputId]);
});
}
// Open dialog box
$("#modal-upload-" + provider.providerId).modal();
}
core.onReady(function() {
// Init each provider
_.each(providerMap, function(provider) {
// Provider's import button
$(".action-sync-import-" + provider.providerId).click(function(event) {
provider.importFiles(event);
});
// Provider's export action
$(".action-sync-export-dialog-" + provider.providerId).click(function() {
initExportDialog(provider);
});
$(".action-sync-export-" + provider.providerId).click(function(event) {
2013-07-18 23:30:28 +00:00
var isRealtime = utils.getInputChecked("#input-sync-export-" + provider.providerId + "-realtime");
2013-06-19 20:33:46 +00:00
var fileDesc = fileMgr.currentFile;
2013-07-18 23:30:28 +00:00
if(isRealtime) {
if(_.size(fileDesc.syncLocations) > 0) {
2013-07-28 10:35:04 +00:00
extensionMgr.onError("Real time collaborative document can't be synchronized with multiple locations");
2013-05-29 19:55:23 +00:00
return;
2013-07-20 01:08:17 +00:00
}
2013-07-18 23:30:28 +00:00
// Perform the provider's real time export
provider.exportRealtimeFile(event, fileDesc.title, fileDesc.content, function(error, syncAttributes) {
if(error) {
return;
}
syncAttributes.isRealtime = true;
fileDesc.addSyncLocation(syncAttributes);
extensionMgr.onSyncExportSuccess(fileDesc, syncAttributes);
2013-07-20 01:08:17 +00:00
// Start the real time sync
realtimeFileDesc = fileDesc;
realtimeSyncAttributes = syncAttributes;
tryStartRealtimeSync();
2013-07-18 23:30:28 +00:00
});
}
else {
2013-07-21 22:18:35 +00:00
if(_.size(fileDesc.syncLocations) > 0 && _.first(_.values(fileDesc.syncLocations)).isRealtime) {
2013-07-28 10:35:04 +00:00
extensionMgr.onError("Real time collaborative document can't be synchronized with multiple locations");
2013-07-18 23:30:28 +00:00
return;
2013-07-20 01:08:17 +00:00
}
2013-07-18 23:30:28 +00:00
// Perform the provider's standard export
provider.exportFile(event, fileDesc.title, fileDesc.content, function(error, syncAttributes) {
if(error) {
return;
}
fileDesc.addSyncLocation(syncAttributes);
extensionMgr.onSyncExportSuccess(fileDesc, syncAttributes);
});
}
2013-05-29 19:55:23 +00:00
// Store input values as preferences for next time we open the
// export dialog
var exportPreferences = {};
_.each(provider.exportPreferencesInputIds, function(inputId) {
exportPreferences[inputId] = $("#input-sync-export-" + inputId).val();
});
localStorage[provider.providerId + ".exportPreferences"] = JSON.stringify(exportPreferences);
});
// Provider's manual export button
$(".action-sync-manual-" + provider.providerId).click(function(event) {
2013-06-19 20:33:46 +00:00
var fileDesc = fileMgr.currentFile;
2013-07-21 22:18:35 +00:00
if(_.size(fileDesc.syncLocations) > 0 && _.first(_.values(fileDesc.syncLocations)).isRealtime) {
2013-07-28 10:35:04 +00:00
extensionMgr.onError("Real time collaborative document can't be synchronized with multiple locations");
2013-07-18 23:30:28 +00:00
return;
2013-07-20 01:08:17 +00:00
}
2013-05-29 19:55:23 +00:00
provider.exportManual(event, fileDesc.title, fileDesc.content, function(error, syncAttributes) {
if(error) {
return;
}
2013-06-16 10:47:35 +00:00
fileDesc.addSyncLocation(syncAttributes);
extensionMgr.onSyncExportSuccess(fileDesc, syncAttributes);
2013-05-29 19:55:23 +00:00
});
});
});
});
extensionMgr.onSynchronizerCreated(synchronizer);
return synchronizer;
2013-04-02 18:42:47 +00:00
});