HackMD/lib/history.js

212 lines
No EOL
6.8 KiB
JavaScript

//history
//external modules
var async = require('async');
var moment = require('moment');
var childProcess = require('child_process');
//core
var config = require("./config.js");
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,
historyPost: historyPost,
historyDelete: historyDelete,
isReady: isReady,
updateHistory: updateHistory
};
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) {
var cache = caches[key];
if (cache.isDirty) {
_caches[key] = cache.history;
cache.isDirty = false;
} else {
if (moment().isAfter(moment(cache.updateAt).add(5, 'minutes'))) {
deleted.push(key);
}
}
});
// 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;
}
});
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;
async.each(Object.keys(caches), function (key, callback) {
if (caches[key].isDirty) dirtyCount++;
return callback(null, null);
}, function (err) {
if (err) return logger.error('history ready check error', err);
});
return dirtyCount > 0 ? false : true;
}
function getHistory(userid, callback) {
if (caches[userid]) {
return callback(null, caches[userid].history);
} else {
models.User.findOne({
where: {
id: userid
}
}).then(function (user) {
if (!user)
return callback(null, null);
var history = [];
if (user.history)
history = JSON.parse(user.history);
if (config.debug)
logger.info('read history success: ' + user.id);
setHistory(userid, history);
return callback(null, history);
}).catch(function (err) {
logger.error('read history failed: ' + err);
return callback(err, null);
});
}
}
function setHistory(userid, history) {
if (Array.isArray(history)) history = historyUpdater.parseHistoryToObject(history);
if (!caches[userid]) {
caches[userid] = {
history: {},
isDirty: false,
updateAt: Date.now()
};
}
caches[userid].history = history;
}
function updateHistory(userid, noteId, document) {
if (userid && noteId && typeof document !== 'undefined') {
getHistory(userid, function (err, history) {
if (err || !history) return;
if (!caches[userid].history[noteId]) {
caches[userid].history[noteId] = {};
}
var noteHistory = caches[userid].history[noteId];
var noteInfo = models.Note.parseNoteInfo(document);
noteHistory.id = noteId;
noteHistory.text = noteInfo.title;
noteHistory.time = moment().valueOf();
noteHistory.tags = noteInfo.tags;
caches[userid].isDirty = true;
});
}
}
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)
});
});
} else {
return response.errorForbidden(res);
}
}
function historyPost(req, res) {
if (req.isAuthenticated()) {
var noteId = req.params.noteId;
if (!noteId) {
if (typeof req.body['history'] === 'undefined') return response.errorBadRequest(res);
if (config.debug)
logger.info('SERVER received history from [' + req.user.id + ']: ' + req.body.history);
try {
var history = JSON.parse(req.body.history);
} catch (err) {
return response.errorBadRequest(res);
}
if (Array.isArray(history)) {
setHistory(req.user.id, history);
caches[req.user.id].isDirty = true;
res.end();
} else {
return response.errorBadRequest(res);
}
} else {
if (typeof req.body['pinned'] === 'undefined') return response.errorBadRequest(res);
getHistory(req.user.id, function (err, history) {
if (err) return response.errorInternalError(res);
if (!history) return response.errorNotFound(res);
if (!caches[req.user.id].history[noteId]) return response.errorNotFound(res);
if (req.body.pinned === 'true' || req.body.pinned === 'false') {
caches[req.user.id].history[noteId].pinned = (req.body.pinned === 'true');
caches[req.user.id].isDirty = true;
res.end();
} else {
return response.errorBadRequest(res);
}
});
}
} else {
return response.errorForbidden(res);
}
}
function historyDelete(req, res) {
if (req.isAuthenticated()) {
var noteId = req.params.noteId;
if (!noteId) {
setHistory(req.user.id, []);
caches[req.user.id].isDirty = true;
res.end();
} else {
getHistory(req.user.id, function (err, history) {
if (err) return response.errorInternalError(res);
if (!history) return response.errorNotFound(res);
delete caches[req.user.id].history[noteId];
caches[req.user.id].isDirty = true;
res.end();
});
}
} else {
return response.errorForbidden(res);
}
}
module.exports = History;