Attempting to fix dashboard stories by no longer relying on full river reloads when a new story comes in but to fetch that story if it is newer (or older) than last visible dashboard story.

This commit is contained in:
Samuel Clay 2016-12-13 16:29:42 -08:00
parent 5cfd564308
commit c5d5ea5001
14 changed files with 114 additions and 38 deletions

View file

@ -1,9 +1,6 @@
import datetime import datetime
import re import re
import redis import redis
from cgi import escape
from django.db import models
from django.contrib.auth.models import User
from django.contrib.sites.models import Site from django.contrib.sites.models import Site
from django.core.mail import EmailMultiAlternatives from django.core.mail import EmailMultiAlternatives
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
@ -87,7 +84,7 @@ class EmailNewsletter:
usersub.needs_unread_recalc = True usersub.needs_unread_recalc = True
usersub.save() usersub.save()
self._publish_to_subscribers(feed) self._publish_to_subscribers(feed, story.story_hash)
MFetchHistory.add(feed_id=feed.pk, fetch_type='push') MFetchHistory.add(feed_id=feed.pk, fetch_type='push')
logging.user(user, "~FCNewsletter feed story: ~SB%s~SN / ~SB%s" % (story.story_title, feed)) logging.user(user, "~FCNewsletter feed story: ~SB%s~SN / ~SB%s" % (story.story_title, feed))
@ -109,12 +106,12 @@ class EmailNewsletter:
params = dict(receiver_user_id=user.pk, email_type='first_newsletter') params = dict(receiver_user_id=user.pk, email_type='first_newsletter')
try: try:
sent_email = MSentEmail.objects.get(**params) MSentEmail.objects.get(**params)
if not force: if not force:
# Return if email already sent # Return if email already sent
return return
except MSentEmail.DoesNotExist: except MSentEmail.DoesNotExist:
sent_email = MSentEmail.objects.create(**params) MSentEmail.objects.create(**params)
text = render_to_string('mail/email_first_newsletter.txt', {}) text = render_to_string('mail/email_first_newsletter.txt', {})
html = render_to_string('mail/email_first_newsletter.xhtml', {}) html = render_to_string('mail/email_first_newsletter.xhtml', {})
@ -177,10 +174,10 @@ class EmailNewsletter:
content = content.replace('!important', '') content = content.replace('!important', '')
return content return content
def _publish_to_subscribers(self, feed): def _publish_to_subscribers(self, feed, story_hash):
try: try:
r = redis.Redis(connection_pool=settings.REDIS_PUBSUB_POOL) r = redis.Redis(connection_pool=settings.REDIS_PUBSUB_POOL)
listeners_count = r.publish(str(feed.pk), 'story:new') listeners_count = r.publish(str(feed.pk), 'story:new:%s' % story_hash)
if listeners_count: if listeners_count:
logging.debug(" ---> [%-30s] ~FMPublished to %s subscribers" % (feed.title[:30], listeners_count)) logging.debug(" ---> [%-30s] ~FMPublished to %s subscribers" % (feed.title[:30], listeners_count))
except redis.ConnectionError: except redis.ConnectionError:

View file

@ -1227,6 +1227,7 @@ def load_river_stories__redis(request):
query = request.REQUEST.get('query', '').strip() query = request.REQUEST.get('query', '').strip()
include_hidden = is_true(request.REQUEST.get('include_hidden', False)) include_hidden = is_true(request.REQUEST.get('include_hidden', False))
include_feeds = is_true(request.REQUEST.get('include_feeds', False)) include_feeds = is_true(request.REQUEST.get('include_feeds', False))
initial_dashboard = is_true(request.GET.get('initial_dashboard', False))
now = localtime_for_timezone(datetime.datetime.now(), user.profile.timezone) now = localtime_for_timezone(datetime.datetime.now(), user.profile.timezone)
usersubs = [] usersubs = []
code = 1 code = 1
@ -1234,7 +1235,7 @@ def load_river_stories__redis(request):
offset = (page-1) * limit offset = (page-1) * limit
story_date_order = "%sstory_date" % ('' if order == 'oldest' else '-') story_date_order = "%sstory_date" % ('' if order == 'oldest' else '-')
if limit == 4 and not request.user.is_staff: if limit == 4 and not initial_dashboard:
logging.user(request, "~FRIgnoring ~FCdashboard river stories") logging.user(request, "~FRIgnoring ~FCdashboard river stories")
return dict(code=-1, message="Had to turn off dashboard river for now.", stories=[]) return dict(code=-1, message="Had to turn off dashboard river for now.", stories=[])

