Example #1
0
 def __init__(self, name):
     self.name = name.lower().strip().replace('#', '')
     self.base_key = 'tag:{}:posts'.format(self.name)
     self.new = RedisSortedSet(self.base_key)
     self.images_only = RedisLastBumpedBuffer(self.base_key + ':images', 1000)
     self.popular = RedisLastBumpedBuffer(self.base_key + ':popular', 1000)
     self.post_count = RedisKey(self.base_key + ':count')
 def __init__(self, name):
     self.name = name.lower().strip().replace('#', '')
     self.base_key = 'tag:{}:posts'.format(self.name)
     self.new = RedisSortedSet(self.base_key)
     self.images_only = RedisLastBumpedBuffer(self.base_key + ':images', 1000)
     self.popular = RedisLastBumpedBuffer(self.base_key + ':popular', 1000)
     self.post_count = RedisKey(self.base_key + ':count')
Example #3
0
 def __init__(self,
              user_id,
              stream_size=1000,
              activity_types=ACTIVITY_TYPES):
     self._user_id = user_id
     self._activity_types = activity_types
     self._buffer = RedisLastBumpedBuffer(
         'user:{}:stream_v6'.format(user_id),
         stream_size,
         getter=self._make_activity)
     self._read = RedisLastBumpedBuffer(
         'user:{}:stream_read'.format(user_id), stream_size)
    def __init__(self, user_id, stream_size=450, activity_types=ACTIVITY_TYPES, buffer_key_postfix='stream_v6'):
        self._activity_types = {}

        for activity_type in activity_types:
            if isinstance(activity_type, basestring):
                activity_type = _load_activity_type(activity_type)

            self._activity_types[activity_type.TYPE] = activity_type

        self._user_id = user_id
        self._buffer = RedisLastBumpedBuffer('user:{}:{}'.format(user_id, buffer_key_postfix), stream_size)

        #TODO if we add support for this in DrawQuest, we need to use one stream size for both
        # iPhone and iPad if those streams differ.
        self._read = RedisLastBumpedBuffer('user:{}:stream_read'.format(user_id), stream_size)
Example #5
0
def _get_aggregate_rlbb(groups, nav):
    groups = list(groups)

    if not groups:
        return []
    else:
        rlbb = RedisLastBumpedBuffer(gen_temp_key(), size=None)
        buffers = [_get_buffer(group, nav).key for group in groups]
        redis.zunionstore(rlbb.key, buffers, aggregate='MAX')
        return rlbb
Example #6
0
    def __init__(self,
                 user_id,
                 stream_size=450,
                 activity_types=ACTIVITY_TYPES,
                 buffer_key_postfix='stream_v6'):
        self._activity_types = {}

        for activity_type in activity_types:
            if isinstance(activity_type, basestring):
                activity_type = _load_activity_type(activity_type)

            self._activity_types[activity_type.TYPE] = activity_type

        self._user_id = user_id
        self._buffer = RedisLastBumpedBuffer(
            'user:{}:{}'.format(user_id, buffer_key_postfix), stream_size)

        #TODO if we add support for this in DrawQuest, we need to use one stream size for both
        # iPhone and iPad if those streams differ.
        self._read = RedisLastBumpedBuffer(
            'user:{}:stream_read'.format(user_id), stream_size)
