From d23ced1fba0e4bc7ecbc00d0a2376f34bab80509 Mon Sep 17 00:00:00 2001 From: Wu Cheng-Han Date: Mon, 10 Oct 2016 20:23:33 +0800 Subject: [PATCH] Update to move authorship calculation code to note model and support update authorship after making revision of docs --- lib/models/note.js | 177 ++++++++++++++++++++++++++++++++++++++++++++- lib/realtime.js | 101 +------------------------- 2 files changed, 177 insertions(+), 101 deletions(-) diff --git a/lib/models/note.js b/lib/models/note.js index d1c073e..17988f7 100644 --- a/lib/models/note.js +++ b/lib/models/note.js @@ -11,11 +11,16 @@ var shortId = require('shortid'); var Sequelize = require("sequelize"); var async = require('async'); var moment = require('moment'); +var DiffMatchPatch = require('diff-match-patch'); +var dmp = new DiffMatchPatch(); // core var config = require("../config.js"); var logger = require("../logger.js"); +//ot +var ot = require("../ot/index.js"); + // permission types var permissionTypes = ["freely", "editable", "locked", "private"]; @@ -115,6 +120,7 @@ module.exports = function (sequelize, DataTypes) { var fsModifiedTime = moment(fs.statSync(filePath).mtime); var dbModifiedTime = moment(note.lastchangeAt || note.createdAt); var body = fs.readFileSync(filePath, 'utf8'); + var contentLength = body.length; var title = Note.parseNoteTitle(body); body = LZString.compressToBase64(body); title = LZString.compressToBase64(title); @@ -126,7 +132,20 @@ module.exports = function (sequelize, DataTypes) { }).then(function (note) { sequelize.models.Revision.saveNoteRevision(note, function (err, revision) { if (err) return _callback(err, null); - return callback(null, note.id); + // update authorship on after making revision of docs + var patch = dmp.patch_fromText(LZString.decompressFromBase64(revision.patch)); + var operations = Note.transformPatchToOperations(patch, contentLength); + var authorship = note.authorship ? JSON.parse(LZString.decompressFromBase64(note.authorship)) : []; + for (var i = 0; i < operations.length; i++) { + authorship = Note.updateAuthorshipByOperation(operations[i], null, authorship); + } + note.update({ + authorship: authorship + }).then(function (note) { + return callback(null, note.id); + }).catch(function (err) { + return _callback(err, null); + }); }); }).catch(function (err) { return _callback(err, null); @@ -247,6 +266,162 @@ module.exports = function (sequelize, DataTypes) { _meta.slideOptions = meta.slideOptions; } return _meta; + }, + updateAuthorshipByOperation: function (operation, userId, authorships) { + var index = 0; + var timestamp = Date.now(); + for (var i = 0; i < operation.length; i++) { + var op = operation[i]; + if (ot.TextOperation.isRetain(op)) { + index += op; + } else if (ot.TextOperation.isInsert(op)) { + var opStart = index; + var opEnd = index + op.length; + var inserted = false; + // authorship format: [userId, startPos, endPos, createdAt, updatedAt] + if (authorships.length <= 0) authorships.push([userId, opStart, opEnd, timestamp, timestamp]); + else { + for (var j = 0; j < authorships.length; j++) { + var authorship = authorships[j]; + if (!inserted) { + var nextAuthorship = authorships[j + 1] || -1; + if (nextAuthorship != -1 && nextAuthorship[1] >= opEnd || j >= authorships.length - 1) { + if (authorship[1] < opStart && authorship[2] > opStart) { + // divide + var postLength = authorship[2] - opStart; + authorship[2] = opStart; + authorship[4] = timestamp; + authorships.splice(j + 1, 0, [userId, opStart, opEnd, timestamp, timestamp]); + authorships.splice(j + 2, 0, [authorship[0], opEnd, opEnd + postLength, authorship[3], timestamp]); + j += 2; + inserted = true; + } else if (authorship[1] >= opStart) { + authorships.splice(j, 0, [userId, opStart, opEnd, timestamp, timestamp]); + j += 1; + inserted = true; + } else if (authorship[2] <= opStart) { + authorships.splice(j + 1, 0, [userId, opStart, opEnd, timestamp, timestamp]); + j += 1; + inserted = true; + } + } + } + if (authorship[1] >= opStart) { + authorship[1] += op.length; + authorship[2] += op.length; + } + } + } + index += op.length; + } else if (ot.TextOperation.isDelete(op)) { + var opStart = index; + var opEnd = index - op; + if (operation.length == 1) { + authorships = []; + } else if (authorships.length > 0) { + for (var j = 0; j < authorships.length; j++) { + var authorship = authorships[j]; + if (authorship[1] >= opStart && authorship[1] <= opEnd && authorship[2] >= opStart && authorship[2] <= opEnd) { + authorships.splice(j, 1); + j -= 1; + } else if (authorship[1] < opStart && authorship[1] < opEnd && authorship[2] > opStart && authorship[2] > opEnd) { + authorship[2] += op; + authorship[4] = timestamp; + } else if (authorship[2] >= opStart && authorship[2] <= opEnd) { + authorship[2] = opStart; + authorship[4] = timestamp; + } else if (authorship[1] >= opStart && authorship[1] <= opEnd) { + authorship[1] = opEnd; + authorship[4] = timestamp; + } + if (authorship[1] >= opEnd) { + authorship[1] += op; + authorship[2] += op; + } + } + } + index += op; + } + } + // merge + for (var j = 0; j < authorships.length; j++) { + var authorship = authorships[j]; + for (var k = j + 1; k < authorships.length; k++) { + var nextAuthorship = authorships[k]; + if (nextAuthorship && authorship[0] === nextAuthorship[0] && authorship[2] === nextAuthorship[1]) { + var minTimestamp = Math.min(authorship[3], nextAuthorship[3]); + var maxTimestamp = Math.max(authorship[3], nextAuthorship[3]); + authorships.splice(j, 1, [authorship[0], authorship[1], nextAuthorship[2], minTimestamp, maxTimestamp]); + authorships.splice(k, 1); + j -= 1; + break; + } + } + } + // clear + for (var j = 0; j < authorships.length; j++) { + var authorship = authorships[j]; + if (!authorship[0]) { + authorships.splice(j, 1); + j -= 1; + } + } + return authorships; + }, + transformPatchToOperations: function (patch, contentLength) { + var operations = []; + if (patch.length > 0) { + // calculate original content length + for (var j = patch.length - 1; j >= 0; j--) { + var p = patch[j]; + for (var i = 0; i < p.diffs.length; i++) { + var diff = p.diffs[i]; + switch(diff[0]) { + case 1: // insert + contentLength -= diff[1].length; + break; + case -1: // delete + contentLength += diff[1].length; + break; + } + } + } + // generate operations + var bias = 0; + var lengthBias = 0; + for (var j = 0; j < patch.length; j++) { + var operation = []; + var p = patch[j]; + var currIndex = p.start1; + var currLength = contentLength - bias; + for (var i = 0; i < p.diffs.length; i++) { + var diff = p.diffs[i]; + switch(diff[0]) { + case 0: // retain + if (i == 0) // first + operation.push(currIndex + diff[1].length); + else if (i != p.diffs.length - 1) // mid + operation.push(diff[1].length); + else // last + operation.push(currLength + lengthBias - currIndex); + currIndex += diff[1].length; + break; + case 1: // insert + operation.push(diff[1]); + lengthBias += diff[1].length; + currIndex += diff[1].length; + break; + case -1: // delete + operation.push(-diff[1].length); + bias += diff[1].length; + currIndex += diff[1].length; + break; + } + } + operations.push(operation); + } + } + return operations; } }, hooks: { diff --git a/lib/realtime.js b/lib/realtime.js index 0c68a25..9c3d5b2 100644 --- a/lib/realtime.js +++ b/lib/realtime.js @@ -642,106 +642,7 @@ function operationCallback(socket, operation) { } } // save authorship - var index = 0; - var authorships = note.authorship; - var timestamp = Date.now(); - for (var i = 0; i < operation.length; i++) { - var op = operation[i]; - if (ot.TextOperation.isRetain(op)) { - index += op; - } else if (ot.TextOperation.isInsert(op)) { - var opStart = index; - var opEnd = index + op.length; - var inserted = false; - // authorship format: [userId, startPos, endPos, createdAt, updatedAt] - if (authorships.length <= 0) authorships.push([userId, opStart, opEnd, timestamp, timestamp]); - else { - for (var j = 0; j < authorships.length; j++) { - var authorship = authorships[j]; - if (!inserted) { - var nextAuthorship = authorships[j + 1] || -1; - if (nextAuthorship != -1 && nextAuthorship[1] >= opEnd || j >= authorships.length - 1) { - if (authorship[1] < opStart && authorship[2] > opStart) { - // divide - var postLength = authorship[2] - opStart; - authorship[2] = opStart; - authorship[4] = timestamp; - authorships.splice(j + 1, 0, [userId, opStart, opEnd, timestamp, timestamp]); - authorships.splice(j + 2, 0, [authorship[0], opEnd, opEnd + postLength, authorship[3], timestamp]); - j += 2; - inserted = true; - } else if (authorship[1] >= opStart) { - authorships.splice(j, 0, [userId, opStart, opEnd, timestamp, timestamp]); - j += 1; - inserted = true; - } else if (authorship[2] <= opStart) { - authorships.splice(j + 1, 0, [userId, opStart, opEnd, timestamp, timestamp]); - j += 1; - inserted = true; - } - } - } - if (authorship[1] >= opStart) { - authorship[1] += op.length; - authorship[2] += op.length; - } - } - } - index += op.length; - } else if (ot.TextOperation.isDelete(op)) { - var opStart = index; - var opEnd = index - op; - if (operation.length == 1) { - authorships = []; - } else if (authorships.length > 0) { - for (var j = 0; j < authorships.length; j++) { - var authorship = authorships[j]; - if (authorship[1] >= opStart && authorship[1] <= opEnd && authorship[2] >= opStart && authorship[2] <= opEnd) { - authorships.splice(j, 1); - j -= 1; - } else if (authorship[1] < opStart && authorship[1] < opEnd && authorship[2] > opStart && authorship[2] > opEnd) { - authorship[2] += op; - authorship[4] = timestamp; - } else if (authorship[2] >= opStart && authorship[2] <= opEnd) { - authorship[2] = opStart; - authorship[4] = timestamp; - } else if (authorship[1] >= opStart && authorship[1] <= opEnd) { - authorship[1] = opEnd; - authorship[4] = timestamp; - } - if (authorship[1] >= opEnd) { - authorship[1] += op; - authorship[2] += op; - } - } - } - index += op; - } - } - // merge - for (var j = 0; j < authorships.length; j++) { - var authorship = authorships[j]; - for (var k = j + 1; k < authorships.length; k++) { - var nextAuthorship = authorships[k]; - if (nextAuthorship && authorship[0] === nextAuthorship[0] && authorship[2] === nextAuthorship[1]) { - var minTimestamp = Math.min(authorship[3], nextAuthorship[3]); - var maxTimestamp = Math.max(authorship[3], nextAuthorship[3]); - authorships.splice(j, 1, [authorship[0], authorship[1], nextAuthorship[2], minTimestamp, maxTimestamp]); - authorships.splice(k, 1); - j -= 1; - break; - } - } - } - // clear - for (var j = 0; j < authorships.length; j++) { - var authorship = authorships[j]; - if (!authorship[0]) { - authorships.splice(j, 1); - j -= 1; - } - } - note.authorship = authorships; + note.authorship = models.Note.updateAuthorshipByOperation(operation, userId, note.authorship); } function connection(socket) {