annotate gpp/core/whos_online.py @ 516:beda97542da8

For #194, add a celery beat task for Django & forum cleanup.
author Brian Neal <bgneal@gmail.com>
date Thu, 15 Dec 2011 00:59:32 +0000
parents 6f5fff924877
children f72ace06658a
rev   line source
bgneal@423 1 """
bgneal@423 2 This module keeps track of who is online. We maintain records for both
bgneal@423 3 authenticated users ("users") and non-authenticated visitors ("visitors").
bgneal@423 4 """
bgneal@423 5 import logging
bgneal@423 6
bgneal@423 7 import redis
bgneal@423 8
bgneal@508 9 from core.services import get_redis_connection
bgneal@508 10
bgneal@508 11
bgneal@423 12 # Users and visitors each have 2 sets that we store in a Redis database:
bgneal@423 13 # a current set and an old set. Whenever a user or visitor is seen, the
bgneal@423 14 # current set is updated. At some interval, the current set is renamed
bgneal@423 15 # to the old set, thus destroying it. At any given time, the union of the
bgneal@423 16 # current and old sets is the "who's online" set.
bgneal@423 17
bgneal@423 18 # Redis key names:
bgneal@423 19 USER_CURRENT_KEY = "wo_user_current"
bgneal@423 20 USER_OLD_KEY = "wo_user_old"
bgneal@423 21 USER_KEYS = [USER_CURRENT_KEY, USER_OLD_KEY]
bgneal@423 22
bgneal@423 23 VISITOR_CURRENT_KEY = "wo_visitor_current"
bgneal@423 24 VISITOR_OLD_KEY = "wo_visitor_old"
bgneal@423 25 VISITOR_KEYS = [VISITOR_CURRENT_KEY, VISITOR_OLD_KEY]
bgneal@423 26
bgneal@423 27
bgneal@423 28 # Logging: we don't want a Redis malfunction to bring down the site. So we
bgneal@423 29 # catch all Redis exceptions, log them, and press on.
bgneal@423 30 logger = logging.getLogger(__name__)
bgneal@423 31
bgneal@423 32
bgneal@423 33 def _get_connection():
bgneal@423 34 """
bgneal@423 35 Create and return a Redis connection. Returns None on failure.
bgneal@423 36 """
bgneal@423 37 try:
bgneal@508 38 conn = get_redis_connection()
bgneal@423 39 return conn
bgneal@423 40 except redis.RedisError, e:
bgneal@423 41 logger.error(e)
bgneal@423 42
bgneal@423 43 return None
bgneal@423 44
bgneal@423 45
bgneal@423 46 def report_user(username):
bgneal@423 47 """
bgneal@423 48 Call this function when a user has been seen. The username will be added to
bgneal@423 49 the current set.
bgneal@423 50 """
bgneal@423 51 conn = _get_connection()
bgneal@423 52 if conn:
bgneal@423 53 try:
bgneal@423 54 conn.sadd(USER_CURRENT_KEY, username)
bgneal@423 55 except redis.RedisError, e:
bgneal@423 56 logger.error(e)
bgneal@423 57
bgneal@423 58
bgneal@423 59 def report_visitor(ip):
bgneal@423 60 """
bgneal@423 61 Call this function when a visitor has been seen. The IP address will be
bgneal@423 62 added the current set.
bgneal@423 63 """
bgneal@423 64 conn = _get_connection()
bgneal@423 65 if conn:
bgneal@423 66 try:
bgneal@423 67 conn.sadd(VISITOR_CURRENT_KEY, ip)
bgneal@423 68 except redis.RedisError, e:
bgneal@423 69 logger.error(e)
bgneal@423 70
bgneal@423 71
bgneal@423 72 def get_users_online():
bgneal@423 73 """
bgneal@423 74 Returns a set of user names which is the union of the current and old
bgneal@423 75 sets.
bgneal@423 76 """
bgneal@423 77 conn = _get_connection()
bgneal@423 78 if conn:
bgneal@423 79 try:
bgneal@423 80 # Note that keys that do not exist are considered empty sets
bgneal@423 81 return conn.sunion(USER_KEYS)
bgneal@423 82 except redis.RedisError, e:
bgneal@423 83 logger.error(e)
bgneal@423 84
bgneal@423 85 return set()
bgneal@423 86
bgneal@423 87
bgneal@423 88 def get_visitors_online():
bgneal@423 89 """
bgneal@423 90 Returns a set of visitor IP addresses which is the union of the current
bgneal@423 91 and old sets.
bgneal@423 92 """
bgneal@423 93 conn = _get_connection()
bgneal@423 94 if conn:
bgneal@423 95 try:
bgneal@423 96 # Note that keys that do not exist are considered empty sets
bgneal@423 97 return conn.sunion(VISITOR_KEYS)
bgneal@423 98 except redis.RedisError, e:
bgneal@423 99 logger.error(e)
bgneal@423 100
bgneal@423 101 return set()
bgneal@423 102
bgneal@423 103
bgneal@423 104 def _tick_set(conn, current, old):
bgneal@423 105 """
bgneal@423 106 This function renames the set named "current" to "old".
bgneal@423 107 """
bgneal@423 108 # An exception may be raised if the current key doesn't exist; if that
bgneal@423 109 # happens we have to delete the old set because no one is online.
bgneal@423 110 try:
bgneal@423 111 conn.rename(current, old)
bgneal@423 112 except redis.ResponseError:
bgneal@423 113 try:
bgneal@423 114 del conn[old]
bgneal@423 115 except redis.RedisError, e:
bgneal@423 116 logger.error(e)
bgneal@423 117 except redis.RedisError, e:
bgneal@423 118 logger.error(e)
bgneal@423 119
bgneal@423 120
bgneal@423 121 def tick():
bgneal@423 122 """
bgneal@423 123 Call this function to "age out" the old sets by renaming the current sets
bgneal@423 124 to the old.
bgneal@423 125 """
bgneal@423 126 conn = _get_connection()
bgneal@423 127 if conn:
bgneal@423 128 _tick_set(conn, USER_CURRENT_KEY, USER_OLD_KEY)
bgneal@423 129 _tick_set(conn, VISITOR_CURRENT_KEY, VISITOR_OLD_KEY)