class Tag(object):
    top = property(lambda self: DateKey(lambda key: RedisLastBumpedBuffer(key, 30*30), self.base_key, ':top'))

    updates_channel = property(lambda self: RealtimeChannel('tu:%s' %  self.name, 5, ttl=24*60*60))

    def __repr__(self):
        return self.name

    def __init__(self, name):
        self.name = name.lower().strip().replace('#', '')
        self.base_key = 'tag:{}:posts'.format(self.name)
        self.new = RedisSortedSet(self.base_key)
        self.images_only = RedisLastBumpedBuffer(self.base_key + ':images', 1000)
        self.popular = RedisLastBumpedBuffer(self.base_key + ':popular', 1000)
        self.post_count = RedisKey(self.base_key + ':count')

    def to_client(self, **kwargs):
        return self.name

    def tag_comment(self, comment, timestamp=None):
        if timestamp is None:
            timestamp = Services.time.time()

        self.new.zadd(int(comment.id), timestamp)
        all_tags.sadd(self.name)

        if comment.reply_content is not None:
            self.images_only.bump(int(comment.id), score=timestamp)
            count = self.post_count.incr()
            self.updates_channel.publish({'post': comment.id, 'tag': self.name, 'count': count})

    def untag_comment(self, comment):
        self.new.zrem(comment.id)
        self.images_only.remove(comment.id)
        self.popular.remove(comment.id)

    def get_absolute_url(self):
        return '/x/' + self.name.replace('#','')

    def user_is_following(self, user):
        if not user.is_authenticated():
            return False

        return self.name in user.redis.followed_tags

    def merge_top_scores(self, day=None):
        """
        Merges daily top scores into monthly and monthly into yearly top scores for this group for the given day
        and the 365 days before it.

        If `day` is `None`, defaults to today.
        """
        if not day:
            day = Services.time.today()
        # Merge today + last 365 days
        days = [day - datetime.timedelta(n) for n in range(366)]

        months = defaultdict(list)
        for day in days:
            months[(day.year, day.month)].append(day)

        years = defaultdict(list)

        for (year, month) in months.keys():
            years[year].append(month)

        for (year, month), days in months.iteritems():
            dest = self.top.month(datetime.date(year, month, 1))
            source_keys = [self.top.day(day).key for day in days]
            redis.zunionstore(dest.key, source_keys, aggregate='max')
            dest.truncate(2)

        for year, year_months in years.iteritems():
            dest = self.top.year(datetime.date(year, 1, 1))
            source_keys = [self.top.month(datetime.date(year, month, 1)).key for month in year_months]
            redis.zunionstore(dest.key, source_keys, aggregate='max')
            dest.truncate(5)
Example #8
0
import time

from canvas.models import Visibility
from canvas.redis_models import redis, RedisLastBumpedBuffer
from drawquest import knobs
from drawquest.apps.quests.details_models import QuestDetails

top_quests_buffer = RedisLastBumpedBuffer('top_quests', 500)


def get_quest_score(quest):
    try:
        details = quest.details()
    except AttributeError:
        details = quest

    if not quest.ugq and not quest.scheduledquest_set.exists():
        return -1

    visibility = getattr(details, 'visibility', None)
    if visibility in Visibility.invisible_choices or visibility == Visibility.CURATED:
        return -1

    first_appeared_on = quest.first_appeared_on()

    if first_appeared_on is None:
        return -1

    tdelta = time.time() - first_appeared_on + 10 * 60
    weight = ((tdelta**1.9) if tdelta > 0 else 1)
    pop_score = details.drawing_count
