Implement degraded mode when too many editors are in a pad

pull/1/head
yflory 4 years ago
parent a5232b6cdb
commit f6015e419e

@ -1026,13 +1026,6 @@ define([
}; };
onlyoffice.onEvent = Util.mkEvent(); onlyoffice.onEvent = Util.mkEvent();
// Cursor
var cursor = common.cursor = {};
cursor.execCommand = function (data, cb) {
postMessage("CURSOR_COMMAND", data, cb);
};
cursor.onEvent = Util.mkEvent();
// Mailbox // Mailbox
var mailbox = common.mailbox = {}; var mailbox = common.mailbox = {};
mailbox.execCommand = function (data, cb) { mailbox.execCommand = function (data, cb) {
@ -2138,8 +2131,6 @@ define([
}, },
// OnlyOffice // OnlyOffice
OO_EVENT: common.onlyoffice.onEvent.fire, OO_EVENT: common.onlyoffice.onEvent.fire,
// Cursor
CURSOR_EVENT: common.cursor.onEvent.fire,
// Mailbox // Mailbox
MAILBOX_EVENT: common.mailbox.onEvent.fire, MAILBOX_EVENT: common.mailbox.onEvent.fire,
// Universal // Universal

@ -15,6 +15,7 @@ define(['json.sortify'], function (Sortify) {
var priv = {}; var priv = {};
var dirty = true; var dirty = true;
var history = false; var history = false;
var degraded = true;
var changeHandlers = []; var changeHandlers = [];
var lazyChangeHandlers = []; var lazyChangeHandlers = [];
var titleChangeHandlers = []; var titleChangeHandlers = [];
@ -64,7 +65,7 @@ define(['json.sortify'], function (Sortify) {
if (members.indexOf(x) === -1 && !history) { return; } if (members.indexOf(x) === -1 && !history) { return; }
mdo[x] = metadataObj.users[x]; mdo[x] = metadataObj.users[x];
}); });
if (!priv.readOnly) { if (!priv.readOnly && !degraded) {
mdo[meta.user.netfluxId] = meta.user; mdo[meta.user.netfluxId] = meta.user;
} }
metadataObj.users = mdo; metadataObj.users = mdo;
@ -176,6 +177,10 @@ define(['json.sortify'], function (Sortify) {
getMetadataLazy: function () { getMetadataLazy: function () {
return metadataLazyObj; return metadataLazyObj;
}, },
setDegraded: function (bool) {
degraded = bool;
},
isDegraded: function () { return degraded; },
onTitleChange: function (f) { titleChangeHandlers.push(f); }, onTitleChange: function (f) { titleChangeHandlers.push(f); },
onChange: function (f) { changeHandlers.push(f); }, onChange: function (f) { changeHandlers.push(f); },
onChangeLazy: function (f) { lazyChangeHandlers.push(f); }, onChangeLazy: function (f) { lazyChangeHandlers.push(f); },

@ -1482,17 +1482,6 @@ define([
} }
}; };
// Cursor
Store.cursor = {
execCommand: function (clientId, data, cb) {
// The cursor module can only be used when the store is ready
onReadyEvt.reg(function () {
if (!store.cursor) { return void cb ({error: 'Cursor channel is disabled'}); }
store.cursor.execCommand(clientId, data, cb);
});
}
};
// Mailbox // Mailbox
Store.mailbox = { Store.mailbox = {
execCommand: function (clientId, data, cb) { execCommand: function (clientId, data, cb) {
@ -2335,7 +2324,7 @@ define([
store.messenger.leavePad(chanId); store.messenger.leavePad(chanId);
} catch (e) { console.error(e); } } catch (e) { console.error(e); }
try { try {
store.cursor.leavePad(chanId); store.modules['cursor'].leavePad(chanId);
} catch (e) { console.error(e); } } catch (e) { console.error(e); }
try { try {
store.onlyoffice.leavePad(chanId); store.onlyoffice.leavePad(chanId);
@ -2357,9 +2346,6 @@ define([
if (driveIdx !== -1) { if (driveIdx !== -1) {
driveEventClients.splice(driveIdx, 1); driveEventClients.splice(driveIdx, 1);
} }
try {
store.cursor.removeClient(clientId);
} catch (e) { console.error(e); }
try { try {
store.onlyoffice.removeClient(clientId); store.onlyoffice.removeClient(clientId);
} catch (e) { console.error(e); } } catch (e) { console.error(e); }
@ -2499,17 +2485,6 @@ define([
}); });
}; };
*/ */
var loadCursor = function () {
store.cursor = Cursor.init(store, function (ev, data, clients) {
clients.forEach(function (cId) {
postMessage(cId, 'CURSOR_EVENT', {
ev: ev,
data: data
});
});
});
};
var loadOnlyOffice = function () { var loadOnlyOffice = function () {
store.onlyoffice = OnlyOffice.init(store, function (ev, data, clients) { store.onlyoffice = OnlyOffice.init(store, function (ev, data, clients) {
clients.forEach(function (cId) { clients.forEach(function (cId) {
@ -2656,7 +2631,7 @@ define([
}; };
postMessage(clientId, 'LOADING_DRIVE', data); postMessage(clientId, 'LOADING_DRIVE', data);
}); });
loadCursor(); loadUniversal(Cursor, 'cursor', waitFor);
loadOnlyOffice(); loadOnlyOffice();
loadUniversal(Messenger, 'messenger', waitFor); loadUniversal(Messenger, 'messenger', waitFor);
store.messenger = store.modules['messenger']; store.messenger = store.modules['messenger'];

@ -6,6 +6,8 @@ define([
], function (Util, Constants, Messages, Crypto) { ], function (Util, Constants, Messages, Crypto) {
var Cursor = {}; var Cursor = {};
var DEGRADED = 3; // XXX Number of users before switching to degraded mode
var convertToUint8 = function (obj) { var convertToUint8 = function (obj) {
var l = Object.keys(obj).length; var l = Object.keys(obj).length;
var u = new Uint8Array(l); var u = new Uint8Array(l);
@ -21,6 +23,7 @@ define([
if (!client || !client.cursor) { return; } if (!client || !client.cursor) { return; }
var chan = ctx.channels[client.channel]; var chan = ctx.channels[client.channel];
if (!chan) { return; } if (!chan) { return; }
if (chan.degraded) { return; }
if (!chan.sendMsg) { return; } // Store not synced yet, we're running with the cache if (!chan.sendMsg) { return; } // Store not synced yet, we're running with the cache
var data = { var data = {
id: client.id, id: client.id,
@ -34,6 +37,7 @@ define([
// Send all our cursors data when someone remote joins the channel // Send all our cursors data when someone remote joins the channel
var sendOurCursors = function (ctx, chan) { var sendOurCursors = function (ctx, chan) {
if (chan.degraded) { return; }
chan.clients.forEach(function (c) { chan.clients.forEach(function (c) {
var client = ctx.clients[c]; var client = ctx.clients[c];
if (!client) { return; } if (!client) { return; }
@ -77,6 +81,7 @@ define([
// ==> Send the cursor position of the other tabs // ==> Send the cursor position of the other tabs
chan.clients.forEach(function (cl) { chan.clients.forEach(function (cl) {
var clientObj = ctx.clients[cl]; var clientObj = ctx.clients[cl];
if (chan.degraded) { return; }
if (!clientObj) { return; } if (!clientObj) { return; }
ctx.emit('MESSAGE', { ctx.emit('MESSAGE', {
id: clientObj.id, id: clientObj.id,
@ -90,6 +95,11 @@ define([
return void cb(); return void cb();
} }
var updateDegraded = function (ctx, wc, chan) {
var m = wc.members;
chan.degraded = (m.length-1) >= DEGRADED;
ctx.emit('DEGRADED', { degraded: chan.degraded }, chan.clients);
};
var onOpen = function (wc) { var onOpen = function (wc) {
ctx.channels[channel] = ctx.channels[channel] || {}; ctx.channels[channel] = ctx.channels[channel] || {};
@ -113,11 +123,14 @@ define([
wc.on('join', function () { wc.on('join', function () {
sendOurCursors(ctx, chan); sendOurCursors(ctx, chan);
updateDegraded(ctx, wc, chan);
}); });
wc.on('leave', function (peer) { wc.on('leave', function (peer) {
ctx.emit('MESSAGE', {leave: true, id: peer}, chan.clients); ctx.emit('MESSAGE', {leave: true, id: peer}, chan.clients);
updateDegraded(ctx, wc, chan);
}); });
wc.on('message', function (cryptMsg) { wc.on('message', function (cryptMsg) {
if (chan.degraded) { return; }
var msg = chan.encryptor.decrypt(cryptMsg, secret.keys && secret.keys.validateKey); var msg = chan.encryptor.decrypt(cryptMsg, secret.keys && secret.keys.validateKey);
var parsed; var parsed;
try { try {
@ -129,6 +142,7 @@ define([
} catch (e) { console.error(e); } } catch (e) { console.error(e); }
}); });
chan.wc = wc; chan.wc = wc;
chan.sendMsg = function (msg, cb) { chan.sendMsg = function (msg, cb) {
cb = cb || function () {}; cb = cb || function () {};
@ -144,6 +158,8 @@ define([
chan.clients = [client]; chan.clients = [client];
first = false; first = false;
cb(); cb();
updateDegraded(ctx, wc, chan);
}; };
network.join(channel).then(onOpen, function (err) { network.join(channel).then(onOpen, function (err) {
@ -223,10 +239,10 @@ define([
delete ctx.clients[clientId]; delete ctx.clients[clientId];
}; };
Cursor.init = function (store, emit) { Cursor.init = function (cfg, waitFor, emit) {
var cursor = {}; var cursor = {};
var ctx = { var ctx = {
store: store, store: cfg.store,
emit: emit, emit: emit,
channels: {}, channels: {},
clients: {} clients: {}

@ -67,8 +67,6 @@ define([
ANON_GET_PREVIEW_CONTENT: Store.anonGetPreviewContent, ANON_GET_PREVIEW_CONTENT: Store.anonGetPreviewContent,
// OnlyOffice // OnlyOffice
OO_COMMAND: Store.onlyoffice.execCommand, OO_COMMAND: Store.onlyoffice.execCommand,
// Cursor
CURSOR_COMMAND: Store.cursor.execCommand,
// Mailbox // Mailbox
MAILBOX_COMMAND: Store.mailbox.execCommand, MAILBOX_COMMAND: Store.mailbox.execCommand,
// Universal // Universal

@ -579,7 +579,7 @@ define([
common.openPadChat(onLocal); common.openPadChat(onLocal);
if (!readOnly && cursorGetter) { if (!readOnly && cursorGetter) {
common.openCursorChannel(onLocal); common.openCursorChannel(onLocal);
cursor = common.createCursor(); cursor = common.createCursor(onLocal);
cursor.onCursorUpdate(function (data) { cursor.onCursorUpdate(function (data) {
var newContentStr = cpNfInner.chainpad.getUserDoc(); var newContentStr = cpNfInner.chainpad.getUserDoc();
var hjson = normalize(JSON.parse(newContentStr)); var hjson = normalize(JSON.parse(newContentStr));

@ -530,6 +530,9 @@ define([
} }
}; };
exp.setRemoteCursor = function (data) { exp.setRemoteCursor = function (data) {
if (data.reset) {
return void exp.removeCursors();
}
if (data.leave) { if (data.leave) {
$('.cp-codemirror-cursor[id^='+data.id+']').each(function (i, el) { $('.cp-codemirror-cursor[id^='+data.id+']').each(function (i, el) {
var id = $(el).attr('id'); var id = $(el).attr('id');

@ -3,28 +3,13 @@ define([
], function (Util) { ], function (Util) {
var module = {}; var module = {};
module.create = function (Common) { module.create = function (Common, onLocal) {
var exp = {}; var exp = {};
var sframeChan = Common.getSframeChannel();
var metadataMgr = Common.getMetadataMgr(); var metadataMgr = Common.getMetadataMgr();
var privateData = metadataMgr.getPrivateData(); var privateData = metadataMgr.getPrivateData();
var share = Util.find(privateData, ['settings', 'general', 'cursor', 'share']); var share = Util.find(privateData, ['settings', 'general', 'cursor', 'share']);
var show = Util.find(privateData, ['settings', 'general', 'cursor', 'show']); var show = Util.find(privateData, ['settings', 'general', 'cursor', 'show']);
var execCommand = function (cmd, data, cb) {
sframeChan.query('Q_CURSOR_COMMAND', {cmd: cmd, data: data}, function (err, obj) {
if (err || (obj && obj.error)) { return void cb(err || (obj && obj.error)); }
cb(void 0, obj);
});
};
exp.updateCursor = function (obj) {
if (share === false) { return; }
execCommand('UPDATE', obj, function (err) {
if (err) { console.error(err); }
});
};
var messageHandlers = []; var messageHandlers = [];
exp.onCursorUpdate = function (handler) { exp.onCursorUpdate = function (handler) {
messageHandlers.push(handler); messageHandlers.push(handler);
@ -40,15 +25,47 @@ define([
}); });
}; };
var onDegraded = function (data) {
if (data.degraded) {
// Enter degraded mode
onMessage({
reset: true
});
metadataMgr.setDegraded(true);
return void metadataMgr.refresh();
}
setTimeout(function () {
metadataMgr.setDegraded(false);
metadataMgr.refresh();
setTimeout(onLocal);
});
};
sframeChan.on('EV_CURSOR_EVENT', function (obj) { var onEvent = function (obj) {
var cmd = obj.ev; var cmd = obj.ev;
var data = obj.data; var data = obj.data;
if (cmd === 'DEGRADED') {
onDegraded(data);
return;
}
if (cmd === 'MESSAGE') { if (cmd === 'MESSAGE') {
onMessage(data); onMessage(data);
return; return;
} }
};
var module = Common.makeUniversal('cursor', {
onEvent: onEvent
}); });
var execCommand = module.execCommand;
exp.updateCursor = function (obj) {
if (share === false) { return; }
execCommand('UPDATE', obj, function (err) {
if (err) { console.error(err); }
});
};
return exp; return exp;
}; };

@ -1613,20 +1613,17 @@ define([
} }
sframeChan.on('Q_CURSOR_OPENCHANNEL', function (data, cb) { sframeChan.on('Q_CURSOR_OPENCHANNEL', function (data, cb) {
Cryptpad.cursor.execCommand({ Cryptpad.universal.execCommand({
type: 'cursor',
data: {
cmd: 'INIT_CURSOR', cmd: 'INIT_CURSOR',
data: { data: {
channel: data, channel: data,
secret: secret secret: secret
} }
}
}, cb); }, cb);
}); });
Cryptpad.cursor.onEvent.reg(function (data) {
sframeChan.event('EV_CURSOR_EVENT', data);
});
sframeChan.on('Q_CURSOR_COMMAND', function (data, cb) {
Cryptpad.cursor.execCommand(data, cb);
});
Cryptpad.onTimeoutEvent.reg(function () { Cryptpad.onTimeoutEvent.reg(function () {
sframeChan.event('EV_WORKER_TIMEOUT'); sframeChan.event('EV_WORKER_TIMEOUT');

@ -167,6 +167,7 @@ MessengerUI, Messages) {
var $userlistContent = toolbar.userlistContent; var $userlistContent = toolbar.userlistContent;
var metadataMgr = config.metadataMgr; var metadataMgr = config.metadataMgr;
var online = !forceOffline && metadataMgr.isConnected(); var online = !forceOffline && metadataMgr.isConnected();
var userData = metadataMgr.getMetadata().users; var userData = metadataMgr.getMetadata().users;
var viewers = metadataMgr.getViewers(); var viewers = metadataMgr.getViewers();
@ -217,13 +218,25 @@ MessengerUI, Messages) {
numberOfViewUsers = '?'; numberOfViewUsers = '?';
} }
if (metadataMgr.isDegraded()) {
numberOfEditUsers = Math.max(metadataMgr.getChannelMembers().length - 1, 0);
numberOfViewUsers = '';
}
// Update the buttons // Update the buttons
var fa_editusers = '<span class="fa fa-users"></span>'; var fa_editusers = '<span class="fa fa-users"></span>';
var fa_viewusers = '<span class="fa fa-eye"></span>'; var fa_viewusers = numberOfViewUsers === '' ? '' : '<span class="fa fa-eye"></span>';
var $spansmall = $('<span>').html(fa_editusers + ' ' + numberOfEditUsers + '&nbsp;&nbsp; ' + fa_viewusers + ' ' + numberOfViewUsers); var $spansmall = $('<span>').html(fa_editusers + ' ' + numberOfEditUsers + '&nbsp;&nbsp; ' + fa_viewusers + ' ' + numberOfViewUsers);
$userButtons.find('.cp-dropdown-button-title').html('').append($spansmall); $userButtons.find('.cp-dropdown-button-title').html('').append($spansmall);
if (!online) { return; } if (!online) { return; }
if (metadataMgr.isDegraded()) {
Messages.toolbar_degraded = "Too many editors are present in the pad. The userlist has been disabled to improve performances"; // XXX
$('<em>').text(Messages.toolbar_degraded).appendTo($editUsersList);
return;
}
// Display the userlist // Display the userlist
// Editors // Editors

@ -1247,6 +1247,16 @@ define([
}); });
framework.onCursorUpdate(function (data) { framework.onCursorUpdate(function (data) {
if (!data) { return; } if (!data) { return; }
if (data.reset) {
Object.keys(remoteCursors).forEach(function (id) {
if (remoteCursors[id].clear) {
remoteCursors[id].clear();
}
delete remoteCursors[id];
});
return;
}
var id = data.id; var id = data.id;
// Clear existing cursor // Clear existing cursor

@ -122,6 +122,9 @@ define([
}; };
exp.onCursorUpdate = function (data, hjson) { exp.onCursorUpdate = function (data, hjson) {
if (data.reset) {
return void exp.removeCursors(inner);
}
if (data.leave) { if (data.leave) {
if (data.id.length === 32) { if (data.id.length === 32) {
Object.keys(cursors).forEach(function (id) { Object.keys(cursors).forEach(function (id) {

Loading…
Cancel
Save