Last commit before realtime drop

This commit is contained in:
benweet 2014-04-12 23:30:18 +01:00
parent 3b30813e3d
commit 5f380b461a
8 changed files with 163 additions and 106 deletions

View File

@ -374,7 +374,7 @@ define([
if(conflictList.length) {
eventMgr.onMessage('"' + remoteTitle + '" has conflicts that you have to review.');
}
}), this);
}, this));
}
// Return remote CRCs

View File

@ -198,7 +198,7 @@ define([
// Refresh conversation if popover is open
var context = currentContext;
if(context.discussionIndex) {
context.popoverElt.querySelector('.discussion-comment-list').innerHTML = getDiscussionComments();
context.getPopoverElt().querySelector('.discussion-comment-list').innerHTML = getDiscussionComments();
}
try {
cssApplier.undoToRange(context.rangyRange);

View File

@ -47,7 +47,6 @@ define([
var $removeButtonElt = $(removeButtonElt);
var syncAttributes = fileDesc.syncLocations[$removeButtonElt.data('syncIndex')];
$removeButtonElt.click(function() {
synchronizer.tryStopRealtimeSync();
fileDesc.removeSyncLocation(syncAttributes);
eventMgr.onSyncRemoved(fileDesc, syncAttributes);
});

View File

@ -562,6 +562,30 @@ define([
dataType: file.isRealtime ? 'json' : 'text',
timeout: constants.AJAX_TIMEOUT
}).done(function(data) {
if(file.isRealtime) {
data = data.data.value;
data = {
content: data.content.value,
discussionList: (function() {
var discussionList = {};
data.discussionList && _.each(data.discussionList.value, function(discussionObject) {
var discussion = {
discussionIndex: discussionObject.value.discussionIndex.json,
selectionStart: discussionObject.value.selectionStart.json,
selectionEnd: discussionObject.value.selectionEnd.json,
};
var type = (discussionObject.value.type || {}).json;
type && (discussion.type = type);
var commentList = (discussionObject.value.commentList || {}).value || [];
commentList.length && (discussion.commentList = commentList.map(function(commentObject) {
return commentObject.json;
}));
discussionList[discussion.discussionIndex] = discussion;
});
return discussionList;
})()
};
}
file.content = data;
objects.shift();
task.chain(recursiveDownloadContent);

View File

@ -290,12 +290,32 @@ define([
// Realtime closure
var realtimeContext;
(function() {
var inCompoundOperation = false;
function modelChangeWrapper(cb) {
realtimeContext.isChanging = true;
try { cb(); }
finally {
if(inCompoundOperation) {
try { realtimeContext.model.endCompoundOperation(); }
catch(e) {}
inCompoundOperation = false;
}
realtimeContext.isChanging = false;
}
}
function compound(cb) {
if(!inCompoundOperation) {
realtimeContext.model.beginCompoundOperation();
inCompoundOperation = true;
}
cb();
}
function toRealtimeDiscussion(context, discussion) {
var realtimeCommentList = context.model.createList();
realtimeCommentList.addEventListener(gapi.drive.realtime.EventType.VALUES_ADDED, modelEventListener);
realtimeCommentList.addEventListener(gapi.drive.realtime.EventType.VALUES_REMOVED, modelEventListener);
realtimeCommentList.addEventListener(gapi.drive.realtime.EventType.VALUES_SET, modelEventListener);
realtimeCommentList.addEventListener(gapi.drive.realtime.EventType.VALUES_ADDED, onModelChange);
realtimeCommentList.addEventListener(gapi.drive.realtime.EventType.VALUES_REMOVED, onModelChange);
realtimeCommentList.addEventListener(gapi.drive.realtime.EventType.VALUES_SET, onModelChange);
discussion.commentList && discussion.commentList.forEach(function(comment) {
realtimeCommentList.push({
author: comment.author,
@ -322,6 +342,10 @@ define([
}
function fromRealtimeDiscussion(realtimeDiscussion) {
var realtimeCommentList = realtimeDiscussion.get('commentList');
realtimeCommentList.addEventListener(gapi.drive.realtime.EventType.VALUES_ADDED, onModelChange);
realtimeCommentList.addEventListener(gapi.drive.realtime.EventType.VALUES_REMOVED, onModelChange);
realtimeCommentList.addEventListener(gapi.drive.realtime.EventType.VALUES_SET, onModelChange);
var discussion = {
discussionIndex: realtimeDiscussion.get('discussionIndex'),
selectionStart: realtimeDiscussion.get('selectionStart'),
@ -329,7 +353,7 @@ define([
};
var type = realtimeDiscussion.get('type');
type && (discussion.type = type);
var commentList = realtimeDiscussion.get('commentList').asArray();
var commentList = realtimeCommentList.asArray();
commentList.length && (discussion.commentList = commentList);
return discussion;
}
@ -344,12 +368,14 @@ define([
return localDiscussionList;
}
function mergeDiscussion(localDiscussion, realtimeDiscussion, isServerChange) {
function mergeDiscussion(localDiscussion, realtimeDiscussion, isModelChange) {
var commentsChanged = false;
// We only pay attention to local selection modifications
if(!isServerChange) {
realtimeDiscussion.set('selectionStart', localDiscussion.selectionStart);
realtimeDiscussion.set('selectionEnd', localDiscussion.selectionEnd);
// We don't pay attention to model selection modifications
if(!isModelChange) {
compound(function() {
realtimeDiscussion.set('selectionStart', localDiscussion.selectionStart);
realtimeDiscussion.set('selectionEnd', localDiscussion.selectionEnd);
});
}
function isInDiscussion(comment, commentList) {
return commentList.some(function(commentInDiscussion) {
@ -362,21 +388,25 @@ define([
var localCommentList = localDiscussion.commentList;
function checkLocalComment(comment, index) {
if(!isInDiscussion(comment, realtimeCommentList.asArray())) {
if(isServerChange) {
if(isModelChange) {
localCommentList.splice(index, 1);
commentsChanged = true;
return true;
}
else {
realtimeCommentList.push(comment);
compound(function() {
realtimeCommentList.push(comment);
});
}
}
}
while(localCommentList.some(checkLocalComment)) {}
function checkRealtimeComment(comment, index) {
if(!isInDiscussion(comment, localCommentList)) {
if(!isServerChange) {
realtimeCommentList.remove(index);
if(!isModelChange) {
compound(function() {
realtimeCommentList.remove(index);
});
return true;
}
else {
@ -389,17 +419,19 @@ define([
return commentsChanged;
}
function mergeDiscussionList(context, isServerChange) {
function mergeDiscussionList(context, isModelChange) {
var commentsChanged = false;
var localDiscussionList = context.fileDesc.discussionList;
_.values(localDiscussionList).forEach(function(localDiscussion) {
var realtimeDiscussion = context.realtimeDiscussionList.get(localDiscussion.discussionIndex);
if(realtimeDiscussion) {
commentsChanged |= mergeDiscussion(localDiscussion, realtimeDiscussion, isServerChange);
commentsChanged |= mergeDiscussion(localDiscussion, realtimeDiscussion, isModelChange);
}
else if(!isServerChange) {
realtimeDiscussion = toRealtimeDiscussion(context, localDiscussion);
context.realtimeDiscussionList.set(localDiscussion.discussionIndex, realtimeDiscussion);
else if(!isModelChange) {
compound(function() {
realtimeDiscussion = toRealtimeDiscussion(context, localDiscussion);
context.realtimeDiscussionList.set(localDiscussion.discussionIndex, realtimeDiscussion);
});
}
else {
delete localDiscussionList[localDiscussion.discussionIndex];
@ -410,15 +442,17 @@ define([
var realtimeDiscussion = context.realtimeDiscussionList.get(discussionIndex);
var localDiscussion = localDiscussionList[discussionIndex];
if(localDiscussion) {
commentsChanged |= mergeDiscussion(localDiscussion, realtimeDiscussion, isServerChange);
commentsChanged |= mergeDiscussion(localDiscussion, realtimeDiscussion, isModelChange);
}
else if(isServerChange) {
else if(isModelChange) {
var discussion = fromRealtimeDiscussion(realtimeDiscussion);
localDiscussionList[discussionIndex] = discussion;
eventMgr.onDiscussionCreated(context.fileDesc, discussion);
}
else {
context.realtimeDiscussionList.delete(discussionIndex);
compound(function() {
context.realtimeDiscussionList.delete(discussionIndex);
});
}
});
context.fileDesc.discussionList = localDiscussionList; // Write in localStorage
@ -451,59 +485,55 @@ define([
}
var onChange = (function() {
var debouncedOnChange = _.debounce(function() {
var debouncedOnChange = utils.debounce(function() {
var context = realtimeContext;
if(!context) {
return;
}
if(context.isServerChange) {
logger.log('Realtime syncing remote changes');
if(context.isModelChange) {
logger.log('Realtime syncing model changes');
}
else {
// Model is supposed to be updated on local modifications
context.model.beginCompoundOperation();
logger.log('Realtime syncing local changes');
}
// Check content modifications
var localContent = context.fileDesc.content;
var remoteContent = context.realtimeString.getText();
var contentChanged = localContent != remoteContent;
if(contentChanged) {
if(context.isServerChange) {
editor.setValue(remoteContent);
modelChangeWrapper(function() {
// Check content modifications
var localContent = context.fileDesc.content;
var remoteContent = context.realtimeString.getText();
var contentChanged = localContent != remoteContent;
if(contentChanged) {
if(context.isModelChange) {
editor.setValue(remoteContent);
}
else {
compound(function() {
context.realtimeString.setText(localContent);
});
}
}
else {
context.realtimeString.setText(localContent);
// Check discussion modifications
mergeDiscussionList(context, context.isModelChange);
// For local changes, CRCs are updated on "save success" event
if(context.isModelChange) {
updateStatus();
}
}
// Check discussion modifications
mergeDiscussionList(context, context.isServerChange);
// For local changes, CRCs are updated on "save success" event
if(context.isServerChange) {
updateStatus();
}
else {
context.model.endCompoundOperation();
}
context.isServerChange = false;
}, 0);
context.isModelChange = false;
});
});
return function(fileDesc) {
if(realtimeContext && realtimeContext.fileDesc === fileDesc) {
debouncedOnChange();
}
};
})();
function modelEventListener(evt) {
if(!realtimeContext) {
function onModelChange() {
if(!realtimeContext || realtimeContext.isChanging) {
return;
}
if(evt.isLocal === false) {
realtimeContext.isServerChange = true;
}
realtimeContext.isModelChange = true;
onChange(realtimeContext.fileDesc);
}
eventMgr.addListener('onContentChanged', onChange);
@ -513,6 +543,9 @@ define([
// Start realtime synchronization
gdriveProvider.startRealtimeSync = function(fileDesc, syncAttributes) {
if(realtimeContext !== undefined) {
return;
}
var context = {
fileDesc: fileDesc,
syncAttributes: syncAttributes
@ -520,6 +553,9 @@ define([
realtimeContext = context;
googleHelper.loadRealtime(syncAttributes.id, accountId, function(err, doc) {
if(err || !doc) {
if(context === realtimeContext) {
realtimeContext = undefined;
}
return;
}
@ -542,8 +578,8 @@ define([
}
context.realtimeString = realtimeString;
// Listen to content modifications
realtimeString.addEventListener(gapi.drive.realtime.EventType.TEXT_INSERTED, modelEventListener);
realtimeString.addEventListener(gapi.drive.realtime.EventType.TEXT_DELETED, modelEventListener);
realtimeString.addEventListener(gapi.drive.realtime.EventType.TEXT_INSERTED, onModelChange);
realtimeString.addEventListener(gapi.drive.realtime.EventType.TEXT_DELETED, onModelChange);
// Get or create discussion map
var realtimeDiscussionList = model.getRoot().get('discussionList');
@ -554,14 +590,14 @@ define([
}
context.realtimeDiscussionList = realtimeDiscussionList;
// Listen to discussion modifications
realtimeDiscussionList.addEventListener(gapi.drive.realtime.EventType.VALUE_CHANGED, modelEventListener);
realtimeDiscussionList.addEventListener(gapi.drive.realtime.EventType.VALUE_CHANGED, onModelChange);
realtimeDiscussionList.keys().forEach(function(discussionIndex) {
var realtimeDiscussion = context.realtimeDiscussionList.get(discussionIndex);
var realtimeCommentList = realtimeDiscussion.get('commentList');
// Listen to comment modifications in every discussion
realtimeCommentList.addEventListener(gapi.drive.realtime.EventType.VALUES_ADDED, modelEventListener);
realtimeCommentList.addEventListener(gapi.drive.realtime.EventType.VALUES_REMOVED, modelEventListener);
realtimeCommentList.addEventListener(gapi.drive.realtime.EventType.VALUES_SET, modelEventListener);
realtimeCommentList.addEventListener(gapi.drive.realtime.EventType.VALUES_ADDED, onModelChange);
realtimeCommentList.addEventListener(gapi.drive.realtime.EventType.VALUES_REMOVED, onModelChange);
realtimeCommentList.addEventListener(gapi.drive.realtime.EventType.VALUES_SET, onModelChange);
});
// Also listen to "save success" event
@ -577,6 +613,7 @@ define([
var remoteDiscussionList = fromRealtimeDiscussionList(realtimeDiscussionList);
var remoteDiscussionListJSON = JSON.stringify(remoteDiscussionList);
gdriveProvider.syncMerge(fileDesc, syncAttributes, remoteContent, remoteTitle, remoteDiscussionList, remoteDiscussionListJSON);
onChange(context.fileDesc);
// Save undo/redo buttons default actions
undoExecute = pagedownEditor.uiManager.buttons.undo.execute;
@ -584,8 +621,8 @@ define([
setUndoRedoButtonStates = pagedownEditor.uiManager.setUndoRedoButtonStates;
// Set temporary actions for undo/redo buttons
pagedownEditor.uiManager.buttons.undo.execute = model.undo;
pagedownEditor.uiManager.buttons.redo.execute = model.redo;
pagedownEditor.uiManager.buttons.undo.execute = _.bind(model.undo, model);
pagedownEditor.uiManager.buttons.redo.execute = _.bind(model.redo, model);
// Add event handler for model's UndoRedoStateChanged events
pagedownEditor.uiManager.setUndoRedoButtonStates = _.debounce(function() {
@ -609,7 +646,7 @@ define([
gdriveProvider.stopRealtimeSync();
}
else if(err.isFatal) {
eventMgr.onError('Real time synchronization is temporarily unavailable.');
// Retry will be attempted shortly...
gdriveProvider.stopRealtimeSync();
}
});
@ -619,6 +656,8 @@ define([
gdriveProvider.stopRealtimeSync = function() {
logger.log("Stopping Google Drive realtime synchronization");
if(realtimeContext !== undefined) {
try { realtimeContext.model.endCompoundOperation(); }
catch(e) {}
realtimeContext.document && realtimeContext.document.close();
realtimeContext = undefined;
}

View File

@ -63,8 +63,8 @@ define([
// Clean fields from deleted files in local storage
Object.keys(storage).forEach(function(key) {
var match = key.match(/(publish\.\S+?)\.\S+/);
if(match && !publishIndexMap.hasOwnProperty(match[1])) {
var match = key.match(/publish\.\S+/);
if(match && !publishIndexMap.hasOwnProperty(match[0])) {
storage.removeItem(key);
}
});

View File

@ -52,8 +52,8 @@ define([
// Clean fields from deleted files in local storage
Object.keys(storage).forEach(function(key) {
var match = key.match(/(sync\.\S+?)\.\S+/);
if(match && !syncIndexMap.hasOwnProperty(match[1])) {
var match = key.match(/sync\.\S+/);
if(match && !syncIndexMap.hasOwnProperty(match[0])) {
storage.removeItem(key);
}
});
@ -241,28 +241,16 @@ define([
* Realtime synchronization
**************************************************************************/
var realtimeFileDesc;
var realtimeSyncAttributes;
var isOnline = true;
// Determines if open file has real time sync location and tries to start
// real time sync
function onFileOpen(fileDesc) {
realtimeFileDesc = _.some(fileDesc.syncLocations, function(syncAttributes) {
realtimeSyncAttributes = syncAttributes;
return syncAttributes.isRealtime;
}) ? fileDesc : undefined;
tryStartRealtimeSync();
}
// Tries to start/stop real time sync on online/offline event
function onOfflineChanged(isOfflineParam) {
if(isOfflineParam === false) {
isOnline = true;
tryStartRealtimeSync();
startRealtimeSync();
}
else {
synchronizer.tryStopRealtimeSync();
stopRealtimeSync();
isOnline = false;
}
}
@ -270,24 +258,36 @@ define([
// Starts real time synchronization if:
// 1. current file has real time sync location
// 2. we are online
function tryStartRealtimeSync() {
if(realtimeFileDesc !== undefined && isOnline === true) {
realtimeSyncAttributes.provider.startRealtimeSync(realtimeFileDesc, realtimeSyncAttributes);
}
function startRealtimeSync() {
var fileDesc = fileMgr.currentFile;
_.each(fileDesc.syncLocations, function(syncAttributes) {
syncAttributes.isRealtime && syncAttributes.provider.startRealtimeSync(fileDesc, syncAttributes);
});
}
// Stops previously started synchronization if any
synchronizer.tryStopRealtimeSync = function() {
if(realtimeFileDesc !== undefined && isOnline === true) {
realtimeSyncAttributes.provider.stopRealtimeSync();
}
};
function stopRealtimeSync() {
_.each(providerMap, function(provider) {
provider.stopRealtimeSync && provider.stopRealtimeSync();
});
}
// Triggers realtime synchronization from eventMgr events
if(window.viewerMode === false) {
eventMgr.addListener("onFileOpen", onFileOpen);
eventMgr.addListener("onFileClosed", synchronizer.tryStopRealtimeSync);
// On file open, try to start realtime sync
eventMgr.addListener("onFileOpen", startRealtimeSync);
// On new sync location, try to start realtime sync
eventMgr.addListener("onSyncExportSuccess", startRealtimeSync);
// On file close, stop any active realtime synchronization
eventMgr.addListener("onFileClosed", stopRealtimeSync);
// Start/stop realtime sync depending on network status
eventMgr.addListener("onOfflineChanged", onOfflineChanged);
// Try to start realtime sync every 15 sec in case of error
eventMgr.addListener("onPeriodicRun", _.throttle(startRealtimeSync, 15000));
// Stop realtime sync if synchronized location is removed
eventMgr.addListener("onSyncRemoved", function(fileDesc, syncAttributes) {
fileDesc === fileMgr.currentFile && syncAttributes.isRealtime && syncAttributes.provider.stopRealtimeSync();
});
}
/***************************************************************************
@ -368,11 +368,6 @@ define([
syncAttributes.isRealtime = true;
fileDesc.addSyncLocation(syncAttributes);
eventMgr.onSyncExportSuccess(fileDesc, syncAttributes);
// Start the real time sync
realtimeFileDesc = fileDesc;
realtimeSyncAttributes = syncAttributes;
tryStartRealtimeSync();
});
}
else {

View File

@ -13,7 +13,7 @@ define([
// Faster than setTimeout (see http://dbaron.org/log/20100309-faster-timeouts)
utils.defer = (function() {
var timeouts = [];
var messageName = "delay";
var messageName = "deferMsg";
window.addEventListener("message", function(evt) {
if(evt.source == window && evt.data == messageName) {
evt.stopPropagation();