class ActivityStream(object):
    ACTIVITY_TYPES = settings.ACTIVITY_TYPE_CLASSES

    def __init__(self, user_id, stream_size=450, activity_types=ACTIVITY_TYPES, buffer_key_postfix='stream_v6'):
        self._activity_types = {}

        for activity_type in activity_types:
            if isinstance(activity_type, basestring):
                activity_type = _load_activity_type(activity_type)

            self._activity_types[activity_type.TYPE] = activity_type

        self._user_id = user_id
        self._buffer = RedisLastBumpedBuffer('user:{}:{}'.format(user_id, buffer_key_postfix), stream_size)

        #TODO if we add support for this in DrawQuest, we need to use one stream size for both
        # iPhone and iPad if those streams differ.
        self._read = RedisLastBumpedBuffer('user:{}:stream_read'.format(user_id), stream_size)

    def _make_activity(self, activity_id):
        from apps.activity.models import Activity, LegacyActivity

        try:
            activity_data = Activity.details_by_id(activity_id)()
        except Activity.DoesNotExist:
            try:
                activity_data = LegacyActivity.details_by_id(activity_id)()
            except LegacyActivity.DoesNotExist:
                return None

        try:
            return self._activity_types[activity_data['activity_type']](activity_data)
        except KeyError as e:
            return None

    def _make_activities(self, activity_ids, earlier_than=None, later_than=None):
        from apps.activity.models import Activity, LegacyActivity

        def filter_by_ts(query):
            if earlier_than is not None:
                query = query.filter(timestamp__lt=earlier_than)

            if later_than is not None:
                query = query.filter(timestamp__gt=later_than)

            return query

        activity_ids = [int(id_) for id_ in activity_ids]

        activities = Activity.objects.filter(id__in=activity_ids).order_by('-timestamp')
        activities = filter_by_ts(activities)
        activities = CachedCall.queryset_details(activities)

        if len(activities) < len(activity_ids):
            legacy_ids = set(activity_ids) - set(int(activity['id']) for activity in activities)
            legacy_activities = LegacyActivity.objects.filter(id__in=legacy_ids).order_by('-timestamp')
            legacy_activities = filter_by_ts(legacy_activities)
            legacy_activities = CachedCall.queryset_details(legacy_activities)
            activities.extend(legacy_activities)

        ret = []

        for activity_data in activities:
            try:
                ret.append(self._activity_types[activity_data['activity_type']](activity_data))
            except KeyError as e:
                continue

        return ret

    def __iter__(self):
        for item_id in self._buffer:
            if item_id is not None:
                yield self._make_activity(item_id)

    def __getitem__(self, key):
        if not isinstance(key, slice):
            raise TypeError("ActivityStream is not indexable without using a slice.")

        return self._make_activities(self._buffer[key])

    def valid_activity_type(self, recipient, activity):
        if hasattr(activity, 'APP_VERSION'):
            last_version = recipient.kv.last_app_version.get()
            try:
                if not last_version or util.parse_version(last_version) < activity.APP_VERSION:
                    return False
            except ValueError:
                return False

        return activity.TYPE in self._activity_types

    def earlier_than(self, timestamp, num=None):
        """ Returns an iterator over the activities up until `timestamp`. """
        start = None if num is None else 0
        ids = self._buffer.zrevrangebyscore('({}'.format(timestamp), '-inf', start=start, num=num)
        return self._make_activities(ids)

    def later_than(self, timestamp, num=None):
        start = None if num is None else 0
        ids = self._buffer.zrevrangebyscore('inf', '({}'.format(timestamp), start=start, num=num)
        return self._make_activities(ids)

    def _invalidate_cache(self):
        from apps.activity import api
        api.invalidate_caches(self._user_id)

    def push(self, activity_item):
        from apps.activity.models import Activity

        if activity_item.TYPE not in self._activity_types:
            return

        if not hasattr(activity_item, 'id'):
            dbactivity = Activity.from_redis_activity(activity_item)
            id_ = dbactivity.id
        else:
            id_ = activity_item.id

        self._buffer.bump(id_, coerce=False)

        self._invalidate_cache()

    def mark_read(self, activity_id):
        from canvas.models import UserRedis

        user_redis = UserRedis(self._user_id)

        activity = self._make_activity(activity_id)

        try:
            if (activity_id not in self._read
                    and activity_id in set(self._buffer)
                    and float(user_redis.user_kv.hget('activity_stream_last_viewed')) < float(activity.timestamp)):
                user_redis.user_kv.hincrby('activity_stream_unseen', -1)
        except TypeError:
            pass

        self._read.bump(activity_id)

    def mark_all_read(self):
        from canvas.models import UserRedis
        user_redis = UserRedis(self._user_id)

        user_redis.user_kv.hset('activity_stream_last_viewed', Services.time.time())
        user_redis.user_kv.hset('activity_stream_unseen', 0)
        user_redis.activity_stream_channel.publish('activity_stream_viewed')

    def has_read(self, activity_id):
        activity_id = int(activity_id)

        from canvas.models import UserRedis
        user_redis = UserRedis(self._user_id)

        activity = self._make_activity(activity_id)

        if activity_id in self._read:
            return True

        try:
            return float(user_redis.user_kv.hget('activity_stream_last_viewed')) >= float(activity.timestamp)
        except TypeError:
            return False
Example #10
0
 def __init__(self, user_id, stream_size=1000, activity_types=ACTIVITY_TYPES):
     self._user_id = user_id
     self._activity_types = activity_types
     self._buffer = RedisLastBumpedBuffer('user:{}:stream_v6'.format(user_id), stream_size,
                                          getter=self._make_activity)
     self._read = RedisLastBumpedBuffer('user:{}:stream_read'.format(user_id), stream_size)
