From d23ced1fba0e4bc7ecbc00d0a2376f34bab80509 Mon Sep 17 00:00:00 2001
From: Wu Cheng-Han
Date: Mon, 10 Oct 2016 20:23:33 +0800
Subject: 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(-)

(limited to 'lib')

diff --git a/lib/models/note.js b/lib/models/note.js
index d1c073e9..17988f74 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 0c68a256..9c3d5b2e 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) {
-- 
cgit v1.2.3