View file

@ -1169,6 +1169,7 @@ class Feed(models.Model):
try: try:
s.save() s.save()
ret_values['new'] += 1 ret_values['new'] += 1
s.publish_to_subscribers()
except (IntegrityError, OperationError), e: except (IntegrityError, OperationError), e:
ret_values['error'] += 1 ret_values['error'] += 1
if settings.DEBUG: if settings.DEBUG:
@ -2232,6 +2233,13 @@ class MStory(mongo.Document):
super(MStory, self).delete(*args, **kwargs) super(MStory, self).delete(*args, **kwargs)
def publish_to_subscribers(self):
try:
r = redis.Redis(connection_pool=settings.REDIS_PUBSUB_POOL)
r.publish("%s:story" % (self.story_feed_id), '%s,%s' % (self.story_hash, self.story_date.strftime('%s')))
except redis.ConnectionError:
logging.debug(" ***> [%-30s] ~BMRedis is unavailable for real-time." % (Feed.get_by_id(self.story_feed_id).title[:30],))
@classmethod @classmethod
def purge_feed_stories(cls, feed, cutoff, verbose=True): def purge_feed_stories(cls, feed, cutoff, verbose=True):
stories = cls.objects(story_feed_id=feed.pk) stories = cls.objects(story_feed_id=feed.pk)

View file

@ -1863,7 +1863,7 @@ class MSharedStory(mongo.DynamicDocument):
try: try:
r = redis.Redis(connection_pool=settings.REDIS_PUBSUB_POOL) r = redis.Redis(connection_pool=settings.REDIS_PUBSUB_POOL)
feed_id = "social:%s" % self.user_id feed_id = "social:%s" % self.user_id
listeners_count = r.publish(feed_id, 'story:new') listeners_count = r.publish(feed_id, 'story:new:%s' % self.story_hash)
if listeners_count: if listeners_count:
logging.debug(" ---> ~FMPublished to %s subscribers" % (listeners_count)) logging.debug(" ---> ~FMPublished to %s subscribers" % (listeners_count))
except redis.ConnectionError: except redis.ConnectionError:

View file