Example #11
0
class ActivityStream(object):
    ACTIVITY_TYPES = settings.ACTIVITY_TYPE_CLASSES

    def __init__(self,
                 user_id,
                 stream_size=450,
                 activity_types=ACTIVITY_TYPES,
                 buffer_key_postfix='stream_v6'):
        self._activity_types = {}

        for activity_type in activity_types:
            if isinstance(activity_type, basestring):
                activity_type = _load_activity_type(activity_type)

            self._activity_types[activity_type.TYPE] = activity_type

        self._user_id = user_id
        self._buffer = RedisLastBumpedBuffer(
            'user:{}:{}'.format(user_id, buffer_key_postfix), stream_size)

        #TODO if we add support for this in DrawQuest, we need to use one stream size for both
        # iPhone and iPad if those streams differ.
        self._read = RedisLastBumpedBuffer(
            'user:{}:stream_read'.format(user_id), stream_size)

    def _make_activity(self, activity_id):
        from apps.activity.models import Activity, LegacyActivity

        try:
            activity_data = Activity.details_by_id(activity_id)()
        except Activity.DoesNotExist:
            try:
                activity_data = LegacyActivity.details_by_id(activity_id)()
            except LegacyActivity.DoesNotExist:
                return None

        try:
            return self._activity_types[activity_data['activity_type']](
                activity_data)
        except KeyError as e:
            return None

    def _make_activities(self,
                         activity_ids,
                         earlier_than=None,
                         later_than=None):
        from apps.activity.models import Activity, LegacyActivity

        def filter_by_ts(query):
            if earlier_than is not None:
                query = query.filter(timestamp__lt=earlier_than)

            if later_than is not None:
                query = query.filter(timestamp__gt=later_than)

            return query

        activity_ids = [int(id_) for id_ in activity_ids]

        activities = Activity.objects.filter(
            id__in=activity_ids).order_by('-timestamp')
        activities = filter_by_ts(activities)
        activities = CachedCall.queryset_details(activities)

        if len(activities) < len(activity_ids):
            legacy_ids = set(activity_ids) - set(
                int(activity['id']) for activity in activities)
            legacy_activities = LegacyActivity.objects.filter(
                id__in=legacy_ids).order_by('-timestamp')
            legacy_activities = filter_by_ts(legacy_activities)
            legacy_activities = CachedCall.queryset_details(legacy_activities)
            activities.extend(legacy_activities)

        ret = []

        for activity_data in activities:
            try:
                ret.append(self._activity_types[activity_data['activity_type']]
                           (activity_data))
            except KeyError as e:
                continue

        return ret

    def __iter__(self):
        for item_id in self._buffer:
            if item_id is not None:
                yield self._make_activity(item_id)

    def __getitem__(self, key):
        if not isinstance(key, slice):
            raise TypeError(
                "ActivityStream is not indexable without using a slice.")

        return self._make_activities(self._buffer[key])

    def valid_activity_type(self, recipient, activity):
        if hasattr(activity, 'APP_VERSION'):
            last_version = recipient.kv.last_app_version.get()
            try:
                if not last_version or util.parse_version(
                        last_version) < activity.APP_VERSION:
                    return False
            except ValueError:
                return False

        return activity.TYPE in self._activity_types

    def earlier_than(self, timestamp, num=None):
        """ Returns an iterator over the activities up until `timestamp`. """
        start = None if num is None else 0
        ids = self._buffer.zrevrangebyscore('({}'.format(timestamp),
                                            '-inf',
                                            start=start,
                                            num=num)
        return self._make_activities(ids)

    def later_than(self, timestamp, num=None):
        start = None if num is None else 0
        ids = self._buffer.zrevrangebyscore('inf',
                                            '({}'.format(timestamp),
                                            start=start,
                                            num=num)
        return self._make_activities(ids)

    def _invalidate_cache(self):
        from apps.activity import api
        api.invalidate_caches(self._user_id)

    def push(self, activity_item):
        from apps.activity.models import Activity

        if activity_item.TYPE not in self._activity_types:
            return

        if not hasattr(activity_item, 'id'):
            dbactivity = Activity.from_redis_activity(activity_item)
            id_ = dbactivity.id
        else:
            id_ = activity_item.id

        self._buffer.bump(id_, coerce=False)

        self._invalidate_cache()

    def mark_read(self, activity_id):
        from canvas.models import UserRedis

        user_redis = UserRedis(self._user_id)

        activity = self._make_activity(activity_id)

        try:
            if (activity_id not in self._read
                    and activity_id in set(self._buffer) and float(
                        user_redis.user_kv.hget('activity_stream_last_viewed'))
                    < float(activity.timestamp)):
                user_redis.user_kv.hincrby('activity_stream_unseen', -1)
        except TypeError:
            pass

        self._read.bump(activity_id)

    def mark_all_read(self):
        from canvas.models import UserRedis
        user_redis = UserRedis(self._user_id)

        user_redis.user_kv.hset('activity_stream_last_viewed',
                                Services.time.time())
        user_redis.user_kv.hset('activity_stream_unseen', 0)
        user_redis.activity_stream_channel.publish('activity_stream_viewed')

    def has_read(self, activity_id):
        activity_id = int(activity_id)

        from canvas.models import UserRedis
        user_redis = UserRedis(self._user_id)

        activity = self._make_activity(activity_id)

        if activity_id in self._read:
            return True

        try:
            return float(user_redis.user_kv.hget(
                'activity_stream_last_viewed')) >= float(activity.timestamp)
        except TypeError:
            return False
