summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/history.js79
-rw-r--r--lib/models/note.js3
-rw-r--r--lib/models/revision.js181
-rw-r--r--lib/realtime.js161
-rwxr-xr-xlib/response.js36
-rw-r--r--lib/workers/dmpWorker.js142
-rw-r--r--lib/workers/historyUpdater.js66
-rw-r--r--lib/workers/noteRevisionSaver.js19
-rw-r--r--lib/workers/noteUpdater.js101
9 files changed, 375 insertions, 413 deletions
diff --git a/lib/history.js b/lib/history.js
index bdc922d7..4a3bbe1e 100644
--- a/lib/history.js
+++ b/lib/history.js
@@ -2,7 +2,6 @@
//external modules
var async = require('async');
var moment = require('moment');
-var childProcess = require('child_process');
//core
var config = require("./config.js");
@@ -10,9 +9,6 @@ var logger = require("./logger.js");
var response = require("./response.js");
var models = require("./models");
-// workers
-var historyUpdater = require("./workers/historyUpdater");
-
//public
var History = {
historyGet: historyGet,
@@ -24,50 +20,49 @@ var History = {
var caches = {};
//update when the history is dirty
-var updaterIsBusy = false;
var updater = setInterval(function () {
- if (updaterIsBusy) return;
var deleted = [];
- var _caches = {};
- Object.keys(caches).forEach(function (key) {
+ async.each(Object.keys(caches), function (key, callback) {
var cache = caches[key];
if (cache.isDirty) {
- _caches[key] = cache.history;
- cache.isDirty = false;
+ if (config.debug) logger.info("history updater found dirty history: " + key);
+ var history = parseHistoryToArray(cache.history);
+ finishUpdateHistory(key, history, function (err, count) {
+ if (err) return callback(err, null);
+ if (!count) return callback(null, null);
+ cache.isDirty = false;
+ cache.updateAt = Date.now();
+ return callback(null, null);
+ });
} else {
if (moment().isAfter(moment(cache.updateAt).add(5, 'minutes'))) {
deleted.push(key);
}
+ return callback(null, null);
}
+ }, function (err) {
+ if (err) return logger.error('history updater error', err);
});
// delete specified caches
for (var i = 0, l = deleted.length; i < l; i++) {
caches[deleted[i]].history = {};
delete caches[deleted[i]];
}
- if (Object.keys(_caches).length <= 0) return;
- updaterIsBusy = true;
- var worker = childProcess.fork("./lib/workers/historyUpdater.js");
- if (config.debug) logger.info('history updater worker process started');
- worker.send({
- msg: 'update history',
- caches: _caches
- });
- worker.on('message', function (data) {
- if (!data || !data.msg || !data.userid) return;
- var cache = caches[data.userid];
- if (!cache) return;
- switch(data.msg) {
- case 'check':
- cache.updateAt = Date.now();
- break;
+}, 1000);
+
+function finishUpdateHistory(userid, history, callback) {
+ models.User.update({
+ history: JSON.stringify(history)
+ }, {
+ where: {
+ id: userid
}
+ }).then(function (count) {
+ return callback(null, count);
+ }).catch(function (err) {
+ return callback(err, null);
});
- worker.on('close', function (code) {
- updaterIsBusy = false;
- if (config.debug) logger.info('history updater worker process exited with code ' + code);
- });
-}, 1000);
+}
function isReady() {
var dirtyCount = 0;
@@ -106,7 +101,7 @@ function getHistory(userid, callback) {
}
function setHistory(userid, history) {
- if (Array.isArray(history)) history = historyUpdater.parseHistoryToObject(history);
+ if (Array.isArray(history)) history = parseHistoryToObject(history);
if (!caches[userid]) {
caches[userid] = {
history: {},
@@ -135,13 +130,31 @@ function updateHistory(userid, noteId, document) {
}
}
+function parseHistoryToArray(history) {
+ var _history = [];
+ Object.keys(history).forEach(function (key) {
+ var item = history[key];
+ _history.push(item);
+ });
+ return _history;
+}
+
+function parseHistoryToObject(history) {
+ var _history = {};
+ for (var i = 0, l = history.length; i < l; i++) {
+ var item = history[i];
+ _history[item.id] = item;
+ }
+ return _history;
+}
+
function historyGet(req, res) {
if (req.isAuthenticated()) {
getHistory(req.user.id, function (err, history) {
if (err) return response.errorInternalError(res);
if (!history) return response.errorNotFound(res);
res.send({
- history: historyUpdater.parseHistoryToArray(history)
+ history: parseHistoryToArray(history)
});
});
} else {
diff --git a/lib/models/note.js b/lib/models/note.js
index 05ed04c0..5727046c 100644
--- a/lib/models/note.js
+++ b/lib/models/note.js
@@ -260,6 +260,9 @@ module.exports = function (sequelize, DataTypes) {
if (!title) title = "Untitled";
return title;
},
+ generateDescription: function (markdown) {
+ return markdown.substr(0, 100).replace(/(?:\r\n|\r|\n)/g, ' ');
+ },
decodeTitle: function (title) {
var decodedTitle = LZString.decompressFromBase64(title);
if (decodedTitle) title = decodedTitle;
diff --git a/lib/models/revision.js b/lib/models/revision.js
index 33fdd73c..8b8eba94 100644
--- a/lib/models/revision.js
+++ b/lib/models/revision.js
@@ -5,13 +5,53 @@ var Sequelize = require("sequelize");
var LZString = require('lz-string');
var async = require('async');
var moment = require('moment');
-var DiffMatchPatch = require('diff-match-patch');
-var dmp = new DiffMatchPatch();
+var childProcess = require('child_process');
+var shortId = require('shortid');
// core
var config = require("../config.js");
var logger = require("../logger.js");
+var dmpWorker = createDmpWorker();
+var dmpCallbackCache = {};
+
+function createDmpWorker() {
+ var worker = childProcess.fork("./lib/workers/dmpWorker.js", {
+ stdio: 'ignore'
+ });
+ if (config.debug) logger.info('dmp worker process started');
+ worker.on('message', function (data) {
+ if (!data || !data.msg || !data.cacheKey) {
+ return logger.error('dmp worker error: not enough data on message');
+ }
+ var cacheKey = data.cacheKey;
+ switch(data.msg) {
+ case 'error':
+ dmpCallbackCache[cacheKey](data.error, null);
+ break;
+ case 'check':
+ dmpCallbackCache[cacheKey](null, data.result);
+ break;
+ }
+ delete dmpCallbackCache[cacheKey];
+ });
+ worker.on('close', function (code) {
+ dmpWorker = null;
+ if (config.debug) logger.info('dmp worker process exited with code ' + code);
+ });
+ return worker;
+}
+
+function sendDmpWorker(data, callback) {
+ if (!dmpWorker) dmpWorker = createDmpWorker();
+ var cacheKey = Date.now() + '_' + shortId.generate();
+ dmpCallbackCache[cacheKey] = callback;
+ data = Object.assign(data, {
+ cacheKey: cacheKey
+ });
+ dmpWorker.send(data);
+}
+
module.exports = function (sequelize, DataTypes) {
var Revision = sequelize.define("Revision", {
id: {
@@ -43,19 +83,6 @@ module.exports = function (sequelize, DataTypes) {
constraints: false
});
},
- createPatch: function (lastDoc, CurrDoc) {
- var ms_start = (new Date()).getTime();
- var diff = dmp.diff_main(lastDoc, CurrDoc);
- dmp.diff_cleanupSemantic(diff);
- var patch = dmp.patch_make(lastDoc, diff);
- patch = dmp.patch_toText(patch);
- var ms_end = (new Date()).getTime();
- if (config.debug) {
- logger.info(patch);
- logger.info((ms_end - ms_start) + 'ms');
- }
- return patch;
- },
getNoteRevisions: function (note, callback) {
Revision.findAll({
where: {
@@ -96,67 +123,11 @@ module.exports = function (sequelize, DataTypes) {
order: '"createdAt" DESC'
}).then(function (count) {
if (count <= 0) return callback(null, null);
- var ms_start = (new Date()).getTime();
- var startContent = null;
- var lastPatch = [];
- var applyPatches = [];
- var authorship = [];
- if (count <= Math.round(revisions.length / 2)) {
- // start from top to target
- for (var i = 0; i < count; i++) {
- var revision = revisions[i];
- if (i == 0) {
- startContent = LZString.decompressFromBase64(revision.content || revision.lastContent);
- }
- if (i != count - 1) {
- var patch = dmp.patch_fromText(LZString.decompressFromBase64(revision.patch));
- applyPatches = applyPatches.concat(patch);
- }
- lastPatch = revision.patch;
- authorship = revision.authorship;
- }
- // swap DIFF_INSERT and DIFF_DELETE to achieve unpatching
- for (var i = 0, l = applyPatches.length; i < l; i++) {
- for (var j = 0, m = applyPatches[i].diffs.length; j < m; j++) {
- var diff = applyPatches[i].diffs[j];
- if (diff[0] == DiffMatchPatch.DIFF_INSERT)
- diff[0] = DiffMatchPatch.DIFF_DELETE;
- else if (diff[0] == DiffMatchPatch.DIFF_DELETE)
- diff[0] = DiffMatchPatch.DIFF_INSERT;
- }
- }
- } else {
- // start from bottom to target
- var l = revisions.length - 1;
- for (var i = l; i >= count - 1; i--) {
- var revision = revisions[i];
- if (i == l) {
- startContent = LZString.decompressFromBase64(revision.lastContent);
- authorship = revision.authorship;
- }
- if (revision.patch) {
- var patch = dmp.patch_fromText(LZString.decompressFromBase64(revision.patch));
- applyPatches = applyPatches.concat(patch);
- }
- lastPatch = revision.patch;
- authorship = revision.authorship;
- }
- }
- try {
- var finalContent = dmp.patch_apply(applyPatches, startContent)[0];
- } catch (err) {
- return callback(err, null);
- }
- var data = {
- content: finalContent,
- patch: dmp.patch_fromText(LZString.decompressFromBase64(lastPatch)),
- authorship: authorship ? JSON.parse(LZString.decompressFromBase64(authorship)) : null
- };
- var ms_end = (new Date()).getTime();
- if (config.debug) {
- logger.info((ms_end - ms_start) + 'ms');
- }
- return callback(null, data);
+ sendDmpWorker({
+ msg: 'get revision',
+ revisions: revisions,
+ count: count
+ }, callback);
}).catch(function (err) {
return callback(err, null);
});
@@ -254,37 +225,43 @@ module.exports = function (sequelize, DataTypes) {
var latestRevision = revisions[0];
var lastContent = LZString.decompressFromBase64(latestRevision.content || latestRevision.lastContent);
var content = LZString.decompressFromBase64(note.content);
- var patch = Revision.createPatch(lastContent, content);
- if (!patch) {
- // if patch is empty (means no difference) then just update the latest revision updated time
- latestRevision.changed('updatedAt', true);
- latestRevision.update({
- updatedAt: Date.now()
- }).then(function (revision) {
- Revision.finishSaveNoteRevision(note, revision, callback);
- }).catch(function (err) {
- return callback(err, null);
- });
- } else {
- Revision.create({
- noteId: note.id,
- patch: LZString.compressToBase64(patch),
- content: note.content,
- length: LZString.decompressFromBase64(note.content).length,
- authorship: note.authorship
- }).then(function (revision) {
- // clear last revision content to reduce db size
+ sendDmpWorker({
+ msg: 'create patch',
+ lastDoc: lastContent,
+ currDoc: content,
+ }, function (err, patch) {
+ if (err) logger.error('save note revision error', err);
+ if (!patch) {
+ // if patch is empty (means no difference) then just update the latest revision updated time
+ latestRevision.changed('updatedAt', true);
latestRevision.update({
- content: null
- }).then(function () {
+ updatedAt: Date.now()
+ }).then(function (revision) {
Revision.finishSaveNoteRevision(note, revision, callback);
}).catch(function (err) {
return callback(err, null);
});
- }).catch(function (err) {
- return callback(err, null);
- });
- }
+ } else {
+ Revision.create({
+ noteId: note.id,
+ patch: LZString.compressToBase64(patch),
+ content: note.content,
+ length: LZString.decompressFromBase64(note.content).length,
+ authorship: note.authorship
+ }).then(function (revision) {
+ // clear last revision content to reduce db size
+ latestRevision.update({
+ content: null
+ }).then(function () {
+ Revision.finishSaveNoteRevision(note, revision, callback);
+ }).catch(function (err) {
+ return callback(err, null);
+ });
+ }).catch(function (err) {
+ return callback(err, null);
+ });
+ }
+ });
}
}).catch(function (err) {
return callback(err, null);
diff --git a/lib/realtime.js b/lib/realtime.js
index 5d769e7d..b50e05b2 100644
--- a/lib/realtime.js
+++ b/lib/realtime.js
@@ -9,7 +9,6 @@ var randomcolor = require("randomcolor");
var Chance = require('chance'),
chance = new Chance();
var moment = require('moment');
-var childProcess = require('child_process');
//core
var config = require("./config.js");
@@ -20,9 +19,6 @@ var models = require("./models");
//ot
var ot = require("./ot/index.js");
-// workers
-var noteUpdater = require("./workers/noteUpdater");
-
//public
var realtime = {
io: null,
@@ -83,62 +79,97 @@ function emitCheck(note) {
var users = {};
var notes = {};
//update when the note is dirty
-var updaterIsBusy = false;
var updater = setInterval(function () {
- if (updaterIsBusy) return;
- var _notes = {};
- Object.keys(notes).forEach(function (key) {
+ async.each(Object.keys(notes), function (key, callback) {
var note = notes[key];
- if (!note.server || !note.server.isDirty) return;
- _notes[key] = {
- id: note.id,
- lastchangeuser: note.lastchangeuser,
- authorship: note.authorship,
- document: note.server.document
- };
- note.server.isDirty = false;
- });
- if (Object.keys(_notes).length <= 0) return;
- updaterIsBusy = true;
- var worker = childProcess.fork("./lib/workers/noteUpdater.js");
- if (config.debug) logger.info('note updater worker process started');
- worker.send({
- msg: 'update note',
- notes: _notes
- });
- worker.on('message', function (data) {
- if (!data || !data.msg || !data.note) return;
- var note = notes[data.note.id];
- if (!note) return;
- switch(data.msg) {
- case 'error':
- for (var i = 0, l = note.socks.length; i < l; i++) {
- var sock = note.socks[i];
- if (typeof sock !== 'undefined' && sock) {
- setTimeout(function () {
- sock.disconnect(true);
- }, 0);
+ if (note.server.isDirty) {
+ if (config.debug) logger.info("updater found dirty note: " + key);
+ updateNote(note, function(err, _note) {
+ // handle when note already been clean up
+ if (!notes[key] || !notes[key].server) return callback(null, null);
+ if (!_note) {
+ realtime.io.to(note.id).emit('info', {
+ code: 404
+ });
+ logger.error('note not found: ', note.id);
+ }
+ if (err || !_note) {
+ for (var i = 0, l = note.socks.length; i < l; i++) {
+ var sock = note.socks[i];
+ if (typeof sock !== 'undefined' && sock) {
+ setTimeout(function () {
+ sock.disconnect(true);
+ }, 0);
+ }
}
+ return callback(err, null);
}
- break;
- case 'note not found':
- realtime.io.to(note.id).emit('info', {
- code: 404
- });
- break;
- case 'check':
- note.lastchangeuserprofile = data.note.lastchangeuserprofile;
- note.updatetime = data.note.updatetime;
- saverSleep = false;
+ note.server.isDirty = false;
+ note.updatetime = moment(_note.lastchangeAt).valueOf();
emitCheck(note);
- break;
+ return callback(null, null);
+ });
+ } else {
+ return callback(null, null);
}
- });
- worker.on('close', function (code) {
- updaterIsBusy = false;
- if (config.debug) logger.info('note updater worker process exited with code ' + code);
+ }, function (err) {
+ if (err) return logger.error('updater error', err);
});
}, 1000);
+function updateNote(note, callback) {
+ models.Note.findOne({
+ where: {
+ id: note.id
+ }
+ }).then(function (_note) {
+ if (!_note) return callback(null, null);
+ if (note.lastchangeuser) {
+ if (_note.lastchangeuserId != note.lastchangeuser) {
+ models.User.findOne({
+ where: {
+ id: note.lastchangeuser
+ }
+ }).then(function (user) {
+ if (!user) return callback(null, null);
+ note.lastchangeuserprofile = models.User.parseProfile(user.profile);
+ return finishUpdateNote(note, _note, callback);
+ }).catch(function (err) {
+ logger.error(err);
+ return callback(err, null);
+ });
+ } else {
+ return finishUpdateNote(note, _note, callback);
+ }
+ } else {
+ note.lastchangeuserprofile = null;
+ return finishUpdateNote(note, _note, callback);
+ }
+ }).catch(function (err) {
+ logger.error(err);
+ return callback(err, null);
+ });
+}
+function finishUpdateNote(note, _note, callback) {
+ if (!note || !note.server) return callback(null, null);
+ var body = note.server.document;
+ var title = note.title = models.Note.parseNoteTitle(body);
+ title = LZString.compressToBase64(title);
+ body = LZString.compressToBase64(body);
+ var values = {
+ title: title,
+ content: body,
+ authorship: LZString.compressToBase64(JSON.stringify(note.authorship)),
+ lastchangeuserId: note.lastchangeuser,
+ lastchangeAt: Date.now()
+ };
+ _note.update(values).then(function (_note) {
+ saverSleep = false;
+ return callback(null, _note);
+ }).catch(function (err) {
+ logger.error(err);
+ return callback(err, null);
+ });
+}
//clean when user not in any rooms or user not in connected list
var cleaner = setInterval(function () {
async.each(Object.keys(users), function (key, callback) {
@@ -161,28 +192,16 @@ var cleaner = setInterval(function () {
});
}, 60000);
var saverSleep = false;
-var saverIsBusy = false;
// save note revision in interval
var saver = setInterval(function () {
- if (saverSleep || saverIsBusy) return;
- saverIsBusy = true;
- var worker = childProcess.fork("./lib/workers/noteRevisionSaver.js");
- if (config.debug) logger.info('note revision saver worker process started');
- worker.send({
- msg: 'save note revision'
- });
- worker.on('message', function (data) {
- if (!data || !data.msg) return;
- switch(data.msg) {
- case 'empty':
- saverSleep = true;
- break;
+ if (saverSleep) return;
+ models.Revision.saveAllNotesRevision(function (err, notes) {
+ if (err) return logger.error('revision saver failed: ' + err);
+ if (notes && notes.length <= 0) {
+ saverSleep = true;
+ return;
}
});
- worker.on('close', function (code) {
- saverIsBusy = false;
- if (config.debug) logger.info('note revision saver worker process exited with code ' + code);
- });
}, 60000 * 5);
function getStatus(callback) {
@@ -524,7 +543,7 @@ function disconnect(socket) {
// remove note in notes if no user inside
if (Object.keys(note.users).length <= 0) {
if (note.server.isDirty) {
- noteUpdater.updateNote(note, function (err, _note) {
+ updateNote(note, function (err, _note) {
if (err) return logger.error('disconnect note failed: ' + err);
// clear server before delete to avoid memory leaks
note.server.document = "";
diff --git a/lib/response.js b/lib/response.js
index fa97f157..0004f824 100755
--- a/lib/response.js
+++ b/lib/response.js
@@ -16,15 +16,6 @@ var config = require("./config.js");
var logger = require("./logger.js");
var models = require("./models");
-//slides
-var md = require('reveal.js/plugin/markdown/markdown');
-
-//reveal.js
-var slideOptions = {
- separator: '^(\r\n?|\n)---(\r\n?|\n)$',
- verticalSeparator: '^(\r\n?|\n)----(\r\n?|\n)$'
-};
-
//public
var response = {
errorForbidden: function (res) {
@@ -187,26 +178,28 @@ function showPublishNote(req, res, next) {
}
var body = LZString.decompressFromBase64(note.content);
var meta = null;
+ var markdown = null;
try {
- meta = models.Note.parseMeta(metaMarked(body).meta);
+ var obj = metaMarked(body);
+ markdown = obj.markdown;
+ meta = models.Note.parseMeta(obj.meta);
} catch(err) {
//na
}
if (!meta) meta = {};
var createtime = note.createdAt;
var updatetime = note.lastchangeAt;
- var text = S(body).escapeHTML().s;
var title = models.Note.decodeTitle(note.title);
title = models.Note.generateWebTitle(meta.title || title);
var origin = config.serverurl;
var data = {
title: title,
- description: meta.description,
+ description: meta.description || markdown ? models.Note.generateDescription(markdown) : null,
viewcount: note.viewcount,
createtime: createtime,
updatetime: updatetime,
url: origin,
- body: text,
+ body: body,
useCDN: config.usecdn,
owner: note.owner ? note.owner.id : null,
ownerprofile: note.owner ? models.User.parseProfile(note.owner.profile) : null,
@@ -259,19 +252,21 @@ function actionDownload(req, res, note) {
function actionInfo(req, res, note) {
var body = LZString.decompressFromBase64(note.content);
var meta = null;
+ var markdown = null;
try {
- meta = models.Note.parseMeta(metaMarked(body).meta);
+ var obj = metaMarked(body);
+ markdown = obj.markdown;
+ meta = models.Note.parseMeta(obj.meta);
} catch(err) {
//na
}
if (!meta) meta = {};
var createtime = note.createdAt;
var updatetime = note.lastchangeAt;
- var text = S(body).escapeHTML().s;
var title = models.Note.decodeTitle(note.title);
var data = {
title: meta.title || title,
- description: meta.description,
+ description: meta.description || markdown ? models.Note.generateDescription(markdown) : null,
viewcount: note.viewcount,
createtime: createtime,
updatetime: updatetime
@@ -571,9 +566,10 @@ function showPublishSlide(req, res, next) {
}
var body = LZString.decompressFromBase64(note.content);
var meta = null;
+ var markdown = null;
try {
var obj = metaMarked(body);
- body = obj.markdown;
+ markdown = obj.markdown;
meta = models.Note.parseMeta(obj.meta);
} catch(err) {
//na
@@ -581,19 +577,17 @@ function showPublishSlide(req, res, next) {
if (!meta) meta = {};
var createtime = note.createdAt;
var updatetime = note.lastchangeAt;
- var text = S(body).escapeHTML().s;
var title = models.Note.decodeTitle(note.title);
title = models.Note.generateWebTitle(meta.title || title);
- var slides = md.slidify(text, slideOptions);
var origin = config.serverurl;
var data = {
title: title,
- description: meta.description,
+ description: meta.description || markdown ? models.Note.generateDescription(markdown) : null,
viewcount: note.viewcount,
createtime: createtime,
updatetime: updatetime,
url: origin,
- slides: slides,
+ body: markdown,
meta: JSON.stringify(obj.meta || {}),
useCDN: config.usecdn,
owner: note.owner ? note.owner.id : null,
diff --git a/lib/workers/dmpWorker.js b/lib/workers/dmpWorker.js
new file mode 100644
index 00000000..fae36191
--- /dev/null
+++ b/lib/workers/dmpWorker.js
@@ -0,0 +1,142 @@
+// external modules
+var LZString = require('lz-string');
+var DiffMatchPatch = require('diff-match-patch');
+var dmp = new DiffMatchPatch();
+
+// core
+var config = require("../config.js");
+var logger = require("../logger.js");
+
+process.on('message', function(data) {
+ if (!data || !data.msg || !data.cacheKey) {
+ return logger.error('dmp worker error: not enough data');
+ }
+ switch (data.msg) {
+ case 'create patch':
+ if (!data.hasOwnProperty('lastDoc') || !data.hasOwnProperty('currDoc')) {
+ return logger.error('dmp worker error: not enough data on create patch');
+ }
+ try {
+ var patch = createPatch(data.lastDoc, data.currDoc);
+ process.send({
+ msg: 'check',
+ result: patch,
+ cacheKey: data.cacheKey
+ });
+ } catch (err) {
+ logger.error('dmp worker error', err);
+ process.send({
+ msg: 'error',
+ error: err,
+ cacheKey: data.cacheKey
+ });
+ }
+ break;
+ case 'get revision':
+ if (!data.hasOwnProperty('revisions') || !data.hasOwnProperty('count')) {
+ return logger.error('dmp worker error: not enough data on get revision');
+ }
+ try {
+ var result = getRevision(data.revisions, data.count);
+ process.send({
+ msg: 'check',
+ result: result,
+ cacheKey: data.cacheKey
+ });
+ } catch (err) {
+ logger.error('dmp worker error', err);
+ process.send({
+ msg: 'error',
+ error: err,
+ cacheKey: data.cacheKey
+ });
+ }
+ break;
+ }
+});
+
+function createPatch(lastDoc, currDoc) {
+ var ms_start = (new Date()).getTime();
+ var diff = dmp.diff_main(lastDoc, currDoc);
+ dmp.diff_cleanupSemantic(diff);
+ var patch = dmp.patch_make(lastDoc, diff);
+ patch = dmp.patch_toText(patch);
+ var ms_end = (new Date()).getTime();
+ if (config.debug) {
+ logger.info(patch);
+ logger.info((ms_end - ms_start) + 'ms');
+ }
+ return patch;
+}
+
+function getRevision(revisions, count) {
+ var ms_start = (new Date()).getTime();
+ var startContent = null;
+ var lastPatch = [];
+ var applyPatches = [];
+ var authorship = [];
+ if (count <= Math.round(revisions.length / 2)) {
+ // start from top to target
+ for (var i = 0; i < count; i++) {
+ var revision = revisions[i];
+ if (i == 0) {
+ startContent = LZString.decompressFromBase64(revision.content || revision.lastContent);
+ }
+ if (i != count - 1) {
+ var patch = dmp.patch_fromText(LZString.decompressFromBase64(revision.patch));
+ applyPatches = applyPatches.concat(patch);
+ }
+ lastPatch = revision.patch;
+ authorship = revision.authorship;
+ }
+ // swap DIFF_INSERT and DIFF_DELETE to achieve unpatching
+ for (var i = 0, l = applyPatches.length; i < l; i++) {
+ for (var j = 0, m = applyPatches[i].diffs.length; j < m; j++) {
+ var diff = applyPatches[i].diffs[j];
+ if (diff[0] == DiffMatchPatch.DIFF_INSERT)
+ diff[0] = DiffMatchPatch.DIFF_DELETE;
+ else if (diff[0] == DiffMatchPatch.DIFF_DELETE)
+ diff[0] = DiffMatchPatch.DIFF_INSERT;
+ }
+ }
+ } else {
+ // start from bottom to target
+ var l = revisions.length - 1;
+ for (var i = l; i >= count - 1; i--) {
+ var revision = revisions[i];
+ if (i == l) {
+ startContent = LZString.decompressFromBase64(revision.lastContent);
+ authorship = revision.authorship;
+ }
+ if (revision.patch) {
+ var patch = dmp.patch_fromText(LZString.decompressFromBase64(revision.patch));
+ applyPatches = applyPatches.concat(patch);
+ }
+ lastPatch = revision.patch;
+ authorship = revision.authorship;
+ }
+ }
+ try {
+ var finalContent = dmp.patch_apply(applyPatches, startContent)[0];
+ } catch (err) {
+ throw new Error(err);
+ }
+ var data = {
+ content: finalContent,
+ patch: dmp.patch_fromText(LZString.decompressFromBase64(lastPatch)),
+ authorship: authorship ? JSON.parse(LZString.decompressFromBase64(authorship)) : null
+ };
+ var ms_end = (new Date()).getTime();
+ if (config.debug) {
+ logger.info((ms_end - ms_start) + 'ms');
+ }
+ return data;
+}
+
+// log uncaught exception
+process.on('uncaughtException', function (err) {
+ logger.error('An uncaught exception has occured.');
+ logger.error(err);
+ logger.error('Process will exit now.');
+ process.exit(1);
+}); \ No newline at end of file
diff --git a/lib/workers/historyUpdater.js b/lib/workers/historyUpdater.js
deleted file mode 100644
index df80e92d..00000000
--- a/lib/workers/historyUpdater.js
+++ /dev/null
@@ -1,66 +0,0 @@
-// external modules
-var async = require('async');
-
-// core
-var config = require("../config.js");
-var logger = require("../logger.js");
-var models = require("../models");
-
-process.on('message', function (data) {
- if (!data || !data.msg || data.msg !== 'update history' || !data.caches) return process.exit();
- var caches = data.caches;
- async.each(Object.keys(caches), function (key, callback) {
- var cache = caches[key];
- if (config.debug) logger.info("history updater found dirty history: " + key);
- var history = parseHistoryToArray(cache);
- finishUpdateHistory(key, history, function (err, count) {
- if (err) return callback(err, null);
- if (!count) return callback(null, null);
- process.send({
- msg: 'check',
- userid: key
- });
- return callback(null, null);
- });
- }, function (err) {
- if (err) logger.error('history updater error', err);
- process.exit();
- });
-});
-
-function finishUpdateHistory(userid, history, callback) {
- models.User.update({
- history: JSON.stringify(history)
- }, {
- where: {
- id: userid
- }
- }).then(function (count) {
- return callback(null, count);
- }).catch(function (err) {
- return callback(err, null);
- });
-}
-
-function parseHistoryToArray(history) {
- var _history = [];
- Object.keys(history).forEach(function (key) {
- var item = history[key];
- _history.push(item);
- });
- return _history;
-}
-
-function parseHistoryToObject(history) {
- var _history = {};
- for (var i = 0, l = history.length; i < l; i++) {
- var item = history[i];
- _history[item.id] = item;
- }
- return _history;
-}
-
-module.exports = {
- parseHistoryToArray: parseHistoryToArray,
- parseHistoryToObject: parseHistoryToObject
-}; \ No newline at end of file
diff --git a/lib/workers/noteRevisionSaver.js b/lib/workers/noteRevisionSaver.js
deleted file mode 100644
index b6b117a3..00000000
--- a/lib/workers/noteRevisionSaver.js
+++ /dev/null
@@ -1,19 +0,0 @@
-// core
-var logger = require("../logger.js");
-var models = require("../models");
-
-process.on('message', function (data) {
- if (!data || !data.msg || data.msg !== 'save note revision') return process.exit();
- models.Revision.saveAllNotesRevision(function (err, notes) {
- if (err) {
- logger.error('note revision saver failed: ' + err);
- return process.exit();
- }
- if (notes && notes.length <= 0) {
- process.send({
- msg: 'empty'
- });
- }
- process.exit();
- });
-}); \ No newline at end of file
diff --git a/lib/workers/noteUpdater.js b/lib/workers/noteUpdater.js
deleted file mode 100644
index 3fc4b1eb..00000000
--- a/lib/workers/noteUpdater.js
+++ /dev/null
@@ -1,101 +0,0 @@
-// external modules
-var async = require('async');
-var moment = require('moment');
-var LZString = require('lz-string');
-
-// core
-var config = require("../config.js");
-var logger = require("../logger.js");
-var models = require("../models");
-
-process.on('message', function (data) {
- if (!data || !data.msg || data.msg !== 'update note' || !data.notes) return process.exit();
- var notes = data.notes;
- async.each(Object.keys(notes), function (key, callback) {
- var note = notes[key];
- if (config.debug) logger.info("note updater found dirty note: " + key);
- updateNote(note, function(err, _note) {
- if (!_note) {
- process.send({
- msg: 'note not found',
- note: note
- });
- logger.error('note not found: ', note.id);
- }
- if (err || !_note) {
- process.send({
- msg: 'error',
- note: note
- });
- return callback(err, null);
- }
- note.updatetime = moment(_note.lastchangeAt).valueOf();
- process.send({
- msg: 'check',
- note: note
- });
- return callback(null, null);
- });
- }, function (err) {
- if (err) logger.error('note updater error', err);
- process.exit();
- });
-});
-
-function updateNote(note, callback) {
- models.Note.findOne({
- where: {
- id: note.id
- }
- }).then(function (_note) {
- if (!_note) return callback(null, null);
- if (note.lastchangeuser) {
- if (_note.lastchangeuserId != note.lastchangeuser) {
- models.User.findOne({
- where: {
- id: note.lastchangeuser
- }
- }).then(function (user) {
- if (!user) return callback(null, null);
- note.lastchangeuserprofile = models.User.parseProfile(user.profile);
- return finishUpdateNote(note, _note, callback);
- }).catch(function (err) {
- logger.error(err);
- return callback(err, null);
- });
- } else {
- return finishUpdateNote(note, _note, callback);
- }
- } else {
- note.lastchangeuserprofile = null;
- return finishUpdateNote(note, _note, callback);
- }
- }).catch(function (err) {
- logger.error(err);
- return callback(err, null);
- });
-}
-
-function finishUpdateNote(note, _note, callback) {
- var body = note.document;
- var title = note.title = models.Note.parseNoteTitle(body);
- title = LZString.compressToBase64(title);
- body = LZString.compressToBase64(body);
- var values = {
- title: title,
- content: body,
- authorship: LZString.compressToBase64(JSON.stringify(note.authorship)),
- lastchangeuserId: note.lastchangeuser,
- lastchangeAt: Date.now()
- };
- _note.update(values).then(function (_note) {
- return callback(null, _note);
- }).catch(function (err) {
- logger.error(err);
- return callback(err, null);
- });
-}
-
-module.exports = {
- updateNote: updateNote
-}; \ No newline at end of file