Update to add cache to history

This commit is contained in:
Wu Cheng-Han 2016-10-10 20:51:46 +08:00
parent dc343978eb
commit af77bb8f59
2 changed files with 233 additions and 51 deletions

56
app.js
View file

@ -22,6 +22,7 @@ var i18n = require('i18n');
var config = require("./lib/config.js"); var config = require("./lib/config.js");
var logger = require("./lib/logger.js"); var logger = require("./lib/logger.js");
var auth = require("./lib/auth.js"); var auth = require("./lib/auth.js");
var history = require("./lib/history.js");
var response = require("./lib/response.js"); var response = require("./lib/response.js");
var models = require("./lib/models"); var models = require("./lib/models");
@ -365,56 +366,9 @@ app.get('/logout', function (req, res) {
res.redirect(config.serverurl + '/'); res.redirect(config.serverurl + '/');
}); });
//get history //get history
app.get('/history', function (req, res) { app.get('/history', history.historyGet);
if (req.isAuthenticated()) {
models.User.findOne({
where: {
id: req.user.id
}
}).then(function (user) {
if (!user)
return response.errorNotFound(res);
var history = [];
if (user.history)
history = JSON.parse(user.history);
res.send({
history: history
});
if (config.debug)
logger.info('read history success: ' + user.id);
}).catch(function (err) {
logger.error('read history failed: ' + err);
return response.errorInternalError(res);
});
} else {
return response.errorForbidden(res);
}
});
//post history //post history
app.post('/history', urlencodedParser, function (req, res) { app.post('/history', urlencodedParser, history.historyPost);
if (req.isAuthenticated()) {
if (config.debug)
logger.info('SERVER received history from [' + req.user.id + ']: ' + req.body.history);
models.User.update({
history: req.body.history
}, {
where: {
id: req.user.id
}
}).then(function (count) {
if (!count)
return response.errorNotFound(res);
if (config.debug)
logger.info("write user history success: " + req.user.id);
}).catch(function (err) {
logger.error('write history failed: ' + err);
return response.errorInternalError(res);
});
res.end();
} else {
return response.errorForbidden(res);
}
});
//get me info //get me info
app.get('/me', function (req, res) { app.get('/me', function (req, res) {
if (req.isAuthenticated()) { if (req.isAuthenticated()) {
@ -522,7 +476,7 @@ function startListen() {
// sync db then start listen // sync db then start listen
models.sequelize.sync().then(function () { models.sequelize.sync().then(function () {
// check if realtime is ready // check if realtime is ready
if (realtime.isReady()) { if (history.isReady() && realtime.isReady()) {
models.Revision.checkAllNotesRevision(function (err, notes) { models.Revision.checkAllNotesRevision(function (err, notes) {
if (err) return new Error(err); if (err) return new Error(err);
if (!notes || notes.length <= 0) return startListen(); if (!notes || notes.length <= 0) return startListen();
@ -549,7 +503,7 @@ process.on('SIGINT', function () {
socket.disconnect(true); socket.disconnect(true);
}); });
var checkCleanTimer = setInterval(function () { var checkCleanTimer = setInterval(function () {
if (realtime.isReady()) { if (history.isReady() && realtime.isReady()) {
models.Revision.checkAllNotesRevision(function (err, notes) { models.Revision.checkAllNotesRevision(function (err, notes) {
if (err) return new Error(err); if (err) return new Error(err);
if (notes.length <= 0) { if (notes.length <= 0) {

228
lib/history.js Normal file
View file

@ -0,0 +1,228 @@
//history
//external modules
var async = require('async');
var moment = require('moment');
//core
var config = require("./config.js");
var logger = require("./logger.js");
var response = require("./response.js");
var models = require("./models");
//public
var History = {
historyGet: historyGet,
historyPost: historyPost,
historyDelete: historyDelete,
isReady: isReady,
updateHistory: updateHistory
};
var caches = {};
//update when the history is dirty
var updater = setInterval(function () {
var deleted = [];
async.each(Object.keys(caches), function (key, callback) {
var cache = caches[key];
if (cache.isDirty) {
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]];
}
}, 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);
});
}
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 = parseHistoryToObject(history);
if (!caches[userid]) {
caches[userid] = {
history: {},
isDirty: false,
updateAt: Date.now()
};
}
caches[userid].history = history;
}
function updateHistory(userid, noteId, document) {
var t0 = new Date().getTime();
if (userid && noteId && document) {
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().format('MMMM Do YYYY, h:mm:ss a');
noteHistory.tags = noteInfo.tags;
caches[userid].isDirty = true;
var t1 = new Date().getTime();
console.warn(t1 - t0);
});
}
}
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: 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;