Example #12
0
class Tag(object):
    top = property(lambda self: DateKey(lambda key: RedisLastBumpedBuffer(key, 30*30), self.base_key, ':top'))

    updates_channel = property(lambda self: RealtimeChannel('tu:%s' %  self.name, 5, ttl=24*60*60))

    def __repr__(self):
        return self.name

    def __init__(self, name):
        self.name = name.lower().strip().replace('#', '')
        self.base_key = 'tag:{}:posts'.format(self.name)
        self.new = RedisSortedSet(self.base_key)
        self.images_only = RedisLastBumpedBuffer(self.base_key + ':images', 1000)
        self.popular = RedisLastBumpedBuffer(self.base_key + ':popular', 1000)
        self.post_count = RedisKey(self.base_key + ':count')

    def to_client(self):
        return self.name

    def tag_comment(self, comment, timestamp=None):
        if timestamp is None:
            timestamp = Services.time.time()

        self.new.zadd(int(comment.id), timestamp)
        all_tags.sadd(self.name)

        if comment.reply_content is not None:
            self.images_only.bump(int(comment.id), score=timestamp)
            count = self.post_count.incr()
            self.updates_channel.publish({'post': comment.id, 'tag': self.name, 'count': count})

    def untag_comment(self, comment):
        self.new.zrem(comment.id)
        self.images_only.remove(comment.id)
        self.popular.remove(comment.id)

    def get_absolute_url(self):
        return '/x/' + self.name.replace('#','')

    def user_is_following(self, user):
        if not user.is_authenticated():
            return False

        return self.name in user.redis.followed_tags

    def merge_top_scores(self, day=None):
        """
        Merges daily top scores into monthly and monthly into yearly top scores for this group for the given day
        and the 365 days before it.

        If `day` is `None`, defaults to today.
        """
        if not day:
            day = Services.time.today()
        # Merge today + last 365 days
        days = [day - datetime.timedelta(n) for n in range(366)]

        months = defaultdict(list)
        for day in days:
            months[(day.year, day.month)].append(day)

        years = defaultdict(list)

        for (year, month) in months.keys():
            years[year].append(month)

        for (year, month), days in months.iteritems():
            dest = self.top.month(datetime.date(year, month, 1))
            source_keys = [self.top.day(day).key for day in days]
            redis.zunionstore(dest.key, source_keys, aggregate='max')
            dest.truncate(2)

        for year, year_months in years.iteritems():
            dest = self.top.year(datetime.date(year, 1, 1))
            source_keys = [self.top.month(datetime.date(year, month, 1)).key for month in year_months]
            redis.zunionstore(dest.key, source_keys, aggregate='max')
            dest.truncate(5)
