Merge remote-tracking branch 'upstream/master'
Conflicts: clients/android/NewsBlur/src/com/newsblur/network/APIClient.java clients/android/NewsBlur/src/com/newsblur/network/domain/LoginErrors.java clients/android/NewsBlur/src/com/newsblur/network/domain/LoginResponse.java clients/android/NewsBlur/src/com/newsblur/network/domain/Message.java media/android media/android/NewsBlur/src/com/newsblur/network/domain/LoginErrors.java media/android/NewsBlur/src/com/newsblur/network/domain/ResponseErrors.java
|
@ -4,6 +4,7 @@ import httplib2
|
|||
import pickle
|
||||
import base64
|
||||
from StringIO import StringIO
|
||||
from oauth2client.client import Error as OAuthError
|
||||
from xml.etree.ElementTree import Element, SubElement, Comment, tostring
|
||||
from lxml import etree
|
||||
from django.db import models
|
||||
|
@ -344,7 +345,10 @@ class GoogleReaderImporter(Importer):
|
|||
|
||||
def test(self):
|
||||
sub_url = "%s/0/token" % (self.scope)
|
||||
resp = self.send_request(sub_url)
|
||||
try:
|
||||
resp = self.send_request(sub_url)
|
||||
except OAuthError:
|
||||
return False
|
||||
return resp
|
||||
|
||||
@timelimit(10)
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import datetime
|
||||
import pickle
|
||||
import base64
|
||||
import httplib2
|
||||
from utils import log as logging
|
||||
from oauth2client.client import OAuth2WebServerFlow, FlowExchangeError
|
||||
from bson.errors import InvalidStringData
|
||||
|
@ -146,6 +147,8 @@ def reader_callback(request):
|
|||
)
|
||||
FLOW.redirect_uri = STEP2_URI
|
||||
|
||||
http = httplib2.Http()
|
||||
http.disable_ssl_certificate_validation = True
|
||||
try:
|
||||
credential = FLOW.step2_exchange(request.REQUEST)
|
||||
except FlowExchangeError:
|
||||
|
|
|
@ -3,10 +3,11 @@ import re
|
|||
import random
|
||||
import time
|
||||
from utils import log as logging
|
||||
from django.http import HttpResponse
|
||||
from django.conf import settings
|
||||
from django.db import connection
|
||||
from django.template import Template, Context
|
||||
|
||||
from utils import json_functions as json
|
||||
|
||||
class LastSeenMiddleware(object):
|
||||
def process_response(self, request, response):
|
||||
|
@ -175,3 +176,25 @@ class ServerHostnameMiddleware:
|
|||
class TimingMiddleware:
|
||||
def process_request(self, request):
|
||||
setattr(request, 'start_time', time.time())
|
||||
|
||||
BANNED_USER_AGENTS = (
|
||||
'feed reader-background',
|
||||
'feed reader-windows',
|
||||
'missing',
|
||||
)
|
||||
class UserAgentBanMiddleware:
|
||||
def process_request(self, request):
|
||||
user_agent = request.environ.get('HTTP_USER_AGENT', 'missing').lower()
|
||||
|
||||
if 'profile' in request.path: return
|
||||
|
||||
if any(ua in user_agent for ua in BANNED_USER_AGENTS):
|
||||
data = {
|
||||
'error': 'User agent banned: %s' % user_agent,
|
||||
'code': -1
|
||||
}
|
||||
logging.user(request, "~FB~SN~BBBanned UA: ~SB%s" % (user_agent))
|
||||
|
||||
return HttpResponse(json.encode(data), status=403, mimetype='text/json')
|
||||
|
||||
|
||||
|
|
|
@ -261,7 +261,10 @@ class Profile(models.Model):
|
|||
transaction = PayPalIPN.objects.filter(custom=self.user.username,
|
||||
txn_type='subscr_payment')[0]
|
||||
refund = paypal.refund_transaction(transaction.txn_id)
|
||||
refunded = int(float(refund['raw']['TOTALREFUNDEDAMOUNT'][0]))
|
||||
try:
|
||||
refunded = int(float(refund['raw']['TOTALREFUNDEDAMOUNT'][0]))
|
||||
except KeyError:
|
||||
refunded = int(transaction.amount)
|
||||
logging.user(self.user, "~FRRefunding paypal payment: $%s" % refunded)
|
||||
self.cancel_premium()
|
||||
|
||||
|
|
|
@ -12,11 +12,13 @@ import hashlib
|
|||
from apps.push import signals
|
||||
from apps.rss_feeds.models import Feed
|
||||
from utils import log as logging
|
||||
from utils.feed_functions import timelimit
|
||||
|
||||
DEFAULT_LEASE_SECONDS = 2592000 # 30 days in seconds
|
||||
|
||||
class PushSubscriptionManager(models.Manager):
|
||||
|
||||
|
||||
@timelimit(5)
|
||||
def subscribe(self, topic, feed, hub=None, callback=None,
|
||||
lease_seconds=None, force_retry=False):
|
||||
if hub is None:
|
||||
|
|
|
@ -4,6 +4,7 @@ import redis
|
|||
from utils import log as logging
|
||||
from utils import json_functions as json
|
||||
from django.db import models, IntegrityError
|
||||
from django.db.models import Q
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.models import User
|
||||
from django.core.cache import cache
|
||||
|
@ -12,11 +13,11 @@ from apps.reader.managers import UserSubscriptionManager
|
|||
from apps.rss_feeds.models import Feed, MStory, DuplicateFeed
|
||||
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 utils.feed_functions import add_object_to_folder
|
||||
from utils.feed_functions import add_object_to_folder, chunks
|
||||
|
||||
class UserSubscription(models.Model):
|
||||
"""
|
||||
A feed which a user has subscrubed to. Carries all of the cached information
|
||||
A feed which a user has subscribed to. Carries all of the cached information
|
||||
about the subscription, including unread counts of the three primary scores.
|
||||
|
||||
Also has a dirty flag (needs_unread_recalc) which means that the unread counts
|
||||
|
@ -84,6 +85,85 @@ class UserSubscription(models.Model):
|
|||
break
|
||||
else:
|
||||
self.delete()
|
||||
|
||||
@classmethod
|
||||
def subs_for_feeds(cls, user_id, feed_ids=None, read_filter="unread"):
|
||||
usersubs = cls.objects
|
||||
if read_filter == "unread":
|
||||
usersubs = usersubs.filter(Q(unread_count_neutral__gt=0) |
|
||||
Q(unread_count_positive__gt=0))
|
||||
if not feed_ids:
|
||||
usersubs = usersubs.filter(user=user_id,
|
||||
active=True).only('feed', 'mark_read_date', 'is_trained')
|
||||
else:
|
||||
usersubs = usersubs.filter(user=user_id,
|
||||
active=True,
|
||||
feed__in=feed_ids).only('feed', 'mark_read_date', 'is_trained')
|
||||
|
||||
return usersubs
|
||||
|
||||
@classmethod
|
||||
def story_hashes(cls, user_id, feed_ids=None, usersubs=None, read_filter="unread", order="newest",
|
||||
include_timestamps=False, group_by_feed=True):
|
||||
r = redis.Redis(connection_pool=settings.REDIS_STORY_HASH_POOL)
|
||||
pipeline = r.pipeline()
|
||||
|
||||
if not usersubs:
|
||||
usersubs = cls.subs_for_feeds(user_id, feed_ids=feed_ids, read_filter=read_filter)
|
||||
feed_ids = [sub.feed_id for sub in usersubs]
|
||||
if not feed_ids:
|
||||
return []
|
||||
|
||||
read_dates = dict((us.feed_id, int(us.mark_read_date.strftime('%s'))) for us in usersubs)
|
||||
current_time = int(time.time() + 60*60*24)
|
||||
unread_interval = datetime.datetime.now() - datetime.timedelta(days=settings.DAYS_OF_UNREAD)
|
||||
unread_timestamp = int(time.mktime(unread_interval.timetuple()))-1000
|
||||
story_hashes = {} if group_by_feed else []
|
||||
feed_counter = 0
|
||||
|
||||
for feed_id_group in chunks(feed_ids, 20):
|
||||
pipeline = r.pipeline()
|
||||
for feed_id in feed_id_group:
|
||||
stories_key = 'F:%s' % feed_id
|
||||
sorted_stories_key = 'zF:%s' % feed_id
|
||||
read_stories_key = 'RS:%s:%s' % (user_id, feed_id)
|
||||
unread_stories_key = 'U:%s:%s' % (user_id, feed_id)
|
||||
unread_ranked_stories_key = 'zU:%s:%s' % (user_id, feed_id)
|
||||
expire_unread_stories_key = False
|
||||
|
||||
max_score = current_time
|
||||
if read_filter == 'unread':
|
||||
# +1 for the intersection b/w zF and F, which carries an implicit score of 1.
|
||||
min_score = read_dates[feed_id] + 1
|
||||
pipeline.sdiffstore(unread_stories_key, stories_key, read_stories_key)
|
||||
expire_unread_stories_key = True
|
||||
else:
|
||||
min_score = unread_timestamp
|
||||
unread_stories_key = stories_key
|
||||
|
||||
if order == 'oldest':
|
||||
byscorefunc = pipeline.zrangebyscore
|
||||
else:
|
||||
byscorefunc = pipeline.zrevrangebyscore
|
||||
min_score, max_score = max_score, min_score
|
||||
|
||||
pipeline.zinterstore(unread_ranked_stories_key, [sorted_stories_key, unread_stories_key])
|
||||
byscorefunc(unread_ranked_stories_key, min_score, max_score, withscores=include_timestamps)
|
||||
pipeline.expire(unread_ranked_stories_key, 60*60)
|
||||
if expire_unread_stories_key:
|
||||
pipeline.delete(unread_stories_key)
|
||||
|
||||
results = pipeline.execute()
|
||||
|
||||
for hashes in results:
|
||||
if not isinstance(hashes, list): continue
|
||||
if group_by_feed:
|
||||
story_hashes[feed_ids[feed_counter]] = hashes
|
||||
feed_counter += 1
|
||||
else:
|
||||
story_hashes.extend(hashes)
|
||||
|
||||
return story_hashes
|
||||
|
||||
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)
|
||||
|
@ -157,8 +237,8 @@ class UserSubscription(models.Model):
|
|||
return []
|
||||
|
||||
@classmethod
|
||||
def feed_stories(cls, user_id, feed_ids, offset=0, limit=6, order='newest', read_filter='all',
|
||||
usersubs=None):
|
||||
def feed_stories(cls, user_id, feed_ids=None, offset=0, limit=6,
|
||||
order='newest', read_filter='all', usersubs=None):
|
||||
r = redis.Redis(connection_pool=settings.REDIS_STORY_HASH_POOL)
|
||||
|
||||
if order == 'oldest':
|
||||
|
@ -166,41 +246,46 @@ class UserSubscription(models.Model):
|
|||
else:
|
||||
range_func = r.zrevrange
|
||||
|
||||
if not isinstance(feed_ids, list):
|
||||
feed_ids = [feed_ids]
|
||||
|
||||
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:
|
||||
stories_cached = r.exists(ranked_stories_keys)
|
||||
unreads_cached = True if read_filter == "unread" else r.exists(unread_ranked_stories_keys)
|
||||
if offset and stories_cached and unreads_cached:
|
||||
story_hashes = range_func(ranked_stories_keys, offset, limit)
|
||||
if read_filter == "unread":
|
||||
unread_story_hashes = story_hashes
|
||||
else:
|
||||
unread_story_hashes = range_func(unread_ranked_stories_keys, 0, offset+limit)
|
||||
return story_hashes, unread_story_hashes
|
||||
else:
|
||||
r.delete(ranked_stories_keys)
|
||||
cache.delete(unread_ranked_stories_keys)
|
||||
r.delete(unread_ranked_stories_keys)
|
||||
|
||||
if not usersubs and feed_ids:
|
||||
usersubs = cls.objects.filter(user=user_id, feed__in=feed_ids)
|
||||
if usersubs:
|
||||
usersubs = dict((sub.feed_id, sub) for sub in usersubs)
|
||||
|
||||
unread_feed_story_hashes = {}
|
||||
for feed_id in feed_ids:
|
||||
if feed_id in usersubs:
|
||||
us = usersubs[feed_id]
|
||||
else:
|
||||
continue
|
||||
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(ranked_stories_keys, **dict(story_hashes))
|
||||
|
||||
story_hashes = cls.story_hashes(user_id, feed_ids=feed_ids,
|
||||
read_filter=read_filter, order=order,
|
||||
include_timestamps=True,
|
||||
group_by_feed=False, usersubs=usersubs)
|
||||
if not story_hashes:
|
||||
return [], []
|
||||
|
||||
for story_hash_group in chunks(story_hashes, 100):
|
||||
r.zadd(ranked_stories_keys, **dict(story_hash_group))
|
||||
story_hashes = range_func(ranked_stories_keys, offset, limit)
|
||||
|
||||
if read_filter == "unread":
|
||||
unread_feed_story_hashes = story_hashes
|
||||
else:
|
||||
unread_story_hashes = cls.story_hashes(user_id, feed_ids=feed_ids,
|
||||
read_filter="unread", order=order,
|
||||
include_timestamps=True,
|
||||
group_by_feed=False)
|
||||
if unread_story_hashes:
|
||||
for unread_story_hash_group in chunks(unread_story_hashes, 100):
|
||||
r.zadd(unread_ranked_stories_keys, **dict(unread_story_hash_group))
|
||||
unread_feed_story_hashes = range_func(unread_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)
|
||||
r.expire(unread_ranked_stories_keys, 60*60)
|
||||
|
||||
return story_hashes, unread_feed_story_hashes
|
||||
|
||||
|
@ -553,28 +638,44 @@ class UserSubscription(models.Model):
|
|||
class RUserStory:
|
||||
|
||||
@classmethod
|
||||
def mark_read(cls, user_id, story_feed_id, story_hash, r=None):
|
||||
def mark_read(cls, user_id, story_feed_id, story_hash, r=None, r2=None):
|
||||
if not r:
|
||||
r = redis.Redis(connection_pool=settings.REDIS_STORY_HASH_POOL)
|
||||
if not r2:
|
||||
r2 = redis.Redis(connection_pool=settings.REDIS_STORY_HASH_POOL2)
|
||||
|
||||
story_hash = MStory.ensure_story_hash(story_hash, story_feed_id=story_feed_id)
|
||||
|
||||
if not story_hash: return
|
||||
|
||||
now = int(time.time())
|
||||
all_read_stories_key = 'RS:%s' % (user_id)
|
||||
r.sadd(all_read_stories_key, story_hash)
|
||||
r2.sadd(all_read_stories_key, story_hash)
|
||||
r2.zadd('z' + all_read_stories_key, story_hash, now)
|
||||
r.expire(all_read_stories_key, settings.DAYS_OF_UNREAD*24*60*60)
|
||||
r2.expire(all_read_stories_key, settings.DAYS_OF_UNREAD*24*60*60)
|
||||
r2.expire('z' + 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)
|
||||
r2.sadd(read_story_key, story_hash)
|
||||
r2.zadd('z' + read_story_key, story_hash, now)
|
||||
r.expire(read_story_key, settings.DAYS_OF_UNREAD*24*60*60)
|
||||
r2.expire(read_story_key, settings.DAYS_OF_UNREAD*24*60*60)
|
||||
r2.expire('z' + 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)
|
||||
r2 = redis.Redis(connection_pool=settings.REDIS_STORY_HASH_POOL2)
|
||||
|
||||
r.srem('RS:%s' % user_id, story_hash)
|
||||
r2.srem('RS:%s' % user_id, story_hash)
|
||||
r2.zrem('zRS:%s' % user_id, story_hash)
|
||||
r.srem('RS:%s:%s' % (user_id, story_feed_id), story_hash)
|
||||
r2.srem('RS:%s:%s' % (user_id, story_feed_id), story_hash)
|
||||
r2.zrem('zRS:%s:%s' % (user_id, story_feed_id), story_hash)
|
||||
|
||||
@staticmethod
|
||||
def get_stories(user_id, feed_id, r=None):
|
||||
|
@ -586,15 +687,33 @@ class RUserStory:
|
|||
@classmethod
|
||||
def switch_feed(cls, user_id, old_feed_id, new_feed_id):
|
||||
r = redis.Redis(connection_pool=settings.REDIS_STORY_HASH_POOL)
|
||||
r2 = redis.Redis(connection_pool=settings.REDIS_STORY_HASH_POOL2)
|
||||
p = r.pipeline()
|
||||
p2 = r2.pipeline()
|
||||
story_hashes = cls.get_stories(user_id, old_feed_id, r=r)
|
||||
|
||||
now = int(time.time())
|
||||
|
||||
for story_hash in story_hashes:
|
||||
_, hash_story = MStory.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)
|
||||
read_feed_key = "RS:%s:%s" % (user_id, new_feed_id)
|
||||
p.sadd(read_feed_key, new_story_hash)
|
||||
p2.sadd(read_feed_key, new_story_hash)
|
||||
p2.zadd('z' + read_feed_key, new_story_hash, now)
|
||||
p.expire(read_feed_key, settings.DAYS_OF_UNREAD*24*60*60)
|
||||
p2.expire(read_feed_key, settings.DAYS_OF_UNREAD*24*60*60)
|
||||
p2.expire('z' + read_feed_key, settings.DAYS_OF_UNREAD*24*60*60)
|
||||
|
||||
read_user_key = "RS:%s" % (user_id)
|
||||
p.sadd(read_user_key, new_story_hash)
|
||||
p2.sadd(read_user_key, new_story_hash)
|
||||
p2.zadd('z' + read_user_key, new_story_hash, now)
|
||||
p.expire(read_user_key, settings.DAYS_OF_UNREAD*24*60*60)
|
||||
p2.expire(read_user_key, settings.DAYS_OF_UNREAD*24*60*60)
|
||||
p2.expire('z' + read_user_key, settings.DAYS_OF_UNREAD*24*60*60)
|
||||
|
||||
p.execute()
|
||||
p2.execute()
|
||||
|
||||
if len(story_hashes) > 0:
|
||||
logging.info(" ---> %s read stories" % len(story_hashes))
|
||||
|
@ -602,9 +721,12 @@ class RUserStory:
|
|||
@classmethod
|
||||
def switch_hash(cls, feed_id, old_hash, new_hash):
|
||||
r = redis.Redis(connection_pool=settings.REDIS_STORY_HASH_POOL)
|
||||
r2 = redis.Redis(connection_pool=settings.REDIS_STORY_HASH_POOL2)
|
||||
p = r.pipeline()
|
||||
p2 = r2.pipeline()
|
||||
UNREAD_CUTOFF = datetime.datetime.now() - datetime.timedelta(days=settings.DAYS_OF_UNREAD)
|
||||
|
||||
now = int(time.time())
|
||||
|
||||
usersubs = UserSubscription.objects.filter(feed_id=feed_id, last_read_date__gte=UNREAD_CUTOFF)
|
||||
logging.info(" ---> ~SB%s usersubs~SN to switch read story hashes..." % len(usersubs))
|
||||
for sub in usersubs:
|
||||
|
@ -612,10 +734,23 @@ class RUserStory:
|
|||
read = r.sismember(rs_key, old_hash)
|
||||
if read:
|
||||
p.sadd(rs_key, new_hash)
|
||||
p.sadd("RS:%s" % sub.user.pk, new_hash)
|
||||
p2.sadd(rs_key, new_hash)
|
||||
p2.zadd('z' + rs_key, new_hash, now)
|
||||
p.expire(rs_key, settings.DAYS_OF_UNREAD*24*60*60)
|
||||
p2.expire(rs_key, settings.DAYS_OF_UNREAD*24*60*60)
|
||||
p2.expire('z' + rs_key, settings.DAYS_OF_UNREAD*24*60*60)
|
||||
|
||||
read_user_key = "RS:%s" % sub.user.pk
|
||||
p.sadd(read_user_key, new_hash)
|
||||
p2.sadd(read_user_key, new_hash)
|
||||
p2.zadd('z' + read_user_key, new_hash, now)
|
||||
p.expire(read_user_key, settings.DAYS_OF_UNREAD*24*60*60)
|
||||
p2.expire(read_user_key, settings.DAYS_OF_UNREAD*24*60*60)
|
||||
p2.expire('z' + read_user_key, settings.DAYS_OF_UNREAD*24*60*60)
|
||||
|
||||
p.execute()
|
||||
|
||||
p2.execute()
|
||||
|
||||
|
||||
class UserSubscriptionFolders(models.Model):
|
||||
"""
|
||||
|
|
|
@ -8,6 +8,7 @@ from django.shortcuts import render
|
|||
from django.contrib.auth.decorators import login_required
|
||||
from django.template.loader import render_to_string
|
||||
from django.db import IntegrityError
|
||||
from django.db.models import Q
|
||||
from django.views.decorators.cache import never_cache
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.contrib.auth import login as login_user
|
||||
|
@ -55,7 +56,7 @@ from vendor.timezones.utilities import localtime_for_timezone
|
|||
@never_cache
|
||||
@render_to('reader/dashboard.xhtml')
|
||||
def index(request, **kwargs):
|
||||
if request.method == "GET" and request.subdomain and request.subdomain not in ['dev', 'app10', 'www', 'debug']:
|
||||
if request.method == "GET" and request.subdomain and request.subdomain not in ['dev', 'www', 'debug']:
|
||||
username = request.subdomain
|
||||
try:
|
||||
if '.' in username:
|
||||
|
@ -104,6 +105,7 @@ def dashboard(request, **kwargs):
|
|||
'statistics' : statistics,
|
||||
'social_profile' : social_profile,
|
||||
'start_import_from_google_reader': start_import_from_google_reader,
|
||||
'debug' : settings.DEBUG,
|
||||
}, "reader/dashboard.xhtml"
|
||||
|
||||
def welcome(request, **kwargs):
|
||||
|
@ -233,7 +235,7 @@ def load_feeds(request):
|
|||
scheduled_feeds = []
|
||||
for sub in user_subs:
|
||||
pk = sub.feed_id
|
||||
if update_counts:
|
||||
if update_counts and sub.needs_unread_recalc:
|
||||
sub.calculate_feed_scores(silent=True)
|
||||
feeds[pk] = sub.canonical(include_favicon=include_favicons)
|
||||
|
||||
|
@ -653,7 +655,7 @@ def load_single_feed(request, feed_id):
|
|||
|
||||
# if page <= 1:
|
||||
# import random
|
||||
# time.sleep(random.randint(0, 6))
|
||||
# time.sleep(random.randint(0, 1))
|
||||
|
||||
return data
|
||||
|
||||
|
@ -784,33 +786,45 @@ 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)
|
||||
|
||||
if not feed_ids and not story_hashes:
|
||||
usersubs = UserSubscription.objects.filter(user=user, active=True)
|
||||
feed_ids = [sub.feed_id for sub in usersubs]
|
||||
else:
|
||||
usersubs = UserSubscription.objects.filter(user=user, active=True, feed__in=feed_ids)
|
||||
|
||||
usersubs = []
|
||||
offset = (page-1) * limit
|
||||
limit = page * limit - 1
|
||||
story_date_order = "%sstory_date" % ('' if order == 'oldest' else '-')
|
||||
|
||||
if story_hashes:
|
||||
unread_feed_story_hashes = []
|
||||
unread_feed_story_hashes = None
|
||||
read_filter = 'unread'
|
||||
else:
|
||||
story_hashes, unread_feed_story_hashes = UserSubscription.feed_stories(user.pk, feed_ids,
|
||||
offset=offset, limit=limit,
|
||||
order=order,
|
||||
read_filter=read_filter,
|
||||
usersubs=usersubs)
|
||||
usersubs = UserSubscription.subs_for_feeds(user.pk, feed_ids=feed_ids,
|
||||
read_filter=read_filter)
|
||||
feed_ids = [sub.feed_id for sub in usersubs]
|
||||
if feed_ids:
|
||||
params = {
|
||||
"user_id": user.pk,
|
||||
"feed_ids": feed_ids,
|
||||
"offset": offset,
|
||||
"limit": limit,
|
||||
"order": order,
|
||||
"read_filter": read_filter,
|
||||
"usersubs": usersubs,
|
||||
}
|
||||
story_hashes, unread_feed_story_hashes = UserSubscription.feed_stories(**params)
|
||||
else:
|
||||
story_hashes = []
|
||||
unread_feed_story_hashes = []
|
||||
|
||||
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)
|
||||
|
||||
if not usersubs:
|
||||
usersubs = UserSubscription.subs_for_feeds(user.pk, feed_ids=found_feed_ids,
|
||||
read_filter=read_filter)
|
||||
|
||||
trained_feed_ids = [sub.feed_id for sub in usersubs if sub.is_trained]
|
||||
found_trained_feed_ids = list(set(trained_feed_ids) & set(found_feed_ids))
|
||||
|
||||
|
||||
# Find starred stories
|
||||
if found_feed_ids:
|
||||
starred_stories = MStarredStory.objects(
|
||||
|
@ -825,13 +839,13 @@ def load_river_stories__redis(request):
|
|||
# Intelligence classifiers for all feeds involved
|
||||
if found_trained_feed_ids:
|
||||
classifier_feeds = list(MClassifierFeed.objects(user_id=user.pk,
|
||||
feed_id__in=found_trained_feed_ids))
|
||||
feed_id__in=found_trained_feed_ids))
|
||||
classifier_authors = list(MClassifierAuthor.objects(user_id=user.pk,
|
||||
feed_id__in=found_trained_feed_ids))
|
||||
feed_id__in=found_trained_feed_ids))
|
||||
classifier_titles = list(MClassifierTitle.objects(user_id=user.pk,
|
||||
feed_id__in=found_trained_feed_ids))
|
||||
feed_id__in=found_trained_feed_ids))
|
||||
classifier_tags = list(MClassifierTag.objects(user_id=user.pk,
|
||||
feed_id__in=found_trained_feed_ids))
|
||||
feed_id__in=found_trained_feed_ids))
|
||||
else:
|
||||
classifier_feeds = []
|
||||
classifier_authors = []
|
||||
|
@ -848,7 +862,8 @@ def load_river_stories__redis(request):
|
|||
for story in stories:
|
||||
story['read_status'] = 0
|
||||
if read_filter == 'all':
|
||||
if story['story_hash'] not in unread_feed_story_hashes.get(story['story_feed_id'], []):
|
||||
if (unread_feed_story_hashes is not None and
|
||||
story['story_hash'] not in unread_feed_story_hashes):
|
||||
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)
|
||||
|
@ -882,21 +897,30 @@ def load_river_stories__redis(request):
|
|||
|
||||
|
||||
@json.json_view
|
||||
def unread_story_hashes(request):
|
||||
def unread_story_hashes__old(request):
|
||||
user = get_user(request)
|
||||
feed_ids = [int(feed_id) for feed_id in request.REQUEST.getlist('feed_id') if feed_id]
|
||||
include_timestamps = is_true(request.REQUEST.get('include_timestamps', False))
|
||||
usersubs = {}
|
||||
|
||||
if not feed_ids:
|
||||
usersubs = UserSubscription.objects.filter(user=user, active=True).only('feed')
|
||||
usersubs = UserSubscription.objects.filter(Q(unread_count_neutral__gt=0) |
|
||||
Q(unread_count_positive__gt=0),
|
||||
user=user, active=True)
|
||||
feed_ids = [sub.feed_id for sub in usersubs]
|
||||
else:
|
||||
usersubs = UserSubscription.objects.filter(Q(unread_count_neutral__gt=0) |
|
||||
Q(unread_count_positive__gt=0),
|
||||
user=user, active=True, feed__in=feed_ids)
|
||||
|
||||
unread_feed_story_hashes = {}
|
||||
story_hash_count = 0
|
||||
|
||||
usersubs = dict((sub.feed_id, sub) for sub in usersubs)
|
||||
for feed_id in feed_ids:
|
||||
try:
|
||||
us = UserSubscription.objects.get(user=user.pk, feed=feed_id)
|
||||
except UserSubscription.DoesNotExist:
|
||||
if feed_id in usersubs:
|
||||
us = usersubs[feed_id]
|
||||
else:
|
||||
continue
|
||||
if not us.unread_count_neutral and not us.unread_count_positive:
|
||||
continue
|
||||
|
@ -906,10 +930,27 @@ def unread_story_hashes(request):
|
|||
story_hash_count += len(unread_feed_story_hashes[feed_id])
|
||||
|
||||
logging.user(request, "~FYLoading ~FCunread story hashes~FY: ~SB%s feeds~SN (%s story hashes)" %
|
||||
(len(feed_ids), story_hash_count))
|
||||
(len(feed_ids), len(story_hash_count)))
|
||||
|
||||
return dict(unread_feed_story_hashes=unread_feed_story_hashes)
|
||||
|
||||
@json.json_view
|
||||
def unread_story_hashes(request):
|
||||
user = get_user(request)
|
||||
feed_ids = [int(feed_id) for feed_id in request.REQUEST.getlist('feed_id') if feed_id]
|
||||
include_timestamps = is_true(request.REQUEST.get('include_timestamps', False))
|
||||
order = request.REQUEST.get('order', 'newest')
|
||||
read_filter = request.REQUEST.get('read_filter', 'unread')
|
||||
|
||||
story_hashes = UserSubscription.story_hashes(user.pk, feed_ids=feed_ids,
|
||||
order=order, read_filter=read_filter,
|
||||
include_timestamps=include_timestamps)
|
||||
|
||||
logging.user(request, "~FYLoading ~FCunread story hashes~FY: ~SB%s feeds~SN (%s story hashes)" %
|
||||
(len(feed_ids), len(story_hashes)))
|
||||
|
||||
return dict(unread_feed_story_hashes=story_hashes)
|
||||
|
||||
@ajax_login_required
|
||||
@json.json_view
|
||||
def mark_all_as_read(request):
|
||||
|
@ -940,7 +981,11 @@ def mark_all_as_read(request):
|
|||
@json.json_view
|
||||
def mark_story_as_read(request):
|
||||
story_ids = request.REQUEST.getlist('story_id')
|
||||
feed_id = int(get_argument_or_404(request, 'feed_id'))
|
||||
try:
|
||||
feed_id = int(get_argument_or_404(request, 'feed_id'))
|
||||
except ValueError:
|
||||
return dict(code=-1, errors=["You must pass a valid feed_id: %s" %
|
||||
request.REQUEST.get('feed_id')])
|
||||
|
||||
try:
|
||||
usersub = UserSubscription.objects.select_related('feed').get(user=request.user, feed=feed_id)
|
||||
|
@ -1064,6 +1109,10 @@ def mark_story_as_unread(request):
|
|||
|
||||
story, found_original = MStory.find_story(feed_id, story_id)
|
||||
|
||||
if not story:
|
||||
logging.user(request, "~FY~SBUnread~SN story in feed: %s (NOT FOUND)" % (feed))
|
||||
return dict(code=-1, message="Story not found.")
|
||||
|
||||
if usersub and story.story_date < usersub.mark_read_date:
|
||||
# Story is outside the mark as read range, so invert all stories before.
|
||||
newer_stories = MStory.objects(story_feed_id=story.story_feed_id,
|
||||
|
|
|
@ -230,12 +230,16 @@ class Feed(models.Model):
|
|||
def sync_redis(self):
|
||||
return MStory.sync_feed_redis(self.pk)
|
||||
|
||||
def expire_redis(self, r=None):
|
||||
def expire_redis(self, r=None, r2=None):
|
||||
if not r:
|
||||
r = redis.Redis(connection_pool=settings.REDIS_STORY_HASH_POOL)
|
||||
if not r2:
|
||||
r2 = redis.Redis(connection_pool=settings.REDIS_STORY_HASH_POOL2)
|
||||
|
||||
r.expire('F:%s' % self.pk, settings.DAYS_OF_UNREAD*24*60*60)
|
||||
r2.expire('F:%s' % self.pk, settings.DAYS_OF_UNREAD*24*60*60)
|
||||
r.expire('zF:%s' % self.pk, settings.DAYS_OF_UNREAD*24*60*60)
|
||||
r2.expire('zF:%s' % self.pk, settings.DAYS_OF_UNREAD*24*60*60)
|
||||
|
||||
@classmethod
|
||||
def autocomplete(self, prefix, limit=5):
|
||||
|
@ -325,7 +329,7 @@ class Feed(models.Model):
|
|||
create_okay = False
|
||||
if feedfinder.isFeed(url):
|
||||
create_okay = True
|
||||
elif aggressive:
|
||||
elif fetch:
|
||||
# Could still be a feed. Just check if there are entries
|
||||
fp = feedparser.parse(url)
|
||||
if len(fp.entries):
|
||||
|
@ -903,7 +907,7 @@ class Feed(models.Model):
|
|||
story_guid = story.get('guid'),
|
||||
story_tags = story_tags
|
||||
)
|
||||
s.extract_image_url()
|
||||
s.extract_image_urls()
|
||||
try:
|
||||
s.save()
|
||||
ret_values['new'] += 1
|
||||
|
@ -964,7 +968,7 @@ class Feed(models.Model):
|
|||
# Do not allow publishers to change the story date once a story is published.
|
||||
# Leads to incorrect unread story counts.
|
||||
# existing_story.story_date = story.get('published') # No, don't
|
||||
existing_story.extract_image_url()
|
||||
existing_story.extract_image_urls()
|
||||
|
||||
try:
|
||||
existing_story.save()
|
||||
|
@ -1151,7 +1155,7 @@ class Feed(models.Model):
|
|||
story['story_title'] = story_db.story_title
|
||||
story['story_content'] = story_content
|
||||
story['story_permalink'] = story_db.story_permalink
|
||||
story['image_url'] = story_db.image_url
|
||||
story['image_urls'] = story_db.image_urls
|
||||
story['story_feed_id'] = feed_id or story_db.story_feed_id
|
||||
story['comment_count'] = story_db.comment_count if hasattr(story_db, 'comment_count') else 0
|
||||
story['comment_user_ids'] = story_db.comment_user_ids if hasattr(story_db, 'comment_user_ids') else []
|
||||
|
@ -1557,7 +1561,7 @@ class MStory(mongo.Document):
|
|||
story_permalink = mongo.StringField()
|
||||
story_guid = mongo.StringField()
|
||||
story_hash = mongo.StringField()
|
||||
image_url = mongo.StringField(max_length=1024)
|
||||
image_urls = mongo.ListField(mongo.StringField(max_length=1024))
|
||||
story_tags = mongo.ListField(mongo.StringField(max_length=250))
|
||||
comment_count = mongo.IntField()
|
||||
comment_user_ids = mongo.ListField(mongo.IntField())
|
||||
|
@ -1742,36 +1746,55 @@ class MStory(mongo.Document):
|
|||
|
||||
return story_hashes
|
||||
|
||||
def sync_redis(self, r=None):
|
||||
def sync_redis(self, r=None, r2=None):
|
||||
if not r:
|
||||
r = redis.Redis(connection_pool=settings.REDIS_STORY_HASH_POOL)
|
||||
if not r2:
|
||||
r2 = redis.Redis(connection_pool=settings.REDIS_STORY_HASH_POOL2)
|
||||
UNREAD_CUTOFF = datetime.datetime.now() - datetime.timedelta(days=settings.DAYS_OF_UNREAD)
|
||||
|
||||
if self.id and self.story_date > UNREAD_CUTOFF:
|
||||
r.sadd('F:%s' % self.story_feed_id, self.story_hash)
|
||||
r.zadd('zF:%s' % self.story_feed_id, self.story_hash, time.mktime(self.story_date.timetuple()))
|
||||
feed_key = 'F:%s' % self.story_feed_id
|
||||
r.sadd(feed_key, self.story_hash)
|
||||
r.expire(feed_key, settings.DAYS_OF_UNREAD*24*60*60)
|
||||
r2.sadd(feed_key, self.story_hash)
|
||||
r2.expire(feed_key, settings.DAYS_OF_UNREAD*24*60*60)
|
||||
|
||||
r.zadd('z' + feed_key, self.story_hash, time.mktime(self.story_date.timetuple()))
|
||||
r.expire('z' + feed_key, settings.DAYS_OF_UNREAD*24*60*60)
|
||||
r2.zadd('z' + feed_key, self.story_hash, time.mktime(self.story_date.timetuple()))
|
||||
r2.expire('z' + feed_key, settings.DAYS_OF_UNREAD*24*60*60)
|
||||
|
||||
def remove_from_redis(self, r=None):
|
||||
def remove_from_redis(self, r=None, r2=None):
|
||||
if not r:
|
||||
r = redis.Redis(connection_pool=settings.REDIS_STORY_HASH_POOL)
|
||||
if not r2:
|
||||
r2 = redis.Redis(connection_pool=settings.REDIS_STORY_HASH_POOL2)
|
||||
if self.id:
|
||||
r.srem('F:%s' % self.story_feed_id, self.story_hash)
|
||||
r2.srem('F:%s' % self.story_feed_id, self.story_hash)
|
||||
r.zrem('zF:%s' % self.story_feed_id, self.story_hash)
|
||||
r2.zrem('zF:%s' % self.story_feed_id, self.story_hash)
|
||||
|
||||
@classmethod
|
||||
def sync_feed_redis(cls, story_feed_id):
|
||||
r = redis.Redis(connection_pool=settings.REDIS_STORY_HASH_POOL)
|
||||
r2 = redis.Redis(connection_pool=settings.REDIS_STORY_HASH_POOL2)
|
||||
UNREAD_CUTOFF = datetime.datetime.now() - datetime.timedelta(days=settings.DAYS_OF_UNREAD)
|
||||
feed = Feed.get_by_id(story_feed_id)
|
||||
stories = cls.objects.filter(story_feed_id=story_feed_id, story_date__gte=UNREAD_CUTOFF)
|
||||
r.delete('F:%s' % story_feed_id)
|
||||
r2.delete('F:%s' % story_feed_id)
|
||||
r.delete('zF:%s' % story_feed_id)
|
||||
r2.delete('zF:%s' % story_feed_id)
|
||||
|
||||
logging.info(" ---> [%-30s] ~FMSyncing ~SB%s~SN stories to redis" % (feed and feed.title[:30] or story_feed_id, stories.count()))
|
||||
p = r.pipeline()
|
||||
p2 = r2.pipeline()
|
||||
for story in stories:
|
||||
story.sync_redis(r=p)
|
||||
story.sync_redis(r=p, r2=p2)
|
||||
p.execute()
|
||||
p2.execute()
|
||||
|
||||
def count_comments(self):
|
||||
from apps.social.models import MSharedStory
|
||||
|
@ -1787,9 +1810,9 @@ class MStory(mongo.Document):
|
|||
self.share_user_ids = [s['user_id'] for s in shares]
|
||||
self.save()
|
||||
|
||||
def extract_image_url(self, force=False):
|
||||
if self.image_url and not force:
|
||||
return self.image_url
|
||||
def extract_image_urls(self, force=False):
|
||||
if self.image_urls and not force:
|
||||
return self.image_urls
|
||||
|
||||
story_content = self.story_content
|
||||
if not story_content and self.story_content_z:
|
||||
|
@ -1798,13 +1821,24 @@ class MStory(mongo.Document):
|
|||
return
|
||||
|
||||
soup = BeautifulSoup(story_content)
|
||||
image = soup.find('img')
|
||||
if image:
|
||||
images = soup.findAll('img')
|
||||
if not images:
|
||||
return
|
||||
|
||||
image_urls = []
|
||||
for image in images:
|
||||
image_url = image.get('src')
|
||||
if not image_url:
|
||||
continue
|
||||
if image_url and len(image_url) >= 1024:
|
||||
return
|
||||
self.image_url = image_url
|
||||
return self.image_url
|
||||
continue
|
||||
image_urls.append(image_url)
|
||||
|
||||
if not image_urls:
|
||||
return
|
||||
|
||||
self.image_urls = image_urls
|
||||
return self.image_urls
|
||||
|
||||
def fetch_original_text(self, force=False, request=None):
|
||||
original_text_z = self.original_text_z
|
||||
|
@ -1838,7 +1872,7 @@ class MStarredStory(mongo.Document):
|
|||
story_guid = mongo.StringField()
|
||||
story_hash = mongo.StringField()
|
||||
story_tags = mongo.ListField(mongo.StringField(max_length=250))
|
||||
image_url = mongo.StringField(max_length=1024)
|
||||
image_urls = mongo.ListField(mongo.StringField(max_length=1024))
|
||||
|
||||
meta = {
|
||||
'collection': 'starred_stories',
|
||||
|
@ -1945,7 +1979,7 @@ class MFetchHistory(mongo.Document):
|
|||
elif fetch_type == 'push':
|
||||
history = fetch_history.push_history or []
|
||||
|
||||
history.insert(0, (date, code, message))
|
||||
history = [[date, code, message]] + history
|
||||
if code and code >= 400:
|
||||
history = history[:50]
|
||||
else:
|
||||
|
@ -1960,44 +1994,11 @@ class MFetchHistory(mongo.Document):
|
|||
|
||||
fetch_history.save()
|
||||
|
||||
if exception:
|
||||
MFetchExceptionHistory.add(feed_id, date=date, code=code,
|
||||
message=message, exception=exception)
|
||||
if fetch_type == 'feed':
|
||||
RStats.add('feed_fetch')
|
||||
|
||||
return cls.feed(feed_id, fetch_history=fetch_history)
|
||||
|
||||
class MFetchExceptionHistory(mongo.Document):
|
||||
feed_id = mongo.IntField(unique=True)
|
||||
date = mongo.DateTimeField()
|
||||
code = mongo.IntField()
|
||||
message = mongo.StringField()
|
||||
exception = mongo.StringField()
|
||||
|
||||
meta = {
|
||||
'collection': 'fetch_exception_history',
|
||||
'allow_inheritance': False,
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def add(cls, feed_id, date=None, code=None, message="", exception=""):
|
||||
if not date:
|
||||
date = datetime.datetime.now()
|
||||
if not isinstance(exception, basestring):
|
||||
exception = unicode(exception)
|
||||
|
||||
try:
|
||||
fetch_exception = cls.objects.read_preference(pymongo.ReadPreference.PRIMARY)\
|
||||
.get(feed_id=feed_id)
|
||||
except cls.DoesNotExist:
|
||||
fetch_exception = cls.objects.create(feed_id=feed_id)
|
||||
fetch_exception.date = date
|
||||
fetch_exception.code = code
|
||||
fetch_exception.message = message
|
||||
fetch_exception.exception = exception
|
||||
fetch_exception.save()
|
||||
|
||||
|
||||
class DuplicateFeed(models.Model):
|
||||
duplicate_address = models.CharField(max_length=764, db_index=True)
|
||||
|
|
|
@ -35,13 +35,13 @@ class TextImporter:
|
|||
if not skip_save:
|
||||
self.story.original_text_z = zlib.compress(content)
|
||||
self.story.save()
|
||||
logging.user(self.request, "~SN~FYFetched ~FGoriginal text~FY: now ~SB%s bytes~SN vs. was ~SB%s bytes" % (
|
||||
logging.user(self.request, ("~SN~FYFetched ~FGoriginal text~FY: now ~SB%s bytes~SN vs. was ~SB%s bytes" % (
|
||||
len(unicode(content)),
|
||||
self.story.story_content_z and len(zlib.decompress(self.story.story_content_z))
|
||||
))
|
||||
)), warn_color=False)
|
||||
else:
|
||||
logging.user(self.request, "~SN~FRFailed~FY to fetch ~FGoriginal text~FY: was ~SB%s bytes" % (
|
||||
logging.user(self.request, ("~SN~FRFailed~FY to fetch ~FGoriginal text~FY: was ~SB%s bytes" % (
|
||||
self.story.story_content_z and len(zlib.decompress(self.story.story_content_z))
|
||||
))
|
||||
)), warn_color=False)
|
||||
|
||||
return content
|
|
@ -11,6 +11,7 @@ from django.template import RequestContext
|
|||
from apps.rss_feeds.models import Feed, merge_feeds
|
||||
from apps.rss_feeds.models import MFetchHistory
|
||||
from apps.rss_feeds.models import MFeedIcon
|
||||
from apps.push.models import PushSubscription
|
||||
from apps.analyzer.models import get_classifiers_for_user
|
||||
from apps.reader.models import UserSubscription
|
||||
from apps.rss_feeds.models import MStory
|
||||
|
@ -20,7 +21,7 @@ from utils.feed_functions import relative_timeuntil, relative_timesince
|
|||
from utils.user_functions import get_user
|
||||
from utils.view_functions import get_argument_or_404
|
||||
from utils.view_functions import required_params
|
||||
|
||||
from vendor.timezones.utilities import localtime_for_timezone
|
||||
|
||||
@json.json_view
|
||||
def search_feed(request):
|
||||
|
@ -128,6 +129,7 @@ def feed_autocomplete(request):
|
|||
@json.json_view
|
||||
def load_feed_statistics(request, feed_id):
|
||||
user = get_user(request)
|
||||
timezone = user.profile.timezone
|
||||
stats = dict()
|
||||
feed = get_object_or_404(Feed, pk=feed_id)
|
||||
feed.update_all_statistics()
|
||||
|
@ -140,6 +142,14 @@ def load_feed_statistics(request, feed_id):
|
|||
stats['last_update'] = relative_timesince(feed.last_update)
|
||||
stats['next_update'] = relative_timeuntil(feed.next_scheduled_update)
|
||||
stats['push'] = feed.is_push
|
||||
if feed.is_push:
|
||||
try:
|
||||
stats['push_expires'] = localtime_for_timezone(feed.push.lease_expires,
|
||||
timezone).strftime("%Y-%m-%d %H:%M:%S")
|
||||
except PushSubscription.DoesNotExist:
|
||||
stats['push_expires'] = 'Missing push'
|
||||
feed.is_push = False
|
||||
feed.save()
|
||||
|
||||
# Minutes between updates
|
||||
update_interval_minutes = feed.get_next_scheduled_update(force=True, verbose=False)
|
||||
|
@ -172,7 +182,6 @@ def load_feed_statistics(request, feed_id):
|
|||
stats['classifier_counts'] = json.decode(feed.data.feed_classifier_counts)
|
||||
|
||||
# Fetch histories
|
||||
timezone = user.profile.timezone
|
||||
fetch_history = MFetchHistory.feed(feed_id, timezone=timezone)
|
||||
stats['feed_fetch_history'] = fetch_history['feed_fetch_history']
|
||||
stats['page_fetch_history'] = fetch_history['page_fetch_history']
|
||||
|
|
|
@ -1228,7 +1228,7 @@ class MSharedStory(mongo.Document):
|
|||
story_permalink = mongo.StringField()
|
||||
story_guid = mongo.StringField(unique_with=('user_id',))
|
||||
story_guid_hash = mongo.StringField(max_length=6)
|
||||
image_url = mongo.StringField(max_length=1024)
|
||||
image_urls = mongo.ListField(mongo.StringField(max_length=1024))
|
||||
story_tags = mongo.ListField(mongo.StringField(max_length=250))
|
||||
posted_to_services = mongo.ListField(mongo.StringField(max_length=20))
|
||||
mute_email_users = mongo.ListField(mongo.IntField())
|
||||
|
@ -1522,6 +1522,7 @@ class MSharedStory(mongo.Document):
|
|||
def sync_all_redis(cls, drop=False):
|
||||
r = redis.Redis(connection_pool=settings.REDIS_POOL)
|
||||
h = redis.Redis(connection_pool=settings.REDIS_STORY_HASH_POOL)
|
||||
h2 = redis.Redis(connection_pool=settings.REDIS_STORY_HASH_POOL2)
|
||||
if drop:
|
||||
for key_name in ["C", "S"]:
|
||||
keys = r.keys("%s:*" % key_name)
|
||||
|
@ -1530,7 +1531,7 @@ class MSharedStory(mongo.Document):
|
|||
r.delete(key)
|
||||
for story in cls.objects.all():
|
||||
story.sync_redis_shares(r=r)
|
||||
story.sync_redis_story(r=h)
|
||||
story.sync_redis_story(r=h, r2=h2)
|
||||
|
||||
def sync_redis(self):
|
||||
self.sync_redis_shares()
|
||||
|
@ -1548,19 +1549,26 @@ class MSharedStory(mongo.Document):
|
|||
else:
|
||||
r.srem(comment_key, self.user_id)
|
||||
|
||||
def sync_redis_story(self, r=None):
|
||||
def sync_redis_story(self, r=None, r2=None):
|
||||
if not r:
|
||||
r = redis.Redis(connection_pool=settings.REDIS_STORY_HASH_POOL)
|
||||
if not r2:
|
||||
r2 = redis.Redis(connection_pool=settings.REDIS_STORY_HASH_POOL2)
|
||||
|
||||
if not self.story_db_id:
|
||||
self.ensure_story_db_id(save=True)
|
||||
|
||||
if self.story_db_id:
|
||||
r.sadd('B:%s' % self.user_id, self.feed_guid_hash)
|
||||
r2.sadd('B:%s' % self.user_id, self.feed_guid_hash)
|
||||
r.zadd('zB:%s' % self.user_id, self.feed_guid_hash,
|
||||
time.mktime(self.shared_date.timetuple()))
|
||||
r2.zadd('zB:%s' % self.user_id, self.feed_guid_hash,
|
||||
time.mktime(self.shared_date.timetuple()))
|
||||
r.expire('B:%s' % self.user_id, settings.DAYS_OF_UNREAD*24*60*60)
|
||||
r2.expire('B:%s' % self.user_id, settings.DAYS_OF_UNREAD*24*60*60)
|
||||
r.expire('zB:%s' % self.user_id, settings.DAYS_OF_UNREAD*24*60*60)
|
||||
r2.expire('zB:%s' % self.user_id, settings.DAYS_OF_UNREAD*24*60*60)
|
||||
|
||||
def remove_from_redis(self):
|
||||
r = redis.Redis(connection_pool=settings.REDIS_POOL)
|
||||
|
@ -1571,8 +1579,11 @@ class MSharedStory(mongo.Document):
|
|||
r.srem(comment_key, self.user_id)
|
||||
|
||||
h = redis.Redis(connection_pool=settings.REDIS_STORY_HASH_POOL)
|
||||
h2 = redis.Redis(connection_pool=settings.REDIS_STORY_HASH_POOL2)
|
||||
h.srem('B:%s' % self.user_id, self.feed_guid_hash)
|
||||
h2.srem('B:%s' % self.user_id, self.feed_guid_hash)
|
||||
h.zrem('zB:%s' % self.user_id, self.feed_guid_hash)
|
||||
h2.zrem('zB:%s' % self.user_id, self.feed_guid_hash)
|
||||
|
||||
def publish_update_to_subscribers(self):
|
||||
try:
|
||||
|
|
|
@ -1078,7 +1078,9 @@ def ignore_follower(request):
|
|||
def find_friends(request):
|
||||
query = request.GET['query']
|
||||
limit = int(request.GET.get('limit', 3))
|
||||
profiles = MSocialProfile.objects.filter(username__icontains=query)[:limit]
|
||||
profiles = MSocialProfile.objects.filter(username__iexact=query)[:limit]
|
||||
if not profiles:
|
||||
profiles = MSocialProfile.objects.filter(username__icontains=query)[:limit]
|
||||
if not profiles:
|
||||
profiles = MSocialProfile.objects.filter(email__icontains=query)[:limit]
|
||||
if not profiles:
|
||||
|
|
|
@ -73,16 +73,19 @@ class RStats:
|
|||
keys = set()
|
||||
errors = set()
|
||||
prefixes = defaultdict(set)
|
||||
sizes = defaultdict(int)
|
||||
prefixes_ttls = defaultdict(lambda: defaultdict(int))
|
||||
prefix_re = re.compile(r"(\w+):(.*)")
|
||||
|
||||
p = r.pipeline()
|
||||
[p.randomkey() for _ in range(sample)]
|
||||
keys = set(p.execute())
|
||||
p = r.pipeline()
|
||||
|
||||
p = r.pipeline()
|
||||
[p.ttl(key) for key in keys]
|
||||
ttls = p.execute()
|
||||
|
||||
dump = [r.execute_command('dump', key) for key in keys]
|
||||
|
||||
for k, key in enumerate(keys):
|
||||
match = prefix_re.match(key)
|
||||
|
@ -91,8 +94,13 @@ class RStats:
|
|||
continue
|
||||
prefix, rest = match.groups()
|
||||
prefixes[prefix].add(rest)
|
||||
sizes[prefix] += len(dump[k])
|
||||
ttl = ttls[k]
|
||||
if ttl < 60*60: # 1 hour
|
||||
if ttl < 0: # Never expire
|
||||
prefixes_ttls[prefix]['-'] += 1
|
||||
elif ttl == 0:
|
||||
prefixes_ttls[prefix]['X'] += 1
|
||||
elif ttl < 60*60: # 1 hour
|
||||
prefixes_ttls[prefix]['1h'] += 1
|
||||
elif ttl < 60*60*12:
|
||||
prefixes_ttls[prefix]['12h'] += 1
|
||||
|
@ -106,9 +114,11 @@ class RStats:
|
|||
prefixes_ttls[prefix]['2w+'] += 1
|
||||
|
||||
keys_count = len(keys)
|
||||
total_size = float(sum([k for k in sizes.values()]))
|
||||
print " ---> %s total keys" % keys_count
|
||||
for prefix, rest in prefixes.items():
|
||||
print " ---> %4s: (%.4s%%) %s keys (%s)" % (prefix, 100. * (len(rest) / float(keys_count)), len(rest), dict(prefixes_ttls[prefix]))
|
||||
total_expiring = sum([k for p, k in dict(prefixes_ttls[prefix]).items() if p != "-"])
|
||||
print " ---> %4s: (%.4s%% keys - %.4s%% space) %s keys (%s expiring: %s)" % (prefix, 100. * (len(rest) / float(keys_count)), 100 * (sizes[prefix] / total_size), len(rest), total_expiring, dict(prefixes_ttls[prefix]))
|
||||
print " ---> %s errors: %s" % (len(errors), errors)
|
||||
|
||||
def round_time(dt=None, round_to=60):
|
||||
|
|
Before Width: | Height: | Size: 213 B After Width: | Height: | Size: 213 B |
Before Width: | Height: | Size: 213 B After Width: | Height: | Size: 213 B |
Before Width: | Height: | Size: 212 B After Width: | Height: | Size: 212 B |
Before Width: | Height: | Size: 224 B After Width: | Height: | Size: 224 B |
Before Width: | Height: | Size: 224 B After Width: | Height: | Size: 224 B |
Before Width: | Height: | Size: 263 B After Width: | Height: | Size: 263 B |
Before Width: | Height: | Size: 255 B After Width: | Height: | Size: 255 B |
Before Width: | Height: | Size: 219 B After Width: | Height: | Size: 219 B |
Before Width: | Height: | Size: 218 B After Width: | Height: | Size: 218 B |
Before Width: | Height: | Size: 261 B After Width: | Height: | Size: 261 B |
Before Width: | Height: | Size: 219 B After Width: | Height: | Size: 219 B |
Before Width: | Height: | Size: 219 B After Width: | Height: | Size: 219 B |
Before Width: | Height: | Size: 214 B After Width: | Height: | Size: 214 B |
Before Width: | Height: | Size: 212 B After Width: | Height: | Size: 212 B |
Before Width: | Height: | Size: 242 B After Width: | Height: | Size: 242 B |
Before Width: | Height: | Size: 241 B After Width: | Height: | Size: 241 B |
Before Width: | Height: | Size: 174 B After Width: | Height: | Size: 174 B |
Before Width: | Height: | Size: 174 B After Width: | Height: | Size: 174 B |
Before Width: | Height: | Size: 172 B After Width: | Height: | Size: 172 B |
Before Width: | Height: | Size: 183 B After Width: | Height: | Size: 183 B |
Before Width: | Height: | Size: 183 B After Width: | Height: | Size: 183 B |
Before Width: | Height: | Size: 183 B After Width: | Height: | Size: 183 B |
Before Width: | Height: | Size: 216 B After Width: | Height: | Size: 216 B |
Before Width: | Height: | Size: 214 B After Width: | Height: | Size: 214 B |
Before Width: | Height: | Size: 217 B After Width: | Height: | Size: 217 B |
Before Width: | Height: | Size: 214 B After Width: | Height: | Size: 214 B |
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 548 B After Width: | Height: | Size: 548 B |
Before Width: | Height: | Size: 438 B After Width: | Height: | Size: 438 B |
Before Width: | Height: | Size: 791 B After Width: | Height: | Size: 791 B |
Before Width: | Height: | Size: 605 B After Width: | Height: | Size: 605 B |
Before Width: | Height: | Size: 996 B After Width: | Height: | Size: 996 B |
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 409 B After Width: | Height: | Size: 409 B |
Before Width: | Height: | Size: 451 B After Width: | Height: | Size: 451 B |
Before Width: | Height: | Size: 815 B After Width: | Height: | Size: 815 B |
Before Width: | Height: | Size: 254 B After Width: | Height: | Size: 254 B |
Before Width: | Height: | Size: 126 B After Width: | Height: | Size: 126 B |
Before Width: | Height: | Size: 136 B After Width: | Height: | Size: 136 B |
Before Width: | Height: | Size: 578 B After Width: | Height: | Size: 578 B |
Before Width: | Height: | Size: 607 B After Width: | Height: | Size: 607 B |
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 973 B After Width: | Height: | Size: 973 B |
Before Width: | Height: | Size: 2 KiB After Width: | Height: | Size: 2 KiB |
Before Width: | Height: | Size: 705 B After Width: | Height: | Size: 705 B |
Before Width: | Height: | Size: 176 B After Width: | Height: | Size: 176 B |
Before Width: | Height: | Size: 132 B After Width: | Height: | Size: 132 B |
Before Width: | Height: | Size: 132 B After Width: | Height: | Size: 132 B |
Before Width: | Height: | Size: 176 B After Width: | Height: | Size: 176 B |
Before Width: | Height: | Size: 176 B After Width: | Height: | Size: 176 B |
Before Width: | Height: | Size: 176 B After Width: | Height: | Size: 176 B |
Before Width: | Height: | Size: 176 B After Width: | Height: | Size: 176 B |
Before Width: | Height: | Size: 203 B After Width: | Height: | Size: 203 B |
Before Width: | Height: | Size: 204 B After Width: | Height: | Size: 204 B |
Before Width: | Height: | Size: 885 B After Width: | Height: | Size: 885 B |
Before Width: | Height: | Size: 997 B After Width: | Height: | Size: 997 B |
Before Width: | Height: | Size: 183 B After Width: | Height: | Size: 183 B |
Before Width: | Height: | Size: 183 B After Width: | Height: | Size: 183 B |
Before Width: | Height: | Size: 858 B After Width: | Height: | Size: 858 B |
Before Width: | Height: | Size: 858 B After Width: | Height: | Size: 858 B |
Before Width: | Height: | Size: 183 B After Width: | Height: | Size: 183 B |
Before Width: | Height: | Size: 183 B After Width: | Height: | Size: 183 B |
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 1.8 KiB |
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 2.3 KiB |
Before Width: | Height: | Size: 324 B After Width: | Height: | Size: 324 B |
Before Width: | Height: | Size: 325 B After Width: | Height: | Size: 325 B |
Before Width: | Height: | Size: 315 B After Width: | Height: | Size: 315 B |
Before Width: | Height: | Size: 315 B After Width: | Height: | Size: 315 B |