From 43b012b32e428924bb0abe48a2b35505f87393a0 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Thu, 5 Dec 2013 12:20:56 -0500 Subject: [PATCH 1/3] defactored getUnreadTopics into separate getUnreadTids method -- for no reason, mind you --- src/topics.js | 113 +++++++++++++++++++++++++++----------------------- 1 file changed, 60 insertions(+), 53 deletions(-) diff --git a/src/topics.js b/src/topics.js index 01b36c6136..9182f374c7 100644 --- a/src/topics.js +++ b/src/topics.js @@ -304,8 +304,52 @@ var async = require('async'), ); }; - Topics.getUnreadTopics = function(uid, start, stop, callback) { + Topics.getUnreadTids = function(uid, start, stop, callback) { + var unreadTids = [], + done = false; + + function continueCondition() { + return unreadTids.length < 20 && !done; + } + async.whilst(continueCondition, function(callback) { + RDB.zrevrange('topics:recent', start, stop, function(err, tids) { + if (err) { + return callback(err); + } + + if (tids && !tids.length) { + done = true; + return callback(null); + } + + if (uid === 0) { + unreadTids.push.apply(unreadTids, tids); + callback(null); + } else { + Topics.hasReadTopics(tids, uid, function(read) { + + var newtids = tids.filter(function(tid, index, self) { + return parseInt(read[index], 10) === 0; + }); + + unreadTids.push.apply(unreadTids, newtids); + + if(continueCondition()) { + start = stop + 1; + stop = start + 19; + } + + callback(null); + }); + } + }); + }, function(err) { + callback(err, unreadTids); + }); + }; + + Topics.getUnreadTopics = function(uid, start, stop, callback) { var unreadTopics = { 'category_name': 'Unread', 'show_sidebar': 'hidden', @@ -326,66 +370,29 @@ var async = require('async'), Topics.getTopicsByTids(topicIds, uid, function(topicData) { unreadTopics.topics = topicData; unreadTopics.nextStart = start + topicIds.length; - if (!topicData || topicData.length === 0) + if (!topicData || topicData.length === 0) { unreadTopics.no_topics_message = 'show'; - if (uid === 0 || topicData.length === 0) + } + if (uid === 0 || topicData.length === 0) { unreadTopics.show_markallread_button = 'hidden'; + } + callback(unreadTopics); }); } - var unreadTids = [], - done = false; - - function continueCondition() { - return unreadTids.length < 20 && !done; - } - - async.whilst( - continueCondition, - function(callback) { - RDB.zrevrange('topics:recent', start, stop, function(err, tids) { - if (err) - return callback(err); - - if (tids && !tids.length) { - done = true; - return callback(null); - } - - if (uid === 0) { - unreadTids.push.apply(unreadTids, tids); - callback(null); - } else { - Topics.hasReadTopics(tids, uid, function(read) { - - var newtids = tids.filter(function(tid, index, self) { - return parseInt(read[index], 10) === 0; - }); - - unreadTids.push.apply(unreadTids, newtids); - - if(continueCondition()) { - start = stop + 1; - stop = start + 19; - } - - callback(null); - }); - } - }); - }, - function(err) { - if (err) - return callback([]); - if (unreadTids.length) - sendUnreadTopics(unreadTids); - else - noUnreadTopics(); + Topics.getUnreadTids(uid, start, stop, function(err, unreadTids) { + if (err) { + return callback([]); + } + if (unreadTids.length) { + sendUnreadTopics(unreadTids); + } else { + noUnreadTopics(); } - ); - } + }); + }; Topics.getTopicsByTids = function(tids, current_user, callback, category_id) { From d9ee9bf5e3e2d68f0d2855680f7de3b4387ef593 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Thu, 5 Dec 2013 13:59:16 -0500 Subject: [PATCH 2/3] revamped handling of unread messages, so that a socket call is made to all socket clients whenever a new unread message is available. Prior behaviour had the unread count updated via ajax call on ajaxify (which was clumsy at best and didn't update automagically) --- public/src/forum/footer.js | 20 ++++++++++++++++++- public/src/utils.js | 28 +++++++++++++------------- src/feed.js | 2 +- src/posts.js | 3 +++ src/routes/api.js | 2 +- src/routes/debug.js | 5 +++++ src/topics.js | 40 +++++++++++++++++++++++++++++++++----- src/webserver.js | 2 +- src/websockets.js | 13 ++++++++++--- 9 files changed, 89 insertions(+), 26 deletions(-) diff --git a/public/src/forum/footer.js b/public/src/forum/footer.js index 1fe343d929..9de817550c 100644 --- a/public/src/forum/footer.js +++ b/public/src/forum/footer.js @@ -183,7 +183,7 @@ if (chat.modalExists(data.fromuid)) { modal = chat.getModal(data.fromuid); chat.appendChatMessage(modal, data.message, data.timestamp); - + if (modal.is(":visible")) { chat.load(modal.attr('UUID')); } else { @@ -196,6 +196,24 @@ }); }); + function updateUnreadCount(count) { + var badge = $('#numUnreadBadge'); + badge.html(count > 20 ? '20+' : count); + + if (count > 0) { + badge + .removeClass('badge-inverse') + .addClass('badge-important'); + } else { + badge + .removeClass('badge-important') + .addClass('badge-inverse'); + } + } + + socket.on('event:unread.updateCount', updateUnreadCount); + socket.emit('api:unread.count', updateUnreadCount); + require(['mobileMenu'], function(mobileMenu) { mobileMenu.init(); }); diff --git a/public/src/utils.js b/public/src/utils.js index 3e890b3f73..d82fe9cd14 100644 --- a/public/src/utils.js +++ b/public/src/utils.js @@ -185,20 +185,20 @@ } }); - jQuery.getJSON(RELATIVE_PATH + '/api/unread/total', function(data) { - var badge = jQuery('#numUnreadBadge'); - badge.html(data.count > 20 ? '20+' : data.count); - - if (data.count > 0) { - badge - .removeClass('badge-inverse') - .addClass('badge-important'); - } else { - badge - .removeClass('badge-important') - .addClass('badge-inverse'); - } - }); + // jQuery.getJSON(RELATIVE_PATH + '/api/unread/total', function(data) { + // var badge = jQuery('#numUnreadBadge'); + // badge.html(data.count > 20 ? '20+' : data.count); + + // if (data.count > 0) { + // badge + // .removeClass('badge-inverse') + // .addClass('badge-important'); + // } else { + // badge + // .removeClass('badge-important') + // .addClass('badge-inverse'); + // } + // }); }, isRelativeUrl: function(url) { diff --git a/src/feed.js b/src/feed.js index 522edab086..85ffa63130 100644 --- a/src/feed.js +++ b/src/feed.js @@ -28,7 +28,7 @@ } Feed.updateTopic = function (tid, callback) { - topics.getTopicWithPosts(tid, 0, 0, -1, function (err, topicData) { + topics.getTopicWithPosts(tid, 0, 0, -1, true, function (err, topicData) { if (err) { return callback(new Error('topic-invalid')); } diff --git a/src/posts.js b/src/posts.js index 66c79350e2..ac4cf01400 100644 --- a/src/posts.js +++ b/src/posts.js @@ -149,6 +149,9 @@ var RDB = require('./redis'), next(); }); }, + function(next) { + topics.pushUnreadCount(null, next); + }, function(next) { Posts.getCidByPid(postData.pid, function(err, cid) { if(err) { diff --git a/src/routes/api.js b/src/routes/api.js index 389763bac5..9ee9fb14c3 100644 --- a/src/routes/api.js +++ b/src/routes/api.js @@ -115,7 +115,7 @@ var path = require('path'), app.get('/topic/:id/:slug?', function (req, res, next) { var uid = (req.user) ? req.user.uid : 0; - topics.getTopicWithPosts(req.params.id, uid, 0, 10, function (err, data) { + topics.getTopicWithPosts(req.params.id, uid, 0, 10, false, function (err, data) { if (!err) { if (data.deleted === '1' && data.expose_tools === 0) { return res.json(404, {}); diff --git a/src/routes/debug.js b/src/routes/debug.js index d089a48b2c..104e48bd08 100644 --- a/src/routes/debug.js +++ b/src/routes/debug.js @@ -78,6 +78,11 @@ var DebugRoute = function(app) { }); }); }); + + app.get('/test', function(req, res) { + topics.pushUnreadCount(); + res.send(); + }); }); }; diff --git a/src/topics.js b/src/topics.js index 9182f374c7..72073bf18a 100644 --- a/src/topics.js +++ b/src/topics.js @@ -17,7 +17,9 @@ var async = require('async'), notifications = require('./notifications'), feed = require('./feed'), favourites = require('./favourites'), - meta = require('./meta'); + meta = require('./meta') + + websockets = require('./websockets'); (function(Topics) { @@ -91,7 +93,6 @@ var async = require('async'), Topics.markAsRead(tid, uid); }); - // in future it may be possible to add topics to several categories, so leaving the door open here. RDB.zadd('categories:' + cid + ':tid', timestamp, tid); RDB.hincrby('category:' + cid, 'topic_count', 1); @@ -109,6 +110,8 @@ var async = require('async'), // Auto-subscribe the post creator to the newly created topic threadTools.toggleFollow(tid, uid); + Topics.pushUnreadCount(); + Topics.getTopicForCategoryView(tid, uid, function(topicData) { topicData.unreplied = 1; @@ -394,6 +397,29 @@ var async = require('async'), }); }; + Topics.pushUnreadCount = function(uids, callback) { + console.log('uids', uids); + if (uids == 0) throw new Error(); + if (!uids) { + clients = websockets.getConnectedClients(); + uids = Object.keys(clients); + } else if (!Array.isArray(uids)) { + uids = [uids]; + } + + async.each(uids, function(uid, next) { + Topics.getUnreadTids(uid, 0, 19, function(err, tids) { + websockets.in('uid_' + uid).emit('event:unread.updateCount', tids.length); + }); + }, function(err) { + winston.error(err); + + if (callback) { + callback(); + } + }); + }; + Topics.getTopicsByTids = function(tids, current_user, callback, category_id) { var retrieved_topics = []; @@ -497,14 +523,18 @@ var async = require('async'), } - Topics.getTopicWithPosts = function(tid, current_user, start, end, callback) { + Topics.getTopicWithPosts = function(tid, current_user, start, end, quiet, callback) { threadTools.exists(tid, function(exists) { if (!exists) { return callback(new Error('Topic tid \'' + tid + '\' not found')); } - Topics.markAsRead(tid, current_user); - Topics.increaseViewCount(tid); + // "quiet" is used for things like RSS feed updating, HTML parsing for non-js users, etc + if (!quiet) { + Topics.markAsRead(tid, current_user); + Topics.pushUnreadCount(current_user); + Topics.increaseViewCount(tid); + } function getTopicData(next) { Topics.getTopicData(tid, next); diff --git a/src/webserver.js b/src/webserver.js index 013ae4b8f4..389255a0a7 100644 --- a/src/webserver.js +++ b/src/webserver.js @@ -465,7 +465,7 @@ var path = require('path'), async.waterfall([ function (next) { - topics.getTopicWithPosts(tid, ((req.user) ? req.user.uid : 0), 0, -1, function (err, topicData) { + topics.getTopicWithPosts(tid, ((req.user) ? req.user.uid : 0), 0, -1, true, function (err, topicData) { if (topicData) { if (topicData.deleted === '1' && topicData.expose_tools === 0) { return next(new Error('Topic deleted'), null); diff --git a/src/websockets.js b/src/websockets.js index ca85bd216c..6fea81be45 100644 --- a/src/websockets.js +++ b/src/websockets.js @@ -66,7 +66,6 @@ websockets.init = function(io) { var hs = socket.handshake, sessionID, uid, lastPostTime = 0; - // Validate the session, if present socketCookieParser(hs, {}, function(err) { sessionID = socket.handshake.signedCookies["express.sid"]; @@ -106,8 +105,6 @@ websockets.init = function(io) { }); }); - - socket.on('disconnect', function() { var index = userSockets[uid].indexOf(socket); @@ -870,6 +867,12 @@ websockets.init = function(io) { }); }); + socket.on('api:unread.count', function(callback) { + topics.getUnreadTids(uid, 0, 19, function(err, tids) { + socket.emit('event:unread.updateCount', tids.length); + }); + }); + socket.on('api:category.loadMore', function(data, callback) { var start = data.after, end = start + 9; @@ -1139,6 +1142,10 @@ websockets.init = function(io) { websockets.in = function(room) { return io.sockets.in(room); }; + + websockets.getConnectedClients = function() { + return userSockets; + } } })(module.exports); From 041e77f68851d7d90b949cd878140276cc68b715 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Thu, 5 Dec 2013 14:48:27 -0500 Subject: [PATCH 3/3] fixing replying (whoops) --- public/src/utils.js | 15 --------------- src/topics.js | 6 ++++-- 2 files changed, 4 insertions(+), 17 deletions(-) diff --git a/public/src/utils.js b/public/src/utils.js index d82fe9cd14..a7afef8ef7 100644 --- a/public/src/utils.js +++ b/public/src/utils.js @@ -184,21 +184,6 @@ notificationIcon.className = 'fa fa-circle active'; } }); - - // jQuery.getJSON(RELATIVE_PATH + '/api/unread/total', function(data) { - // var badge = jQuery('#numUnreadBadge'); - // badge.html(data.count > 20 ? '20+' : data.count); - - // if (data.count > 0) { - // badge - // .removeClass('badge-inverse') - // .addClass('badge-important'); - // } else { - // badge - // .removeClass('badge-important') - // .addClass('badge-inverse'); - // } - // }); }, isRelativeUrl: function(url) { diff --git a/src/topics.js b/src/topics.js index 72073bf18a..da1e16c4cf 100644 --- a/src/topics.js +++ b/src/topics.js @@ -398,7 +398,6 @@ var async = require('async'), }; Topics.pushUnreadCount = function(uids, callback) { - console.log('uids', uids); if (uids == 0) throw new Error(); if (!uids) { clients = websockets.getConnectedClients(); @@ -410,9 +409,12 @@ var async = require('async'), async.each(uids, function(uid, next) { Topics.getUnreadTids(uid, 0, 19, function(err, tids) { websockets.in('uid_' + uid).emit('event:unread.updateCount', tids.length); + next(); }); }, function(err) { - winston.error(err); + if (err) { + winston.error(err.message); + } if (callback) { callback();