Example #13
0
class ActivityStream(object):
    ACTIVITY_TYPES = {cls.TYPE: cls for cls in _load_activity_types()}

    def __init__(self,
                 user_id,
                 stream_size=1000,
                 activity_types=ACTIVITY_TYPES):
        self._user_id = user_id
        self._activity_types = activity_types
        self._buffer = RedisLastBumpedBuffer(
            'user:{}:stream_v6'.format(user_id),
            stream_size,
            getter=self._make_activity)
        self._read = RedisLastBumpedBuffer(
            'user:{}:stream_read'.format(user_id), stream_size)

    def _make_activity(self, activity_id):
        from apps.activity.models import Activity
        activity_data = Activity.details_by_id(activity_id)()
        try:
            return self._activity_types[activity_data['activity_type']](
                activity_data)
        except KeyError:
            return None

    def __iter__(self):
        for item in self._buffer:
            if item is not None:
                yield item

    def valid_activity_type(self, activity_type):
        return activity_type in self.ACTIVITY_TYPES

    def iter_until(self, timestamp):
        """ Returns an iterator over the activities up until `timestamp`. """
        return itertools.dropwhile(
            lambda activity: activity.timestamp >= float(timestamp),
            self._buffer)

    def _invalidate_cache(self):
        if settings.PROJECT == 'drawquest':
            from apps.activity import api
            api.activity_stream_items.delete_cache(None,
                                                   None,
                                                   user=self._user_id)

    def push(self, activity_item):
        from apps.activity.models import Activity

        if not hasattr(activity_item, 'id'):
            dbactivity = Activity.from_redis_activity(activity_item)
            id_ = dbactivity.id
        else:
            id_ = activity_item.id
        self._buffer.bump(id_, coerce=False)

        self._invalidate_cache()

    def mark_read(self, activity_id):
        from canvas.models import UserRedis
        user_redis = UserRedis(self._user_id)

        activity = self._make_activity(activity_id)

        try:
            if (activity_id not in self._read
                    and activity_id in set(item.id for item in self._buffer)
                    and float(
                        user_redis.user_kv.hget('activity_stream_last_viewed'))
                    < float(activity.timestamp)):
                user_redis.user_kv.hincrby('activity_stream_unseen', -1)
        except TypeError:
            pass

        self._read.bump(activity_id)

    def mark_all_read(self):
        from canvas.models import UserRedis
        user_redis = UserRedis(self._user_id)

        user_redis.user_kv.hset('activity_stream_last_viewed',
                                Services.time.time())
        user_redis.user_kv.hset('activity_stream_unseen', 0)
        user_redis.activity_stream_channel.publish('activity_stream_viewed')

    def has_read(self, activity_id):
        activity_id = int(activity_id)

        from canvas.models import UserRedis
        user_redis = UserRedis(self._user_id)

        activity = self._make_activity(activity_id)

        if activity_id in self._read:
            return True

        try:
            return float(user_redis.user_kv.hget(
                'activity_stream_last_viewed')) >= float(activity.timestamp)
        except TypeError:
            return False
Example #14
0
 def after_setUp(self):
     redis.delete('rblf_key')
     self.lbf = RedisLastBumpedBuffer('rblf_key', 3)
Example #15
0
class TestRedisLastBumpedBuffer(CanvasTestCase):
    def after_setUp(self):
        redis.delete('rblf_key')
        self.lbf = RedisLastBumpedBuffer('rblf_key', 3)

    def set_common_data(self):
        self.lbf.bump(1, 0.1)
        self.lbf.bump(2, 0.9)
        self.lbf.bump(3, 0.4)
        self.lbf.bump(4, 1.0)

    def test_bump_and_get_back_bumped_id(self):
        self.lbf.bump(10, 1.23)
        self.assertEquals([10], self.lbf[:])

    def test_bump_thrice_and_get_ids_in_decreasing_value_order(self):
        self.lbf.bump(10, 1.0)
        self.lbf.bump(11, 1.1)
        self.lbf.bump(9, 1.2)
        self.assertEquals([9, 11, 10], self.lbf[:])

    def test_bumped_four_times_and_get_top_3_ids(self):
        self.set_common_data()
        self.assertEquals([4, 2, 3], self.lbf[:])

    def test_get_the_top_2_via_slicing(self):
        self.set_common_data()
        self.assertEquals([4, 2], self.lbf[:2])
        self.assertEquals([4, 2], self.lbf[0:2])

    def test_get_the_the_back_2_via_slicing(self):
        self.set_common_data()
        self.assertEquals([2, 3], self.lbf[1:3])

    def test_ten_bumps_still_three_items(self):
        for x in range(10):
            self.lbf.bump(x, x)
        self.assertEquals([9, 8, 7], self.lbf[:])
 def after_setUp(self):
     redis.delete('rblf_key')
     self.lbf = RedisLastBumpedBuffer('rblf_key', 3)
class TestRedisLastBumpedBuffer(CanvasTestCase):
    def after_setUp(self):
        redis.delete('rblf_key')
        self.lbf = RedisLastBumpedBuffer('rblf_key', 3)

    def set_common_data(self):
        self.lbf.bump(1, 0.1)
        self.lbf.bump(2, 0.9)
        self.lbf.bump(3, 0.4)
        self.lbf.bump(4, 1.0)

    def test_bump_and_get_back_bumped_id(self):
        self.lbf.bump(10, 1.23)
        self.assertEquals([10], self.lbf[:])

    def test_bump_thrice_and_get_ids_in_decreasing_value_order(self):
        self.lbf.bump(10, 1.0)
        self.lbf.bump(11, 1.1)
        self.lbf.bump(9, 1.2)
        self.assertEquals([9, 11, 10], self.lbf[:])

    def test_bumped_four_times_and_get_top_3_ids(self):
        self.set_common_data()
        self.assertEquals([4, 2, 3], self.lbf[:])

    def test_get_the_top_2_via_slicing(self):
        self.set_common_data()
        self.assertEquals([4, 2], self.lbf[:2])
        self.assertEquals([4, 2], self.lbf[0:2])

    def test_get_the_the_back_2_via_slicing(self):
        self.set_common_data()
        self.assertEquals([2, 3], self.lbf[1:3])

    def test_ten_bumps_still_three_items(self):
        for x in range(10):
            self.lbf.bump(x, x)
        self.assertEquals([9,8,7], self.lbf[:])
Example #18
0
class ActivityStream(object):
    ACTIVITY_TYPES = {cls.TYPE: cls for cls in _load_activity_types()}

    def __init__(self, user_id, stream_size=1000, activity_types=ACTIVITY_TYPES):
        self._user_id = user_id
        self._activity_types = activity_types
        self._buffer = RedisLastBumpedBuffer('user:{}:stream_v6'.format(user_id), stream_size,
                                             getter=self._make_activity)
        self._read = RedisLastBumpedBuffer('user:{}:stream_read'.format(user_id), stream_size)

    def _make_activity(self, activity_id):
        from apps.activity.models import Activity
        activity_data = Activity.details_by_id(activity_id)()
        try:
            return self._activity_types[activity_data['activity_type']](activity_data)
        except KeyError:
            return None

    def __iter__(self):
        for item in self._buffer:
            if item is not None:
                yield item

    def valid_activity_type(self, activity_type):
        return activity_type in self.ACTIVITY_TYPES

    def iter_until(self, timestamp):
        """ Returns an iterator over the activities up until `timestamp`. """
        return itertools.dropwhile(lambda activity: activity.timestamp >= float(timestamp), self._buffer)
    
    def _invalidate_cache(self):
        if settings.PROJECT == 'drawquest':
            from apps.activity import api
            api.activity_stream_items.delete_cache(None, None, user=self._user_id)

    def push(self, activity_item):
        from apps.activity.models import Activity

        if not hasattr(activity_item, 'id'):
            dbactivity = Activity.from_redis_activity(activity_item)
            id_ = dbactivity.id
        else:
            id_ = activity_item.id
        self._buffer.bump(id_, coerce=False)

        self._invalidate_cache()

    def mark_read(self, activity_id):
        from canvas.models import UserRedis
        user_redis = UserRedis(self._user_id)

        activity = self._make_activity(activity_id)

        try:
            if (activity_id not in self._read
                    and activity_id in set(item.id for item in self._buffer)
                    and float(user_redis.user_kv.hget('activity_stream_last_viewed')) < float(activity.timestamp)):
                user_redis.user_kv.hincrby('activity_stream_unseen', -1)
        except TypeError:
            pass

        self._read.bump(activity_id)

    def mark_all_read(self):
        from canvas.models import UserRedis
        user_redis = UserRedis(self._user_id)

        user_redis.user_kv.hset('activity_stream_last_viewed', Services.time.time())
        user_redis.user_kv.hset('activity_stream_unseen', 0)
        user_redis.activity_stream_channel.publish('activity_stream_viewed')

    def has_read(self, activity_id):
        activity_id = int(activity_id)

        from canvas.models import UserRedis
        user_redis = UserRedis(self._user_id)

        activity = self._make_activity(activity_id)

        if activity_id in self._read:
            return True

        try:
            return float(user_redis.user_kv.hget('activity_stream_last_viewed')) >= float(activity.timestamp)
        except TypeError:
            return False