@ -569,6 +569,7 @@ NEWSBLUR.AssetModel = Backbone.Router.extend({
} }
if (data.stories && first_load) { if (data.stories && first_load) {
// console.log(['first load river', data.stories.length, ' stories']);
this.feed_tags = data.feed_tags || {}; this.feed_tags = data.feed_tags || {};
this.feed_authors = data.feed_authors || {}; this.feed_authors = data.feed_authors || {};
this.active_feed = this.get_feed(feed_id); this.active_feed = this.get_feed(feed_id);
@ -583,6 +584,7 @@ NEWSBLUR.AssetModel = Backbone.Router.extend({
this.starred_stories = data.starred_stories; this.starred_stories = data.starred_stories;
this.stories.reset(data.stories, {added: data.stories.length}); this.stories.reset(data.stories, {added: data.stories.length});
} else if (data.stories) { } else if (data.stories) {
// console.log(['adding to river', data.stories.length, ' stories']);
this.stories.add(data.stories, {silent: true}); this.stories.add(data.stories, {silent: true});
this.stories.trigger('add', {added: data.stories.length}); this.stories.trigger('add', {added: data.stories.length});
} }
@ -707,6 +709,12 @@ NEWSBLUR.AssetModel = Backbone.Router.extend({
fetch_dashboard_stories: function(feed_id, feeds, page, callback, error_callback) { fetch_dashboard_stories: function(feed_id, feeds, page, callback, error_callback) {
var self = this; var self = this;
this.dashboard_stories.comparator = function(a, b) {
var a_time = parseInt(a.get('story_timestamp'), 10);
var b_time = parseInt(b.get('story_timestamp'), 10);
return a_time < b_time ? 1 : (a_time == b_time) ? 0 : -1;
};
var pre_callback = function(data) { var pre_callback = function(data) {
if (data.user_profiles) { if (data.user_profiles) {
self.add_user_profiles(data.user_profiles); self.add_user_profiles(data.user_profiles);
@ -731,13 +739,32 @@ NEWSBLUR.AssetModel = Backbone.Router.extend({
order: this.view_setting(feed_id, 'order'), order: this.view_setting(feed_id, 'order'),
read_filter: this.view_setting(feed_id, 'read_filter'), read_filter: this.view_setting(feed_id, 'read_filter'),
include_hidden: false, include_hidden: false,
dashboard: true dashboard: true,
initial_dashboard: true
}, pre_callback, error_callback, { }, pre_callback, error_callback, {
'ajax_group': 'dashboard', 'ajax_group': 'dashboard',
'request_type': 'GET' 'request_type': 'GET'
}); });
}, },
add_dashboard_story: function(story_hash) {
var self = this;
var pre_callback = function(data) {
self.dashboard_stories.add(data.stories, {silent: true});
self.dashboard_stories.limit(NEWSBLUR.Globals.is_premium ? 5 : 3);
self.dashboard_stories.trigger('reset', {added: 1});
};
this.make_request('/reader/river_stories', {
h: story_hash,
dashboard: true
}, pre_callback, null, {
'ajax_group': 'dashboard',
'request_type': 'GET'
});
},
fetch_river_blurblogs_stories: function(feed_id, page, options, callback, error_callback, first_load) { fetch_river_blurblogs_stories: function(feed_id, page, options, callback, error_callback, first_load) {
var self = this; var self = this;

View file

@ -258,7 +258,7 @@ NEWSBLUR.Models.Story = Backbone.Model.extend({
NEWSBLUR.Collections.Stories = Backbone.Collection.extend({ NEWSBLUR.Collections.Stories = Backbone.Collection.extend({
model: NEWSBLUR.Models.Story, model: NEWSBLUR.Models.Story,
read_stories: [], read_stories: [],
previous_stories_stack: [], previous_stories_stack: [],
@ -459,6 +459,10 @@ NEWSBLUR.Collections.Stories = Backbone.Collection.extend({
}); });
}, },
limit: function(count) {
this.models = this.models.slice(0, count);
},
// =========== // ===========
// = Getters = // = Getters =
// =========== // ===========

View file

@ -1541,7 +1541,7 @@
var $original_tabs = $('.task_view_page, .task_view_story'); var $original_tabs = $('.task_view_page, .task_view_story');
var $page_tab = $('.task_view_page'); var $page_tab = $('.task_view_page');
view = view || NEWSBLUR.assets.view_setting(feed_id); view = view || NEWSBLUR.assets.view_setting(feed_id);
console.log(['set_correct_story_view_for_feed', feed_id, view]); // console.log(['set_correct_story_view_for_feed', feed_id, view]);
$original_tabs.removeClass('NB-disabled-page') $original_tabs.removeClass('NB-disabled-page')
.removeClass('NB-disabled') .removeClass('NB-disabled')
@ -1632,7 +1632,7 @@
$page_tab.addClass('NB-disabled'); $page_tab.addClass('NB-disabled');
} }
console.log(['setting reader.story_view', view, " was:", this.story_view]); // console.log(['setting reader.story_view', view, " was:", this.story_view]);
this.story_view = view; this.story_view = view;
}, },
@ -1963,9 +1963,12 @@
NEWSBLUR.assets.stories.reset(NEWSBLUR.assets.dashboard_stories.map(function(story) { NEWSBLUR.assets.stories.reset(NEWSBLUR.assets.dashboard_stories.map(function(story) {
return story.toJSON(); return story.toJSON();
})); }));
this.model.fetch_river_stories(this.active_feed, feeds, 1,
_.bind(this.post_open_river_stories, this), NEWSBLUR.app.taskbar_info.show_stories_error, false);
} else {
this.model.fetch_river_stories(this.active_feed, feeds, 1,
_.bind(this.post_open_river_stories, this), NEWSBLUR.app.taskbar_info.show_stories_error, true);
} }
this.model.fetch_river_stories(this.active_feed, feeds, 1,
_.bind(this.post_open_river_stories, this), NEWSBLUR.app.taskbar_info.show_stories_error, true);
}, },
post_open_river_stories: function(data, first_load) { post_open_river_stories: function(data, first_load) {
@ -2323,7 +2326,7 @@
this.model.mark_feed_as_read([feed_id], cutoff_timestamp, direction, this.model.mark_feed_as_read([feed_id], cutoff_timestamp, direction,
feed_id == this.active_feed, _.bind(function() { feed_id == this.active_feed, _.bind(function() {
this.feeds_unread_count(feed_id); this.feed_unread_count(feed_id);
}, this)); }, this));
if (!direction && NEWSBLUR.assets.preference('markread_nextfeed') == 'nextfeed' && if (!direction && NEWSBLUR.assets.preference('markread_nextfeed') == 'nextfeed' &&
@ -2865,7 +2868,7 @@
var feed_id = this.active_story_view(); var feed_id = this.active_story_view();
var feed = this.model.get_feed(feed_id); var feed = this.model.get_feed(feed_id);
view = view || this.story_view; view = view || this.story_view;
NEWSBLUR.log(['switch_taskbar_view', view, options.skip_save_type, feed]); // NEWSBLUR.log(['switch_taskbar_view', view, options.skip_save_type, feed]);
if (view == 'page' && feed && feed.get('has_exception') && if (view == 'page' && feed && feed.get('has_exception') &&
feed.get('exception_type') == 'page') { feed.get('exception_type') == 'page') {
@ -4756,12 +4759,19 @@
this.socket.on('feed:update', _.bind(function(feed_id, message) { this.socket.on('feed:update', _.bind(function(feed_id, message) {
NEWSBLUR.log(['Real-time feed update', feed_id, message]); NEWSBLUR.log(['Real-time feed update', feed_id, message]);
this.feed_unread_count(feed_id, { this.feed_unread_count(feed_id, {
realtime: true, realtime: true
new_story: true
}); });
}, this)); }, this));
this.socket.removeAllListeners('feed:story:new');
this.socket.on('feed:story:new', _.bind(function(feed_id, message) {
var story_hash = message.split(',')[0];
var timestamp = message.split(',')[1];
// NEWSBLUR.log(['Real-time new story', feed_id, story_hash, timestamp]);
NEWSBLUR.app.dashboard_river.new_story(story_hash, timestamp);
}, this));
this.socket.removeAllListeners(NEWSBLUR.Globals.username); this.socket.removeAllListeners(NEWSBLUR.Globals.username);
this.socket.removeAllListeners("user:update"); this.socket.removeAllListeners("user:update");
this.socket.on('user:update', _.bind(function(username, message) { this.socket.on('user:update', _.bind(function(username, message) {
@ -4974,18 +4984,9 @@
_.delay(_.bind(function() { _.delay(_.bind(function() {
this.model.feed_unread_count(feed_id, options.callback); this.model.feed_unread_count(feed_id, options.callback);
if (options.new_story) {
NEWSBLUR.app.dashboard_river.load_stories();
}
}, this), Math.random() * delay); }, this), Math.random() * delay);
}, },
feeds_unread_count: function(feed_ids, options) {
options = options || {};
this.model.feed_unread_count(feed_ids, options.callback);
},
update_interactions_count: function() { update_interactions_count: function() {
this.model.interactions_count(function(data) { this.model.interactions_count(function(data) {
NEWSBLUR.app.sidebar_header.update_interactions_count(data.interactions_count); NEWSBLUR.app.sidebar_header.update_interactions_count(data.interactions_count);

View file

@ -47,6 +47,7 @@ NEWSBLUR.Views.DashboardRiver = Backbone.View.extend({
// ========== // ==========
load_stories: function() { load_stories: function() {
// console.log(['dashboard river load_stories', this.page]);
// var feeds = NEWSBLUR.assets.folders.feed_ids_in_folder(); // var feeds = NEWSBLUR.assets.folders.feed_ids_in_folder();
var feeds = this.feeds(); var feeds = this.feeds();
if (!feeds.length) return; if (!feeds.length) return;
@ -54,7 +55,7 @@ NEWSBLUR.Views.DashboardRiver = Backbone.View.extend({
this.page = 1; this.page = 1;
this.story_titles.show_loading(); this.story_titles.show_loading();
NEWSBLUR.assets.fetch_dashboard_stories("river:", feeds, this.page, NEWSBLUR.assets.fetch_dashboard_stories(this.active_feed, feeds, this.page,
_.bind(this.post_load_stories, this), NEWSBLUR.app.taskbar_info.show_stories_error); _.bind(this.post_load_stories, this), NEWSBLUR.app.taskbar_info.show_stories_error);
}, },
@ -74,7 +75,7 @@ NEWSBLUR.Views.DashboardRiver = Backbone.View.extend({
var counts = NEWSBLUR.assets.folders.unread_counts(); var counts = NEWSBLUR.assets.folders.unread_counts();
var unread_view = NEWSBLUR.assets.preference('unread_view'); var unread_view = NEWSBLUR.assets.preference('unread_view');
if (unread_view >= 1) { if (unread_view >= 1) {
console.log(['counts', counts['ps'], visible, this.page]); // console.log(['counts', counts['ps'], visible, this.page]);
if (counts['ps'] <= visible) { if (counts['ps'] <= visible) {
this.show_end_line(); this.show_end_line();
return; return;
@ -91,7 +92,7 @@ NEWSBLUR.Views.DashboardRiver = Backbone.View.extend({
var feeds = this.feeds(); var feeds = this.feeds();
this.page += 1; this.page += 1;
this.story_titles.show_loading(); this.story_titles.show_loading();
NEWSBLUR.assets.fetch_dashboard_stories("river:", feeds, this.page, NEWSBLUR.assets.fetch_dashboard_stories(this.active_feed, feeds, this.page,
_.bind(this.post_load_stories, this), NEWSBLUR.app.taskbar_info.show_stories_error); _.bind(this.post_load_stories, this), NEWSBLUR.app.taskbar_info.show_stories_error);
}, },
@ -118,6 +119,31 @@ NEWSBLUR.Views.DashboardRiver = Backbone.View.extend({
show_end_line: function() { show_end_line: function() {
this.story_titles.show_no_more_stories(); this.story_titles.show_no_more_stories();
this.$(".NB-end-line").addClass("NB-visible"); this.$(".NB-end-line").addClass("NB-visible");
},
new_story: function(story_hash, timestamp) {
var oldest_story = NEWSBLUR.assets.dashboard_stories.last();
if (oldest_story) {
var last_timestamp = parseInt(oldest_story.get('story_timestamp'), 10);
timestamp = parseInt(timestamp, 10);
if (NEWSBLUR.assets.view_setting(this.active_feed, 'order') == 'newest') {
if (timestamp < last_timestamp) {
// console.log(['New story older than last/oldest dashboard story', timestamp, '<', last_timestamp]);
return;
}
} else {
if (timestamp > last_timestamp) {
// console.log(['New story older than last/newest dashboard story', timestamp, '<', last_timestamp]);
return;
}
}
}
console.log(['Fetching dashboard story', story_hash]);
NEWSBLUR.assets.add_dashboard_story(story_hash);
} }
}); });

View file

@ -60,6 +60,8 @@ NEWSBLUR.Views.TextTabView = Backbone.View.extend({
}, },
render: function(data) { render: function(data) {
if (!this.story) return;
if (data && (data.story_id != this.story.get('id') || if (data && (data.story_id != this.story.get('id') ||
data.feed_id != this.story.get('story_feed_id'))) { data.feed_id != this.story.get('story_feed_id'))) {
return; return;

View file

@ -1464,7 +1464,6 @@
// route callback be fired (not usually desirable), or `replace: true`, if // route callback be fired (not usually desirable), or `replace: true`, if
// you wish to modify the current URL without adding an entry to the history. // you wish to modify the current URL without adding an entry to the history.
navigate: function(fragment, options) { navigate: function(fragment, options) {
console.log(['backbone navigate', fragment, options]);
if (!History.started) return false; if (!History.started) return false;
if (!options || options === true) options = {trigger: options}; if (!options || options === true) options = {trigger: options};
fragment = this.getFragment(fragment || ''); fragment = this.getFragment(fragment || '');

View file

@ -59,12 +59,16 @@ io.on 'connection', (socket) ->
" (#{io.engine.clientsCount} connected) " + " (#{io.engine.clientsCount} connected) " +
" #{if SECURE then "(SSL)" else "(non-SSL)"}" " #{if SECURE then "(SSL)" else "(non-SSL)"}"
socket.subscribe.subscribe @feeds socket.subscribe.subscribe @feeds
feeds_story = @feeds.map (f) -> "#{f}:story"
socket.subscribe.subscribe feeds_story
socket.subscribe.subscribe @username socket.subscribe.subscribe @username
socket.subscribe.on 'message', (channel, message) => socket.subscribe.on 'message', (channel, message) =>
log.info @username, "Update on #{channel}: #{message}" log.info @username, "Update on #{channel}: #{message}"
if channel == @username if channel == @username
socket.emit 'user:update', channel, message socket.emit 'user:update', channel, message
else if channel.indexOf(':story') >= 0
socket.emit 'feed:story:new', channel, message
else else
socket.emit 'feed:update', channel, message socket.emit 'feed:update', channel, message

View file

@ -63,8 +63,13 @@
})(this)); })(this));
socket.subscribe.on("connect", (function(_this) { socket.subscribe.on("connect", (function(_this) {
return function() { return function() {
var feeds_story;
log.info(_this.username, ("Connected (" + _this.feeds.length + " feeds, " + ip + "),") + (" (" + io.engine.clientsCount + " connected) ") + (" " + (SECURE ? "(SSL)" : "(non-SSL)"))); log.info(_this.username, ("Connected (" + _this.feeds.length + " feeds, " + ip + "),") + (" (" + io.engine.clientsCount + " connected) ") + (" " + (SECURE ? "(SSL)" : "(non-SSL)")));
socket.subscribe.subscribe(_this.feeds); socket.subscribe.subscribe(_this.feeds);
feeds_story = _this.feeds.map(function(f) {
return "" + f + ":story";
});
socket.subscribe.subscribe(feeds_story);
return socket.subscribe.subscribe(_this.username); return socket.subscribe.subscribe(_this.username);
}; };
})(this)); })(this));
@ -73,6 +78,8 @@
log.info(_this.username, "Update on " + channel + ": " + message); log.info(_this.username, "Update on " + channel + ": " + message);
if (channel === _this.username) { if (channel === _this.username) {
return socket.emit('user:update', channel, message); return socket.emit('user:update', channel, message);
} else if (channel.indexOf(':story') >= 0) {
return socket.emit('feed:story:new', channel, message);
} else { } else {
return socket.emit('feed:update', channel, message); return socket.emit('feed:update', channel, message);
} }

View file

@ -72,7 +72,7 @@
{% render_getting_started %} {% render_getting_started %}
{% endif %} {% endif %}
{% if user.is_staff or user.username == "chrome" %} {% if user.is_staff %}
<div class="NB-module NB-module-river"> <div class="NB-module NB-module-river">
<h5 class="NB-module-header"> <h5 class="NB-module-header">
<div class="NB-module-river-settings NB-javascript"></div> <div class="NB-module-river-settings NB-javascript"></div>

View file

@ -954,7 +954,7 @@ class Dispatcher:
logging.debug(" ---> [%-30s] ~FRIntegrityError on feed: %s" % (feed.title[:30], feed.feed_address,)) logging.debug(" ---> [%-30s] ~FRIntegrityError on feed: %s" % (feed.title[:30], feed.feed_address,))
if ret_entries and ret_entries['new']: if ret_entries and ret_entries['new']:
self.publish_to_subscribers(feed) self.publish_to_subscribers(feed, ret_entries['new'])
done_msg = (u'%2s ---> [%-30s] ~FYProcessed in ~FM~SB%.4ss~FY~SN (~FB%s~FY) [%s]' % ( done_msg = (u'%2s ---> [%-30s] ~FYProcessed in ~FM~SB%.4ss~FY~SN (~FB%s~FY) [%s]' % (
identity, feed.title[:30], delta, identity, feed.title[:30], delta,
@ -973,10 +973,10 @@ class Dispatcher:
# time_taken = datetime.datetime.utcnow() - self.time_start # time_taken = datetime.datetime.utcnow() - self.time_start
def publish_to_subscribers(self, feed): def publish_to_subscribers(self, feed, new_count):
try: try:
r = redis.Redis(connection_pool=settings.REDIS_PUBSUB_POOL) r = redis.Redis(connection_pool=settings.REDIS_PUBSUB_POOL)
listeners_count = r.publish(str(feed.pk), 'story:new') listeners_count = r.publish(str(feed.pk), 'story:new_count:%s' % new_count)
if listeners_count: if listeners_count:
logging.debug(" ---> [%-30s] ~FMPublished to %s subscribers" % (feed.title[:30], listeners_count)) logging.debug(" ---> [%-30s] ~FMPublished to %s subscribers" % (feed.title[:30], listeners_count))
except redis.ConnectionError: except redis.ConnectionError: