Merge branch 'ruserstory' into circular

* ruserstory:
  Adding real-time update on marking feed as read.
  Remvoing unused everything_unread param from river stories.
  Using story_hashes instead of fetch_stories parameter.
  Moving read stories over to RUserStory when merging feeds.
  Cleaning up MUserStory imports.
  Speeding up blurblog sub counter b yonly using unread stories instead of all stories since a certain date.
  Speeding up unread story counts.
  Speeding up unread story counts.
  Adding force calculate sub scores option.
  Showing starred and shared status on global/river blurblog.
  Fixing global feed to show read stories.
  Fixing reading stories in river blurblog.
  Moving river blurblog to no longer use MUserStory.
  Adding MUserStory back in when reading and unreading, to maintain backwards compatibility.
  Fully converting feeds and folders to use redis-only read stories (removing MUserStory). Still need to do socialsubs.

Conflicts:
	apps/reader/views.py
	apps/social/models.py
This commit is contained in:
Samuel Clay 2013-05-09 17:00:30 -07:00
commit 24201a496b
4 changed files with 273 additions and 269 deletions

View file

@ -2,6 +2,7 @@ import datetime
import time
import redis
import hashlib
import re
import mongoengine as mongo
from utils import log as logging
from utils import json_functions as json
@ -86,30 +87,8 @@ class UserSubscription(models.Model):
break
else:
self.delete()
@classmethod
def sync_all_redis(cls, user_id, skip_feed=False):
us = cls.objects.filter(user=user_id)
for sub in us:
print " ---> Syncing usersub: %s" % sub
sub.sync_redis(skip_feed=skip_feed)
def sync_redis(self, skip_feed=False):
r = redis.Redis(connection_pool=settings.REDIS_STORY_HASH_POOL)
UNREAD_CUTOFF = datetime.datetime.utcnow() - datetime.timedelta(days=settings.DAYS_OF_UNREAD+1)
userstories = MUserStory.objects.filter(feed_id=self.feed_id, user_id=self.user_id,
read_date__gte=UNREAD_CUTOFF)
total = userstories.count()
logging.debug(" ---> ~SN~FMSyncing ~SB%s~SN stories (%s)" % (total, self))
pipeline = r.pipeline()
for userstory in userstories:
userstory.sync_redis(r=pipeline)
pipeline.execute()
def get_stories(self, offset=0, limit=6, order='newest', read_filter='all', withscores=False):
def get_stories(self, offset=0, limit=6, order='newest', read_filter='all', withscores=False, hashes_only=False):
r = redis.Redis(connection_pool=settings.REDIS_STORY_HASH_POOL)
ignore_user_stories = False
@ -117,7 +96,8 @@ class UserSubscription(models.Model):
read_stories_key = 'RS:%s:%s' % (self.user_id, self.feed_id)
unread_stories_key = 'U:%s:%s' % (self.user_id, self.feed_id)
unread_ranked_stories_key = 'zU:%s:%s' % (self.user_id, self.feed_id)
unread_ranked_stories_key = 'z%sU:%s:%s' % ('h' if hashes_only else '',
self.user_id, self.feed_id)
if offset and not withscores and r.exists(unread_ranked_stories_key):
pass
else:
@ -167,7 +147,7 @@ class UserSubscription(models.Model):
if not ignore_user_stories:
r.delete(unread_stories_key)
if withscores:
if withscores or hashes_only:
return story_ids
elif story_ids:
story_date_order = "%sstory_date" % ('' if order == 'oldest' else '-')
@ -189,13 +169,17 @@ class UserSubscription(models.Model):
if not isinstance(feed_ids, list):
feed_ids = [feed_ids]
unread_ranked_stories_keys = 'zU:%s:feeds' % (user_id)
if offset and r.exists(unread_ranked_stories_keys):
story_hashes = range_func(unread_ranked_stories_keys, offset, limit)
return story_hashes
ranked_stories_keys = 'zU:%s:feeds' % (user_id)
unread_ranked_stories_keys = 'zhU:%s:feeds' % (user_id)
unread_story_hashes = cache.get(unread_ranked_stories_keys)
if offset and r.exists(ranked_stories_keys) and unread_story_hashes:
story_hashes = range_func(ranked_stories_keys, offset, limit)
return story_hashes, unread_story_hashes
else:
r.delete(unread_ranked_stories_keys)
r.delete(ranked_stories_keys)
cache.delete(unread_ranked_stories_keys)
unread_feed_story_hashes = {}
for feed_id in feed_ids:
try:
us = cls.objects.get(user=user_id, feed=feed_id)
@ -204,14 +188,16 @@ class UserSubscription(models.Model):
story_hashes = us.get_stories(offset=0, limit=200,
order=order, read_filter=read_filter,
withscores=True)
unread_feed_story_hashes[feed_id] = us.get_stories(read_filter='unread', limit=200,
hashes_only=True)
if story_hashes:
r.zadd(unread_ranked_stories_keys, **dict(story_hashes))
r.zadd(ranked_stories_keys, **dict(story_hashes))
story_hashes = range_func(unread_ranked_stories_keys, offset, limit)
r.expire(unread_ranked_stories_keys, 24*60*60)
story_hashes = range_func(ranked_stories_keys, offset, limit)
r.expire(ranked_stories_keys, 60*60)
cache.set(unread_ranked_stories_keys, unread_feed_story_hashes, 24*60*60)
return story_hashes
return story_hashes, unread_feed_story_hashes
@classmethod
def add_subscription(cls, user, feed_address, folder=None, bookmarklet=False, auto_active=True,
@ -336,10 +322,6 @@ class UserSubscription(models.Model):
self.oldest_unread_story_date = now
self.needs_unread_recalc = False
# No longer removing old user read stories, since they're needed for social,
# and they get cleaned up automatically when new stories come in.
# MUserStory.delete_old_stories(self.user_id, self.feed_id)
self.save()
def mark_story_ids_as_read(self, story_ids, request=None):
@ -368,7 +350,8 @@ class UserSubscription(models.Model):
'story': story,
'story_date': story.story_date,
})
RUserStory.mark_read(self.user_id, self.feed_id, story_id)
return data
def calculate_feed_scores(self, silent=False, stories=None, force=False):
@ -381,12 +364,12 @@ class UserSubscription(models.Model):
# logging.info(' ---> [%s] SKIPPING Computing scores: %s (1 week+)' % (self.user, self.feed))
return
if not self.feed.fetched_once:
if not silent:
logging.info(' ---> [%s] NOT Computing scores: %s' % (self.user, self.feed))
self.needs_unread_recalc = False
self.save()
return
# if not self.feed.fetched_once:
# if not silent:
# logging.info(' ---> [%s] NOT Computing scores: %s' % (self.user, self.feed))
# self.needs_unread_recalc = False
# self.save()
# return
feed_scores = dict(negative=0, neutral=0, positive=0)
@ -400,23 +383,18 @@ class UserSubscription(models.Model):
if not stories:
stories = cache.get('S:%s' % self.feed_id)
unread_story_hashes = self.get_stories(read_filter='unread', limit=500, hashes_only=True)
if not stories:
stories_db = MStory.objects(story_feed_id=self.feed_id,
story_date__gte=date_delta)
stories_db = MStory.objects(story_hash__in=unread_story_hashes)
stories = Feed.format_stories(stories_db, self.feed_id)
story_ids = [s['id'] for s in stories]
read_stories = MUserStory.objects(user_id=self.user_id,
feed_id=self.feed_id,
story_id__in=story_ids)
read_stories_ids = [us.story_id for us in read_stories]
oldest_unread_story_date = now
unread_stories = []
for story in stories:
if story['story_date'] < date_delta:
continue
if story['id'] not in read_stories_ids:
if story['story_hash'] in unread_story_hashes:
unread_stories.append(story)
if story['story_date'] < oldest_unread_story_date:
oldest_unread_story_date = story['story_date']
@ -488,22 +466,11 @@ class UserSubscription(models.Model):
logging.info(" *** ---> UserSubscriptionFolders error: %s" % e)
return
# Switch to original feed for the user subscription
logging.info(" ===> %s " % self.user)
self.feed = new_feed
self.needs_unread_recalc = True
try:
new_sub = UserSubscription.objects.get(user=self.user, feed=new_feed)
except UserSubscription.DoesNotExist:
self.save()
user_sub_folders.rewrite_feed(new_feed, old_feed)
else:
# except (IntegrityError, OperationError):
logging.info(" !!!!> %s already subscribed" % self.user)
self.delete()
return
# Switch read stories
stories = RUserStory.switch_feed(user_id=self.user_id, old_feed_id=old_feed.pk,
new_feed_id=new_feed.pk)
user_stories = MUserStory.objects(user_id=self.user_id, feed_id=old_feed.pk)
if user_stories.count() > 0:
logging.info(" ---> %s read stories" % user_stories.count())
@ -528,7 +495,7 @@ class UserSubscription(models.Model):
user_story.delete()
else:
user_story.delete()
def switch_feed_for_classifier(model):
duplicates = model.objects(feed_id=old_feed.pk, user_id=self.user_id)
if duplicates.count():
@ -548,6 +515,20 @@ class UserSubscription(models.Model):
switch_feed_for_classifier(MClassifierAuthor)
switch_feed_for_classifier(MClassifierFeed)
switch_feed_for_classifier(MClassifierTag)
# Switch to original feed for the user subscription
self.feed = new_feed
self.needs_unread_recalc = True
try:
new_sub = UserSubscription.objects.get(user=self.user, feed=new_feed)
except UserSubscription.DoesNotExist:
self.save()
user_sub_folders.rewrite_feed(new_feed, old_feed)
else:
# except (IntegrityError, OperationError):
logging.info(" !!!!> %s already subscribed" % self.user)
self.delete()
return
@classmethod
def collect_orphan_feeds(cls, user):
@ -583,6 +564,85 @@ class UserSubscription(models.Model):
usf.save()
class RUserStory:
RE_STORY_HASH = re.compile(r"^(\d{1,10}):(\w{6})$")
@classmethod
def story_hash(cls, story_id, story_feed_id):
if not cls.RE_STORY_HASH.match(story_id):
story, _ = MStory.find_story(story_feed_id=story_feed_id, story_id=story_id)
if not story: return
story_id = story.story_hash
return story_id
@classmethod
def split_story_hash(cls, story_hash):
matches = cls.RE_STORY_HASH.match(story_hash)
if matches:
groups = matches.groups()
return groups[0], groups[1]
return None, None
@classmethod
def story_hashes(cls, story_ids):
story_hashes = []
for story_id in story_ids:
story_hash = cls.story_hash(story_id)
if not story_hash: continue
story_hashes.append(story_hash)
return story_hashes
@classmethod
def mark_read(cls, user_id, story_feed_id, story_hash, r=None):
if not r:
r = redis.Redis(connection_pool=settings.REDIS_STORY_HASH_POOL)
story_hash = cls.story_hash(story_hash, story_feed_id=story_feed_id)
if not story_hash: return
all_read_stories_key = 'RS:%s' % (user_id)
r.sadd(all_read_stories_key, story_hash)
r.expire(all_read_stories_key, settings.DAYS_OF_UNREAD*24*60*60)
read_story_key = 'RS:%s:%s' % (user_id, story_feed_id)
r.sadd(read_story_key, story_hash)
r.expire(read_story_key, settings.DAYS_OF_UNREAD*24*60*60)
@staticmethod
def mark_unread(user_id, story_feed_id, story_hash):
r = redis.Redis(connection_pool=settings.REDIS_STORY_HASH_POOL)
r.srem('RS:%s' % user_id, story_hash)
r.srem('RS:%s:%s' % (user_id, story_feed_id), story_hash)
@staticmethod
def get_stories(user_id, feed_id, r=None):
if not r:
r = redis.Redis(connection_pool=settings.REDIS_STORY_HASH_POOL)
story_hashes = r.smembers("RS:%s:%s" % (user_id, feed_id))
return story_hashes
@classmethod
def switch_feed(cls, user_id, old_feed_id, new_feed_id):
r = redis.Redis(connection_pool=settings.REDIS_STORY_HASH_POOL)
p = r.pipeline()
story_hashes = cls.get_stories(user_id, old_feed_id, r=r)
for story_hash in story_hashes:
_, hash_story = cls.split_story_hash(story_hash)
new_story_hash = "%s:%s" % (new_feed_id, hash_story)
p.sadd("RS:%s:%s" % (user_id, new_feed_id), new_story_hash)
p.execute()
if len(story_hashes) > 0:
logging.info(" ---> %s read stories" % len(story_hashes))
class MUserStory(mongo.Document):
"""
Stories read by the user. These are deleted as the mark_read_date for the
@ -614,12 +674,12 @@ class MUserStory(mongo.Document):
def save(self, *args, **kwargs):
self.story_hash = self.feed_guid_hash
self.sync_redis()
# self.sync_redis()
super(MUserStory, self).save(*args, **kwargs)
def delete(self, *args, **kwargs):
self.remove_from_redis()
# self.remove_from_redis()
super(MUserStory, self).delete(*args, **kwargs)
@ -655,44 +715,22 @@ class MUserStory(mongo.Document):
cls.objects(user_id=user_id, feed_id=feed_id, read_date__lte=mark_read_date).delete()
@property
def story_db_id(self):
if self.story:
return self.story.id
elif self.found_story:
if '_ref' in self.found_story:
return self.found_story['_ref'].id
elif hasattr(self.found_story, 'id'):
return self.found_story.id
story, found_original = MStory.find_story(self.feed_id, self.story_id)
if story:
if found_original:
self.story = story
else:
self.found_story = story
self.save()
return story.id
def sync_redis(self, r=None):
if not r:
r = redis.Redis(connection_pool=settings.REDIS_STORY_HASH_POOL)
if self.story_db_id:
all_read_stories_key = 'RS:%s' % (self.user_id)
r.sadd(all_read_stories_key, self.feed_guid_hash)
r.expire(all_read_stories_key, settings.DAYS_OF_UNREAD*24*60*60)
all_read_stories_key = 'RS:%s' % (self.user_id)
r.sadd(all_read_stories_key, self.feed_guid_hash)
r.expire(all_read_stories_key, settings.DAYS_OF_UNREAD*24*60*60)
read_story_key = 'RS:%s:%s' % (self.user_id, self.feed_id)
r.sadd(read_story_key, self.feed_guid_hash)
r.expire(read_story_key, settings.DAYS_OF_UNREAD*24*60*60)
read_story_key = 'RS:%s:%s' % (self.user_id, self.feed_id)
r.sadd(read_story_key, self.feed_guid_hash)
r.expire(read_story_key, settings.DAYS_OF_UNREAD*24*60*60)
def remove_from_redis(self):
r = redis.Redis(connection_pool=settings.REDIS_STORY_HASH_POOL)
if self.story_db_id:
r.srem('RS:%s' % self.user_id, self.feed_guid_hash)
r.srem('RS:%s:%s' % (self.user_id, self.feed_id), self.feed_guid_hash)
r.srem('RS:%s' % self.user_id, self.feed_guid_hash)
r.srem('RS:%s:%s' % (self.user_id, self.feed_id), self.feed_guid_hash)
@classmethod
def sync_all_redis(cls, user_id=None, feed_id=None, force=False):
@ -825,7 +863,6 @@ class UserSubscriptionFolders(models.Model):
return
if user_sub:
user_sub.delete()
MUserStory.objects(user_id=self.user_id, feed_id=feed_id).delete()
def delete_folder(self, folder_to_delete, in_folder, feed_ids_in_folder, commit_delete=True):
def _find_folder_in_folders(old_folders, folder_name, feeds_to_delete, deleted_folder=None):

View file

@ -27,7 +27,7 @@ from apps.analyzer.models import apply_classifier_titles, apply_classifier_feeds
from apps.analyzer.models import apply_classifier_authors, apply_classifier_tags
from apps.analyzer.models import get_classifiers_for_user, sort_classifiers_by_feed
from apps.profile.models import Profile
from apps.reader.models import UserSubscription, UserSubscriptionFolders, MUserStory, Feature
from apps.reader.models import UserSubscription, UserSubscriptionFolders, MUserStory, RUserStory, Feature
from apps.reader.forms import SignupForm, LoginForm, FeatureForm
from apps.rss_feeds.models import MFeedIcon
from apps.statistics.models import MStatistics
@ -495,7 +495,6 @@ def load_single_feed(request, feed_id):
include_story_content = is_true(request.REQUEST.get('include_story_content', True))
dupe_feed_id = None
userstories_db = None
user_profiles = []
now = localtime_for_timezone(datetime.datetime.now(), user.profile.timezone)
if page: offset = limit * (page-1)
@ -543,27 +542,22 @@ def load_single_feed(request, feed_id):
classifier_tags=classifier_tags)
checkpoint3 = time.time()
userstories = []
unread_story_hashes = []
if stories:
story_ids = [story['id'] for story in stories]
userstories_db = MUserStory.objects(user_id=user.pk,
feed_id=feed.pk,
story_id__in=story_ids
).only('story_id').hint([('user_id', 1),
('feed_id', 1),
('story_id', 1)])
if read_filter == 'all' and usersub:
unread_story_hashes = usersub.get_stories(read_filter='unread', limit=500, hashes_only=True)
story_hashes = [story['story_hash'] for story in stories]
starred_stories = MStarredStory.objects(user_id=user.pk,
story_feed_id=feed.pk,
story_guid__in=story_ids
).only('story_guid', 'starred_date')
story_hash__in=story_hashes)\
.only('story_hash', 'starred_date')
shared_stories = MSharedStory.objects(user_id=user.pk,
story_feed_id=feed_id,
story_guid__in=story_ids
).only('story_guid', 'shared_date', 'comments')
starred_stories = dict([(story.story_guid, story.starred_date) for story in starred_stories])
shared_stories = dict([(story.story_guid, dict(shared_date=story.shared_date, comments=story.comments))
story_hash__in=story_hashes)\
.only('story_guid', 'shared_date', 'comments')
starred_stories = dict([(story.story_hash, story.starred_date) for story in starred_stories])
shared_stories = dict([(story.story_hash, dict(shared_date=story.shared_date, comments=story.comments))
for story in shared_stories])
userstories = set(us.story_id for us in userstories_db)
checkpoint4 = time.time()
@ -574,21 +568,22 @@ def load_single_feed(request, feed_id):
story['short_parsed_date'] = format_story_link_date__short(story_date, now)
story['long_parsed_date'] = format_story_link_date__long(story_date, now)
if usersub:
if story['id'] in userstories:
story['read_status'] = 1
elif not story.get('read_status') and story['story_date'] < usersub.mark_read_date:
story['read_status'] = 1
elif not story.get('read_status') and story['story_date'] > usersub.last_read_date:
story['read_status'] = 1
if read_filter == 'unread' and usersub:
story['read_status'] = 0
if story['id'] in starred_stories:
elif read_filter == 'all' and usersub:
story['read_status'] = story['story_hash'] not in unread_story_hashes
if story['story_hash'] in starred_stories:
story['starred'] = True
starred_date = localtime_for_timezone(starred_stories[story['id']], user.profile.timezone)
starred_date = localtime_for_timezone(starred_stories[story['story_hash']],
user.profile.timezone)
story['starred_date'] = format_story_link_date__long(starred_date, now)
if story['id'] in shared_stories:
if story['story_hash'] in shared_stories:
story['shared'] = True
shared_date = localtime_for_timezone(shared_stories[story['id']]['shared_date'], user.profile.timezone)
shared_date = localtime_for_timezone(shared_stories[story['story_hash']]['shared_date'],
user.profile.timezone)
story['shared_date'] = format_story_link_date__long(shared_date, now)
story['shared_comments'] = strip_tags(shared_stories[story['id']]['comments'])
story['shared_comments'] = strip_tags(shared_stories[story['story_hash']]['comments'])
else:
story['read_status'] = 1
story['intelligence'] = {
@ -613,8 +608,8 @@ def load_single_feed(request, feed_id):
diff4 = checkpoint4-start
timediff = time.time()-start
last_update = relative_timesince(feed.last_update)
time_breakdown = ("~SN~FR(~SB%.4s/%.4s/%.4s/%.4s(%s)~SN)" % (
diff1, diff2, diff3, diff4, userstories_db and userstories_db.count() or '~SN0~SB')
time_breakdown = ("~SN~FR(~SB%.4s/%.4s/%.4s/%.4s~SN)" % (
diff1, diff2, diff3, diff4)
if timediff > 1 else "")
logging.user(request, "~FYLoading feed: ~SB%s%s (%s/%s) %s" % (
feed.feed_title[:22], ('~SN/p%s' % page) if page > 1 else '', order, read_filter, time_breakdown))
@ -709,7 +704,7 @@ def load_starred_stories(request):
stories, user_profiles = MSharedStory.stories_with_comments_and_profiles(stories, user.pk, check_all=True)
story_ids = [story['id'] for story in stories]
story_hashes = [story['story_hash'] for story in stories]
story_feed_ids = list(set(s['story_feed_id'] for s in stories))
usersub_ids = UserSubscription.objects.filter(user__pk=user.pk, feed__pk__in=story_feed_ids).values('feed__pk')
usersub_ids = [us['feed__pk'] for us in usersub_ids]
@ -717,9 +712,10 @@ def load_starred_stories(request):
unsub_feeds = Feed.objects.filter(pk__in=unsub_feed_ids)
unsub_feeds = dict((feed.pk, feed.canonical(include_favicon=False)) for feed in unsub_feeds)
shared_stories = MSharedStory.objects(user_id=user.pk,
story_guid__in=story_ids
).only('story_guid', 'shared_date', 'comments')
shared_stories = dict([(story.story_guid, dict(shared_date=story.shared_date, comments=story.comments))
story_hash__in=story_hashes)\
.only('story_hash', 'shared_date', 'comments')
shared_stories = dict([(story.story_hash, dict(shared_date=story.shared_date,
comments=story.comments))
for story in shared_stories])
for story in stories:
@ -736,9 +732,9 @@ def load_starred_stories(request):
'tags': 0,
'title': 0,
}
if story['id'] in shared_stories:
if story['story_hash'] in shared_stories:
story['shared'] = True
story['shared_comments'] = strip_tags(shared_stories[story['id']]['comments'])
story['shared_comments'] = strip_tags(shared_stories[story['story_hash']]['comments'])
logging.user(request, "~FCLoading starred stories: ~SB%s stories" % (len(stories)))
@ -759,8 +755,6 @@ def load_river_stories__redis(request):
order = request.REQUEST.get('order', 'newest')
read_filter = request.REQUEST.get('read_filter', 'unread')
now = localtime_for_timezone(datetime.datetime.now(), user.profile.timezone)
UNREAD_CUTOFF = (datetime.datetime.utcnow() -
datetime.timedelta(days=settings.DAYS_OF_UNREAD))
if not feed_ids:
usersubs = UserSubscription.objects.filter(user=user, active=True)
@ -769,41 +763,25 @@ def load_river_stories__redis(request):
offset = (page-1) * limit
limit = page * limit - 1
story_hashes = UserSubscription.feed_stories(user.pk, feed_ids, offset=offset, limit=limit,
order=order, read_filter=read_filter)
story_hashes, unread_feed_story_hashes = UserSubscription.feed_stories(user.pk, feed_ids,
offset=offset, limit=limit,
order=order,
read_filter=read_filter)
story_date_order = "%sstory_date" % ('' if order == 'oldest' else '-')
mstories = MStory.objects(story_hash__in=story_hashes).order_by(story_date_order)
stories = Feed.format_stories(mstories)
found_feed_ids = list(set([story['story_feed_id'] for story in stories]))
stories, user_profiles = MSharedStory.stories_with_comments_and_profiles(stories, user.pk)
feed_marked_read_dates = None
if read_filter == 'all':
feed_marked_read_dates = dict((us.feed_id, us.mark_read_date)
for us in UserSubscription.objects.filter(user=user,
feed__in=found_feed_ids).only(
'feed', 'mark_read_date'))
# Find starred stories
if found_feed_ids:
if read_filter == 'all':
story_ids = [story['id'] for story in stories]
userstories_db = MUserStory.objects(user_id=user.pk,
feed_id__in=found_feed_ids,
story_id__in=story_ids
).only('story_id').hint([('user_id', 1),
('feed_id', 1),
('story_id', 1)])
userstories = set(us.story_id for us in userstories_db)
else:
userstories = []
starred_stories = MStarredStory.objects(
user_id=user.pk,
story_feed_id__in=found_feed_ids
).only('story_guid', 'starred_date')
starred_stories = dict([(story.story_guid, story.starred_date)
).only('story_hash', 'starred_date')
starred_stories = dict([(story.story_hash, story.starred_date)
for story in starred_stories])
else:
userstories = []
starred_stories = {}
# Intelligence classifiers for all feeds involved
@ -832,18 +810,15 @@ def load_river_stories__redis(request):
for story in stories:
story['read_status'] = 0
if read_filter == 'all':
if story['id'] in userstories:
story['read_status'] = 1
elif story['story_date'] < feed_marked_read_dates[story['story_feed_id']]:
story['read_status'] = 1
elif story['story_date'] < UNREAD_CUTOFF:
if story['story_hash'] not in unread_feed_story_hashes.get(story['story_feed_id'], []):
story['read_status'] = 1
story_date = localtime_for_timezone(story['story_date'], user.profile.timezone)
story['short_parsed_date'] = format_story_link_date__short(story_date, now)
story['long_parsed_date'] = format_story_link_date__long(story_date, now)
if story['id'] in starred_stories:
if story['story_hash'] in starred_stories:
story['starred'] = True
starred_date = localtime_for_timezone(starred_stories[story['id']], user.profile.timezone)
starred_date = localtime_for_timezone(starred_stories[story['story_hash']],
user.profile.timezone)
story['starred_date'] = format_story_link_date__long(starred_date, now)
story['intelligence'] = {
'feed': apply_classifier_feeds(classifier_feeds, story['story_feed_id']),
@ -1042,7 +1017,7 @@ def mark_story_as_unread(request):
story_guid_hash=story.guid_hash)
dirty_count = social_subs and social_subs.count()
dirty_count = ("(%s social_subs)" % dirty_count) if dirty_count else ""
try:
m = MUserStory.objects.get(user_id=request.user.pk, feed_id=feed_id, story_id=story_id)
m.delete()
@ -1051,6 +1026,7 @@ def mark_story_as_unread(request):
logging.user(request, "~SB~FRCouldn't find read story to mark as unread.")
else:
data['code'] = -1
RUserStory.mark_unread(user_id=request.user.pk, story_feed_id=feed_id, story_hash=story.story_hash)
r = redis.Redis(connection_pool=settings.REDIS_POOL)
r.publish(request.user.username, 'feed:%s' % feed_id)
@ -1062,6 +1038,7 @@ def mark_story_as_unread(request):
@ajax_login_required
@json.json_view
def mark_feed_as_read(request):
r = redis.Redis(connection_pool=settings.REDIS_POOL)
feed_ids = request.REQUEST.getlist('feed_id')
multiple = len(feed_ids) > 1
code = 1
@ -1087,6 +1064,7 @@ def mark_feed_as_read(request):
try:
sub.mark_feed_read()
r.publish(request.user.username, 'feed:%s' % feed_id)
except IntegrityError:
code = -1

View file

@ -17,7 +17,8 @@ from django.core.urlresolvers import reverse
from django.template.loader import render_to_string
from django.template.defaultfilters import slugify
from django.core.mail import EmailMultiAlternatives
from apps.reader.models import UserSubscription, MUserStory
from django.core.cache import cache
from apps.reader.models import UserSubscription, MUserStory, RUserStory
from apps.analyzer.models import MClassifierFeed, MClassifierAuthor, MClassifierTag, MClassifierTitle
from apps.analyzer.models import apply_classifier_titles, apply_classifier_feeds, apply_classifier_authors, apply_classifier_tags
from apps.rss_feeds.models import Feed, MStory
@ -852,7 +853,7 @@ class MSocialSubscription(mongo.Document):
}
def get_stories(self, offset=0, limit=6, order='newest', read_filter='all',
withscores=False, everything_unread=False):
withscores=False, hashes_only=False):
r = redis.Redis(connection_pool=settings.REDIS_STORY_HASH_POOL)
ignore_user_stories = False
@ -862,14 +863,15 @@ class MSocialSubscription(mongo.Document):
if not r.exists(stories_key):
return []
elif everything_unread or read_filter != 'unread' or not r.exists(read_stories_key):
elif read_filter != 'unread' or not r.exists(read_stories_key):
ignore_user_stories = True
unread_stories_key = stories_key
else:
r.sdiffstore(unread_stories_key, stories_key, read_stories_key)
sorted_stories_key = 'zB:%s' % (self.subscription_user_id)
unread_ranked_stories_key = 'zUB:%s:%s' % (self.user_id, self.subscription_user_id)
unread_ranked_stories_key = 'z%sUB:%s:%s' % ('h' if hashes_only else '',
self.user_id, self.subscription_user_id)
r.zinterstore(unread_ranked_stories_key, [sorted_stories_key, unread_stories_key])
current_time = int(time.time() + 60*60*24)
@ -896,7 +898,7 @@ class MSocialSubscription(mongo.Document):
return story_ids
@classmethod
def feed_stories(cls, user_id, social_user_ids, offset=0, limit=6, order='newest', read_filter='all', relative_user_id=None, everything_unread=False, cache=True):
def feed_stories(cls, user_id, social_user_ids, offset=0, limit=6, order='newest', read_filter='all', relative_user_id=None, cache=True):
r = redis.Redis(connection_pool=settings.REDIS_STORY_HASH_POOL)
if not relative_user_id:
@ -910,31 +912,41 @@ class MSocialSubscription(mongo.Document):
if not isinstance(social_user_ids, list):
social_user_ids = [social_user_ids]
unread_ranked_stories_keys = 'zU:%s:social' % (user_id)
if offset and r.exists(unread_ranked_stories_keys) and cache:
story_hashes = range_func(unread_ranked_stories_keys, offset, offset+limit, withscores=True)
ranked_stories_keys = 'zU:%s:social' % (user_id)
read_ranked_stories_keys = 'zhU:%s:social' % (user_id)
if (offset and cache and
r.exists(ranked_stories_keys) and
r.exists(read_ranked_stories_keys)):
story_hashes = range_func(ranked_stories_keys, offset, offset+limit, withscores=True)
read_story_hashes = range_func(read_ranked_stories_keys, 0, -1)
if story_hashes:
return zip(*story_hashes)
story_hashes, story_dates = zip(*story_hashes)
return story_hashes, story_dates, read_story_hashes
else:
return [], []
return [], [], []
else:
r.delete(unread_ranked_stories_keys)
r.delete(ranked_stories_keys)
r.delete(read_ranked_stories_keys)
for social_user_id in social_user_ids:
us = cls.objects.get(user_id=relative_user_id, subscription_user_id=social_user_id)
story_hashes = us.get_stories(offset=0, limit=100,
order=order, read_filter=read_filter,
withscores=True, everything_unread=everything_unread)
withscores=True)
if story_hashes:
r.zadd(unread_ranked_stories_keys, **dict(story_hashes))
story_hashes = range_func(unread_ranked_stories_keys, offset, offset+limit, withscores=True)
r.expire(unread_ranked_stories_keys, 24*60*60)
r.zadd(ranked_stories_keys, **dict(story_hashes))
r.zinterstore(read_ranked_stories_keys, [ranked_stories_keys, "RS:%s" % user_id])
story_hashes = range_func(ranked_stories_keys, offset, limit, withscores=True)
read_story_hashes = range_func(read_ranked_stories_keys, offset, limit)
r.expire(ranked_stories_keys, 24*60*60)
r.expire(read_ranked_stories_keys, 24*60*60)
if story_hashes:
return zip(*story_hashes)
story_hashes, story_dates = zip(*story_hashes)
return story_hashes, story_dates, read_story_hashes
else:
return [], []
return [], [], []
def mark_story_ids_as_read(self, story_ids, feed_id=None, mark_all_read=False, request=None):
data = dict(code=0, payload=story_ids)
@ -967,6 +979,7 @@ class MSocialSubscription(mongo.Document):
date = now if now > story.story_date else story.story_date # For handling future stories
feed_id = story.story_feed_id
try:
RUserStory.mark_read(self.user_id, feed_id, story.story_hash)
m, _ = MUserStory.objects.get_or_create(user_id=self.user_id,
feed_id=feed_id,
story_id=story.story_guid,
@ -1029,6 +1042,7 @@ class MSocialSubscription(mongo.Document):
now = datetime.datetime.utcnow()
date = now if now > story.story_date else story.story_date # For handling future stories
try:
RUserStory.mark_read(user_id, story.story_feed_id, story.story_hash)
m, _ = MUserStory.objects.get_or_create(user_id=user_id,
feed_id=story.story_feed_id,
story_id=story.story_guid,
@ -1104,8 +1118,9 @@ class MSocialSubscription(mongo.Document):
else:
self.mark_read_date = date_delta
unread_story_hashes = self.get_stories(read_filter='unread', limit=500, hashes_only=True)
stories_db = MSharedStory.objects(user_id=self.subscription_user_id,
shared_date__gte=date_delta)
story_hash__in=unread_story_hashes)
story_feed_ids = set()
story_ids = []
for s in stories_db:
@ -1115,21 +1130,12 @@ class MSocialSubscription(mongo.Document):
usersubs = UserSubscription.objects.filter(user__pk=self.user_id, feed__pk__in=story_feed_ids)
usersubs_map = dict((sub.feed_id, sub) for sub in usersubs)
# usersubs = UserSubscription.objects.filter(user__pk=user.pk, feed__pk__in=story_feed_ids)
# usersubs_map = dict((sub.feed_id, sub) for sub in usersubs)
read_stories_ids = []
if story_feed_ids:
read_stories = MUserStory.objects(user_id=self.user_id,
feed_id__in=story_feed_ids,
story_id__in=story_ids).only('story_id')
read_stories_ids = list(set(rs.story_id for rs in read_stories))
oldest_unread_story_date = now
unread_stories_db = []
for story in stories_db:
if getattr(story, 'story_guid', None) in read_stories_ids:
if story['story_hash'] not in unread_story_hashes:
continue
feed_id = story.story_feed_id
if usersubs_map.get(feed_id) and story.shared_date < usersubs_map[feed_id].mark_read_date:

View file

@ -21,7 +21,7 @@ from apps.social.tasks import UpdateRecalcForSubscription, EmailFirstShare
from apps.analyzer.models import MClassifierTitle, MClassifierAuthor, MClassifierFeed, MClassifierTag
from apps.analyzer.models import apply_classifier_titles, apply_classifier_feeds, apply_classifier_authors, apply_classifier_tags
from apps.analyzer.models import get_classifiers_for_user, sort_classifiers_by_feed
from apps.reader.models import MUserStory, UserSubscription
from apps.reader.models import UserSubscription
from apps.profile.models import Profile
from utils import json_functions as json
from utils import log as logging
@ -97,53 +97,43 @@ def load_social_stories(request, user_id, username=None):
classifier_titles = classifier_titles + list(MClassifierTitle.objects(user_id=user.pk, feed_id__in=story_feed_ids))
classifier_tags = classifier_tags + list(MClassifierTag.objects(user_id=user.pk, feed_id__in=story_feed_ids))
story_ids = [story['id'] for story in stories]
userstories_db = MUserStory.objects(user_id=user.pk,
feed_id__in=story_feed_ids,
story_id__in=story_ids).only('story_id')
userstories = set(us.story_id for us in userstories_db)
unread_story_hashes = []
if read_filter == 'all' and socialsub:
unread_story_hashes = socialsub.get_stories(read_filter='unread', limit=500)
story_hashes = [story['story_hash'] for story in stories]
starred_stories = MStarredStory.objects(user_id=user.pk,
story_guid__in=story_ids).only('story_guid', 'starred_date')
story_hash__in=story_hashes)\
.only('story_hash', 'starred_date')
shared_stories = MSharedStory.objects(user_id=user.pk,
story_guid__in=story_ids)\
.only('story_guid', 'shared_date', 'comments')
starred_stories = dict([(story.story_guid, story.starred_date) for story in starred_stories])
shared_stories = dict([(story.story_guid, dict(shared_date=story.shared_date, comments=story.comments))
story_hash__in=story_hashes)\
.only('story_hash', 'shared_date', 'comments')
starred_stories = dict([(story.story_hash, story.starred_date) for story in starred_stories])
shared_stories = dict([(story.story_hash, dict(shared_date=story.shared_date,
comments=story.comments))
for story in shared_stories])
for story in stories:
story['social_user_id'] = social_user_id
story_feed_id = story['story_feed_id']
# story_date = localtime_for_timezone(story['story_date'], user.profile.timezone)
shared_date = localtime_for_timezone(story['shared_date'], user.profile.timezone)
story['short_parsed_date'] = format_story_link_date__short(shared_date, now)
story['long_parsed_date'] = format_story_link_date__long(shared_date, now)
if not socialsub:
story['read_status'] = 1
elif story['id'] in userstories:
story['read_status'] = 1
elif story['shared_date'] < date_delta:
story['read_status'] = 1
elif not usersubs_map.get(story_feed_id):
story['read_status'] = 0
elif not story.get('read_status') and story['shared_date'] < usersubs_map[story_feed_id].mark_read_date:
story['read_status'] = 1
elif not story.get('read_status') and story['shared_date'] < date_delta:
story['read_status'] = 1
# elif not story.get('read_status') and socialsub and story['shared_date'] > socialsub.last_read_date:
# story['read_status'] = 0
else:
story['read_status'] = 1
if read_filter == 'unread' and socialsub:
story['read_status'] = 0
elif read_filter == 'all' and socialsub:
story['read_status'] = story['story_hash'] not in unread_story_hashes
if story['id'] in starred_stories:
if story['story_hash'] in starred_stories:
story['starred'] = True
starred_date = localtime_for_timezone(starred_stories[story['id']], user.profile.timezone)
starred_date = localtime_for_timezone(starred_stories[story['story_hash']],
user.profile.timezone)
story['starred_date'] = format_story_link_date__long(starred_date, now)
if story['id'] in shared_stories:
if story['story_hash'] in shared_stories:
story['shared'] = True
story['shared_comments'] = strip_tags(shared_stories[story['id']]['comments'])
story['shared_comments'] = strip_tags(shared_stories[story['story_hash']]['comments'])
story['intelligence'] = {
'feed': apply_classifier_feeds(classifier_feeds, story['story_feed_id'],
@ -188,7 +178,7 @@ def load_river_blurblog(request):
global_feed = request.REQUEST.get('global_feed', None)
now = localtime_for_timezone(datetime.datetime.now(), user.profile.timezone)
UNREAD_CUTOFF = datetime.datetime.utcnow() - datetime.timedelta(days=settings.DAYS_OF_UNREAD)
if global_feed:
global_user = User.objects.get(username='popular')
relative_user_id = global_user.pk
@ -203,11 +193,11 @@ def load_river_blurblog(request):
offset = (page-1) * limit
limit = page * limit - 1
story_hashes, story_dates = MSocialSubscription.feed_stories(user.pk, social_user_ids,
offset=offset, limit=limit,
order=order, read_filter=read_filter,
relative_user_id=relative_user_id,
everything_unread=global_feed)
story_hashes, story_dates, read_feed_story_hashes = MSocialSubscription.feed_stories(
user.pk, social_user_ids,
offset=offset, limit=limit,
order=order, read_filter=read_filter,
relative_user_id=relative_user_id)
mstories = MStory.find_by_story_hashes(story_hashes)
story_hashes_to_dates = dict(zip(story_hashes, story_dates))
def sort_stories_by_hash(a, b):
@ -232,30 +222,23 @@ def load_river_blurblog(request):
unsub_feeds = Feed.objects.filter(pk__in=unsub_feed_ids)
unsub_feeds = [feed.canonical(include_favicon=False) for feed in unsub_feeds]
# Find starred stories
if story_feed_ids:
story_ids = [story['id'] for story in stories]
story_hashes = [story['story_hash'] for story in stories]
starred_stories = MStarredStory.objects(
user_id=user.pk,
story_guid__in=story_ids
).only('story_guid', 'starred_date')
starred_stories = dict([(story.story_guid, story.starred_date)
story_hash__in=story_hashes
).only('story_hash', 'starred_date')
starred_stories = dict([(story.story_hash, story.starred_date)
for story in starred_stories])
shared_stories = MSharedStory.objects(user_id=user.pk,
story_guid__in=story_ids)\
.only('story_guid', 'shared_date', 'comments')
shared_stories = dict([(story.story_guid, dict(shared_date=story.shared_date, comments=story.comments))
for story in shared_stories])
userstories_db = MUserStory.objects(user_id=user.pk,
feed_id__in=story_feed_ids,
story_id__in=story_ids).only('story_id')
userstories = set(us.story_id for us in userstories_db)
story_hash__in=story_hashes)\
.only('story_hash', 'shared_date', 'comments')
shared_stories = dict([(story.story_hash, dict(shared_date=story.shared_date,
comments=story.comments))
for story in shared_stories])
else:
starred_stories = {}
shared_stories = {}
userstories = []
# Intelligence classifiers for all feeds involved
if story_feed_ids:
@ -277,18 +260,15 @@ def load_river_blurblog(request):
# Just need to format stories
for story in stories:
if story['id'] in userstories:
story['read_status'] = 0
if story['story_hash'] in read_feed_story_hashes:
story['read_status'] = 1
elif story['story_date'] < UNREAD_CUTOFF:
story['read_status'] = 1
else:
story['read_status'] = 0
story_date = localtime_for_timezone(story['story_date'], user.profile.timezone)
story['short_parsed_date'] = format_story_link_date__short(story_date, now)
story['long_parsed_date'] = format_story_link_date__long(story_date, now)
if story['id'] in starred_stories:
if story['story_hash'] in starred_stories:
story['starred'] = True
starred_date = localtime_for_timezone(starred_stories[story['id']], user.profile.timezone)
starred_date = localtime_for_timezone(starred_stories[story['story_hash']], user.profile.timezone)
story['starred_date'] = format_story_link_date__long(starred_date, now)
story['intelligence'] = {
'feed': apply_classifier_feeds(classifier_feeds, story['story_feed_id'],
@ -297,12 +277,15 @@ def load_river_blurblog(request):
'tags': apply_classifier_tags(classifier_tags, story),
'title': apply_classifier_titles(classifier_titles, story),
}
if story['id'] in shared_stories:
if story['story_hash'] in shared_stories:
story['shared'] = True
shared_date = localtime_for_timezone(shared_stories[story['id']]['shared_date'],
shared_date = localtime_for_timezone(shared_stories[story['story_hash']]['shared_date'],
user.profile.timezone)
story['shared_date'] = format_story_link_date__long(shared_date, now)
story['shared_comments'] = strip_tags(shared_stories[story['id']]['comments'])
story['shared_comments'] = strip_tags(shared_stories[story['story_hash']]['comments'])
if story['shared_date'] < UNREAD_CUTOFF or story['story_hash'] in read_feed_story_hashes:
story['read_status'] = 1
classifiers = sort_classifiers_by_feed(user=user, feed_ids=story_feed_ids,
classifier_feeds=classifier_feeds,
@ -426,7 +409,7 @@ def load_social_page(request, user_id, username=None, **kwargs):
story['shared_by_user'] = True
shared_story = MSharedStory.objects.get(user_id=user.pk,
story_feed_id=story['story_feed_id'],
story_guid=story['id'])
story_hash=story['story_hash'])
story['user_comments'] = shared_story.comments
stories = MSharedStory.attach_users_to_stories(stories, profiles)
@ -536,10 +519,11 @@ def mark_story_as_shared(request):
shared_story = MSharedStory.objects.filter(user_id=request.user.pk,
story_feed_id=feed_id,
story_guid=story_id).limit(1).first()
story_hash=story['story_hash']).limit(1).first()
if not shared_story:
story_db = {
"story_guid": story.story_guid,
"story_hash": story.story_hash,
"story_permalink": story.story_permalink,
"story_title": story.story_title,
"story_feed_id": story.story_feed_id,
@ -551,7 +535,6 @@ def mark_story_as_shared(request):
"user_id": request.user.pk,
"comments": comments,
"has_comments": bool(comments),
"story_db_id": story.id,
}
shared_story = MSharedStory.objects.create(**story_db)
if source_user_id:
@ -621,7 +604,7 @@ def mark_story_as_unshared(request):
shared_story = MSharedStory.objects(user_id=request.user.pk,
story_feed_id=feed_id,
story_guid=story_id).limit(1).first()
story_hash=story['story_hash']).limit(1).first()
if not shared_story:
return json.json_response(request, {'code': -1, 'message': 'Shared story not found.'})