Update to move authorship calculation code to note model and support update authorship after making revision of docs

This commit is contained in:
Wu Cheng-Han 2016-10-10 20:23:33 +08:00
parent a5e6b5dd3b
commit d23ced1fba
2 changed files with 177 additions and 101 deletions

View file

@ -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: {

View file

@ -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) {