From a46baea85bb7135c2891e4c3e33f33007a2f9033 Mon Sep 17 00:00:00 2001 From: yflory Date: Thu, 17 Sep 2020 17:36:05 +0200 Subject: [PATCH 01/21] OnlyOffice history --- www/common/onlyoffice/inner.js | 63 ++++++++++++++++++++++++++++- www/common/sframe-common-history.js | 27 +++++++++++++ 2 files changed, 89 insertions(+), 1 deletion(-) diff --git a/www/common/onlyoffice/inner.js b/www/common/onlyoffice/inner.js index 5c36f6604..4a5a4a9e5 100644 --- a/www/common/onlyoffice/inner.js +++ b/www/common/onlyoffice/inner.js @@ -404,6 +404,7 @@ define([ clearTimeout(pendingChanges[key]); delete pendingChanges[key]; }); + if (APP.stopHistory) { APP.history = false; } startOO(blob, type, true); }; @@ -619,6 +620,7 @@ define([ removeClient(obj.data); break; case 'MESSAGE': + if (APP.history) { return; } if (ooChannel.ready) { // In read-only mode, push the message to the queue and prompt // the user to refresh OO (without reloading the page) @@ -1041,7 +1043,7 @@ define([ startOO = function (blob, file, force) { if (APP.ooconfig && !force) { return void console.error('already started'); } var url = URL.createObjectURL(blob); - var lock = readOnly || APP.migrate; + var lock = readOnly || APP.migrate || APP.history; // Starting from version 3, we can use the view mode again // defined but never used @@ -1732,6 +1734,65 @@ define([ makeCheckpoint(true); }); $save.appendTo(toolbar.$bottomM); + + (function () { + /* add a history button */ + // XXX move out of dev mode + var commit = function () { + // ~ Commit current version + // XXX make checkpoint? + }; + var loadCp = function (cp) { + loadLastDocument(cp, function () { + var file = getFileType(); + var type = common.getMetadataMgr().getPrivateData().ooType; + var blob = loadInitDocument(type, true); + resetData(blob, file); + }, function (blob, file) { + // XXX ? + resetData(blob, file); + }); + }; + var draw = function (cp, patch) { + if (typeof(cp) === "undefined") { + // Patch on the current cp + // XXX send patch to oo + return; + } + // We want to load a checkpoint: + loadCp(cp); + console.error('draw'); + }; + var setHistoryMode = function (bool, update) { + if (bool) { + APP.history = true; + getEditor().setViewModeDisconnect(); + return; + } + APP.stopHistory = true; + if (update) { + // We want to commit the current version + // Do nothing: we're going to create a cp + return; + } + // Cancel button: redraw from lastCp + var lastCp = getLastCp(); + loadCp(lastCp); + + //redraw(); + }; + var histConfig = { + onLocal: draw, + onRemote: function () {}, + setHistory: setHistoryMode, + applyVal: draw, + onlyoffice: content.hashes || {}, + $toolbar: $(toolbarContainer) + }; + var $hist = common.createButton('history', true, {histConfig: histConfig}); + $hist.addClass('cp-hidden-if-readonly'); + toolbar.$drawer.append($hist); + })(); } if (window.CP_DEV_MODE || DISPLAY_RESTORE_BUTTON) { common.createButton('', true, { diff --git a/www/common/sframe-common-history.js b/www/common/sframe-common-history.js index 399357106..74a5dd1d7 100644 --- a/www/common/sframe-common-history.js +++ b/www/common/sframe-common-history.js @@ -54,6 +54,29 @@ define([ }); }; + var cpIndex = 0; + var sortedCp; + if (config.onlyoffice) { + sortedCp = Object.keys(config.onlyoffice).map(Number).sort(function (a, b) { + return a-b; + }); + } + var loadMoreOOHistory = function () { + if (!Array.isArray(sortedCp)) { + return void console.error("Wrong type"); + } + var hashes = config.onlyoffice; + if (!sortedCp.length) { + // XXX no cp + } + var cp = {}; + if (cpIndex < sortedCp.length) { + var idx = sortedCp[sortedCp.length - 1 - cpIndex]; + cp = config.onlyoffice[idx]; + } + config.onLocal(cp); + }; + var allMessages = []; var lastKnownHash; var isComplete = false; @@ -334,6 +357,10 @@ define([ config.onOpen(); } + if (config.onlyoffice) { + return void loadMoreOOHistory(); + } + // Load all the history messages into a new chainpad object loadMoreHistory(config, common, function (err, newRt, isFull) { History.readOnly = common.getMetadataMgr().getPrivateData().readOnly; From b31707098acdece3e9f3f091960e0b08a0efac12 Mon Sep 17 00:00:00 2001 From: yflory Date: Fri, 18 Sep 2020 12:42:09 +0200 Subject: [PATCH 02/21] Improve getHistoryRange to support 'toHash' --- lib/hk-util.js | 5 +++-- lib/workers/db-worker.js | 8 ++++++++ lib/workers/index.js | 3 ++- www/common/outer/async-store.js | 5 +++-- www/common/sframe-common-outer.js | 1 + 5 files changed, 17 insertions(+), 5 deletions(-) diff --git a/lib/hk-util.js b/lib/hk-util.js index ed5f43297..14263e481 100644 --- a/lib/hk-util.js +++ b/lib/hk-util.js @@ -662,10 +662,11 @@ const handleGetHistoryRange = function (Env, Server, seq, userId, parsed) { } var oldestKnownHash = map.from; + var untilHash = map.to; var desiredMessages = map.count; var desiredCheckpoint = map.cpCount; var txid = map.txid; - if (typeof(desiredMessages) !== 'number' && typeof(desiredCheckpoint) !== 'number') { + if (typeof(desiredMessages) !== 'number' && typeof(desiredCheckpoint) !== 'number' && !untilHash) { return void Server.send(userId, [seq, 'ERROR', 'UNSPECIFIED_COUNT', HISTORY_KEEPER_ID]); } @@ -674,7 +675,7 @@ const handleGetHistoryRange = function (Env, Server, seq, userId, parsed) { } Server.send(userId, [seq, 'ACK']); - Env.getOlderHistory(channelName, oldestKnownHash, desiredMessages, desiredCheckpoint, function (err, toSend) { + Env.getOlderHistory(channelName, oldestKnownHash, untilHash, desiredMessages, desiredCheckpoint, function (err, toSend) { if (err && err.code !== 'ENOENT') { Env.Log.error("HK_GET_OLDER_HISTORY", err); } diff --git a/lib/workers/db-worker.js b/lib/workers/db-worker.js index d80c2dc21..87b8fe93a 100644 --- a/lib/workers/db-worker.js +++ b/lib/workers/db-worker.js @@ -221,6 +221,7 @@ const computeMetadata = function (data, cb) { const getOlderHistory = function (data, cb) { const oldestKnownHash = data.hash; + const untilHash = data.toHash; const channelName = data.channel; const desiredMessages = data.desiredMessages; const desiredCheckpoint = data.desiredCheckpoint; @@ -251,6 +252,13 @@ const getOlderHistory = function (data, cb) { var toSend = []; if (typeof (desiredMessages) === "number") { toSend = messages.slice(-desiredMessages); + } else if (untilHash) { + for (var i = messages.length - 1; i >= 0; i--) { + toSend.unshift(messages[i]); + if (Array.isArray(messages[i]) && HK.getHash(messages[i][4], Log) === untilHash) { + break; + } + } } else { let cpCount = 0; for (var i = messages.length - 1; i >= 0; i--) { diff --git a/lib/workers/index.js b/lib/workers/index.js index a77de5437..1033aad0b 100644 --- a/lib/workers/index.js +++ b/lib/workers/index.js @@ -281,12 +281,13 @@ Workers.initialize = function (Env, config, _cb) { }); }; - Env.getOlderHistory = function (channel, oldestKnownHash, desiredMessages, desiredCheckpoint, cb) { + Env.getOlderHistory = function (channel, oldestKnownHash, toHash, desiredMessages, desiredCheckpoint, cb) { Env.store.getWeakLock(channel, function (next) { sendCommand({ channel: channel, command: "GET_OLDER_HISTORY", hash: oldestKnownHash, + toHash: toHash, desiredMessages: desiredMessages, desiredCheckpoint: desiredCheckpoint, }, Util.both(next, cb)); diff --git a/www/common/outer/async-store.js b/www/common/outer/async-store.js index 7c8793893..f07378d64 100644 --- a/www/common/outer/async-store.js +++ b/www/common/outer/async-store.js @@ -2069,7 +2069,7 @@ define([ if (first) { // If the first message if not a checkpoint, it means it is the first // message of the pad, so we have the full history! - if (!/^cp\|/.test(msg)) { fullHistory = true; } + if (!/^cp\|/.test(msg) && !data.toHash) { fullHistory = true; } lastKnownHash = msg.slice(0,64); first = false; } @@ -2086,7 +2086,8 @@ define([ network.on('message', onMsg); network.sendto(hk, JSON.stringify(['GET_HISTORY_RANGE', data.channel, { from: data.lastKnownHash, - cpCount: data.cpCount || 2, + to: data.toHash, + cpCount: data.cpCount || 2, // Ignored if "to" is provided txid: txid }])); }; diff --git a/www/common/sframe-common-outer.js b/www/common/sframe-common-outer.js index 28d6678a1..a08ecb0f2 100644 --- a/www/common/sframe-common-outer.js +++ b/www/common/sframe-common-outer.js @@ -1108,6 +1108,7 @@ define([ Cryptpad.getHistoryRange({ channel: channel, validateKey: validate, + toHash: data.toHash, lastKnownHash: data.lastKnownHash }, function (data) { cb({ From 4f147d4fd2fb3bf24a8a57ab167f1b352a169dcb Mon Sep 17 00:00:00 2001 From: yflory Date: Fri, 18 Sep 2020 17:54:57 +0200 Subject: [PATCH 03/21] Add support for onlyoffice history --- lib/workers/db-worker.js | 2 +- www/common/onlyoffice/history.js | 340 ++++++++++++++++++++++++++++++ www/common/onlyoffice/inner.js | 111 +++++++--- www/common/outer/async-store.js | 2 + www/common/outer/onlyoffice.js | 24 ++- www/common/sframe-common-outer.js | 2 +- 6 files changed, 439 insertions(+), 42 deletions(-) create mode 100644 www/common/onlyoffice/history.js diff --git a/lib/workers/db-worker.js b/lib/workers/db-worker.js index 87b8fe93a..681988ad0 100644 --- a/lib/workers/db-worker.js +++ b/lib/workers/db-worker.js @@ -255,7 +255,7 @@ const getOlderHistory = function (data, cb) { } else if (untilHash) { for (var i = messages.length - 1; i >= 0; i--) { toSend.unshift(messages[i]); - if (Array.isArray(messages[i]) && HK.getHash(messages[i][4], Log) === untilHash) { + if (Array.isArray(messages[i]) && HK.getHash(messages[i][4]) === untilHash) { break; } } diff --git a/www/common/onlyoffice/history.js b/www/common/onlyoffice/history.js new file mode 100644 index 000000000..e9f361f36 --- /dev/null +++ b/www/common/onlyoffice/history.js @@ -0,0 +1,340 @@ +define([ + 'jquery', + '/common/common-interface.js', + '/bower_components/nthen/index.js', + //'/bower_components/chainpad-json-validator/json-ot.js', +], function ($, UI, nThen, /* JsonOT */) { + //var ChainPad = window.ChainPad; + var History = {}; + + History.create = function (common, config) { + if (!config.$toolbar) { return void console.error("config.$toolbar is undefined");} + if (History.loading) { return void console.error("History is already being loaded..."); } + History.loading = true; + var $toolbar = config.$toolbar; + var sframeChan = common.getSframeChannel(); + + if (!config.onlyoffice || !config.setHistory || !config.onCheckpoint || !config.onPatch) { + throw new Error("Missing config element"); + } + + var cpIndex = -1; + var msgIndex = 0; + var sortedCp; + var ooMessages = {}; + var loading = false; + var update = function () {}; + + // Get an array of the checkpoint IDs sorted their patch index + var hashes = config.onlyoffice.hashes; + var sortedCp = Object.keys(hashes).map(Number).sort(function (a, b) { + return hashes[a].index - hashes[b].index; + }); + + var fillOO = function (id, messages) { + if (!id) { return; } + if (ooMessages[id]) { return; } + ooMessages[id] = messages; + update(); + }; + + // We want to load a checkpoint (or initial state) + var loadMoreOOHistory = function () { + if (!Array.isArray(sortedCp)) { return void console.error("Wrong type"); } + + var cp = {}; + + // Get the checkpoint ID + var id = -1; + if (cpIndex < sortedCp.length) { + id = sortedCp[sortedCp.length - 1 - cpIndex]; + cp = hashes[id]; + } + var nextId = sortedCp[sortedCp.length - cpIndex]; + + // Get the history between "toHash" and "fromHash". This function is using + // "getOlderHistory", that's why we start from the more recent hash + // and we go back in time to an older hash + + // We need to get all the patches between the current cp hash and the next cp hash + + // Current cp or initial hash (invalid hash ==> initial hash) + var toHash = cp.hash || 'NONE'; + // Next cp or last hash + var fromHash = nextId ? hashes[nextId].hash : config.onlyoffice.lastHash; + + msgIndex = 0; + if (ooMessages[id]) { + // Cp already loaded: reload OO + loading = false; + return void config.onCheckpoint(cp); + } + + sframeChan.query('Q_GET_HISTORY_RANGE', { + channel: config.onlyoffice.channel, + lastKnownHash: fromHash, + toHash: toHash, + }, function (err, data) { + if (err) { return void console.error(err); } + if (!Array.isArray(data.messages)) { return void console.error('Not an array!'); } + + var messages = (data.messages || []).slice(1); + + if (config.debug) { console.log(data.messages); } + fillOO(id, messages); + loading = false; + config.onCheckpoint(cp); + }); + }; + + // config.setHistory(bool, bool) + // - bool1: history value + // - bool2: reset old content? + var render = function (val) { + if (typeof val === "undefined") { return; } + try { + config.applyVal(val); + } catch (e) { + // Probably a parse error + console.error(e); + } + }; + var onClose = function () { config.setHistory(false); }; + var onRevert = function () { + config.onRevert(); + }; + + config.setHistory(true); + + var Messages = common.Messages; + + var realtime; + + var states = []; + var c = 0;//states.length - 1; + + var $hist = $toolbar.find('.cp-toolbar-history'); + var $bottom = $toolbar.find('.cp-toolbar-bottom'); + + $hist.html('').css('display', 'flex'); + $bottom.hide(); + + UI.spinner($hist).get().show(); + + var $loadMore, $version, get; + + var $fastPrev = $('