annotate gpp/core/whos_online.py @ 505:a5d11471d031

Refactor the logic in the rate limiter decorator. Check to see if the request was ajax, as the ajax view always returns 200. Have to decode the JSON response to see if an error occurred or not.
author Brian Neal <bgneal@gmail.com>
date Sat, 03 Dec 2011 19:13:38 +0000
parents 3fe60148f75c
children 6f5fff924877
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 from django.conf import settings
bgneal@423 8 import redis
bgneal@423 9
bgneal@423 10 # Users and visitors each have 2 sets that we store in a Redis database:
bgneal@423 11 # a current set and an old set. Whenever a user or visitor is seen, the
bgneal@423 12 # current set is updated. At some interval, the current set is renamed
bgneal@423 13 # to the old set, thus destroying it. At any given time, the union of the
bgneal@423 14 # current and old sets is the "who's online" set.
bgneal@423 15
bgneal@423 16 # Redis connection and database settings
bgneal@423 17
bgneal@423 18 HOST = getattr(settings, 'WHOS_ONLINE_REDIS_HOST', 'localhost')
bgneal@423 19 PORT = getattr(settings, 'WHOS_ONLINE_REDIS_PORT', 6379)
bgneal@423 20 DB = getattr(settings, 'WHOS_ONLINE_REDIS_DB', 0)
bgneal@423 21
bgneal@423 22 # Redis key names:
bgneal@423 23 USER_CURRENT_KEY = "wo_user_current"
bgneal@423 24 USER_OLD_KEY = "wo_user_old"
bgneal@423 25 USER_KEYS = [USER_CURRENT_KEY, USER_OLD_KEY]
bgneal@423 26
bgneal@423 27 VISITOR_CURRENT_KEY = "wo_visitor_current"
bgneal@423 28 VISITOR_OLD_KEY = "wo_visitor_old"
bgneal@423 29 VISITOR_KEYS = [VISITOR_CURRENT_KEY, VISITOR_OLD_KEY]
bgneal@423 30
bgneal@423 31
bgneal@423 32 # Logging: we don't want a Redis malfunction to bring down the site. So we
bgneal@423 33 # catch all Redis exceptions, log them, and press on.
bgneal@423 34 logger = logging.getLogger(__name__)
bgneal@423 35
bgneal@423 36
bgneal@423 37 def _get_connection():
bgneal@423 38 """
bgneal@423 39 Create and return a Redis connection. Returns None on failure.
bgneal@423 40 """
bgneal@423 41 try:
bgneal@423 42 conn = redis.Redis(host=HOST, port=PORT, db=DB)
bgneal@423 43 return conn
bgneal@423 44 except redis.RedisError, e:
bgneal@423 45 logger.error(e)
bgneal@423 46
bgneal@423 47 return None
bgneal@423 48
bgneal@423 49
bgneal@423 50 def report_user(username):
bgneal@423 51 """
bgneal@423 52 Call this function when a user has been seen. The username will be added to
bgneal@423 53 the current set.
bgneal@423 54 """
bgneal@423 55 conn = _get_connection()
bgneal@423 56 if conn:
bgneal@423 57 try:
bgneal@423 58 conn.sadd(USER_CURRENT_KEY, username)
bgneal@423 59 except redis.RedisError, e:
bgneal@423 60 logger.error(e)
bgneal@423 61
bgneal@423 62
bgneal@423 63 def report_visitor(ip):
bgneal@423 64 """
bgneal@423 65 Call this function when a visitor has been seen. The IP address will be
bgneal@423 66 added the current set.
bgneal@423 67 """
bgneal@423 68 conn = _get_connection()
bgneal@423 69 if conn:
bgneal@423 70 try:
bgneal@423 71 conn.sadd(VISITOR_CURRENT_KEY, ip)
bgneal@423 72 except redis.RedisError, e:
bgneal@423 73 logger.error(e)
bgneal@423 74
bgneal@423 75
bgneal@423 76 def get_users_online():
bgneal@423 77 """
bgneal@423 78 Returns a set of user names which is the union of the current and old
bgneal@423 79 sets.
bgneal@423 80 """
bgneal@423 81 conn = _get_connection()
bgneal@423 82 if conn:
bgneal@423 83 try:
bgneal@423 84 # Note that keys that do not exist are considered empty sets
bgneal@423 85 return conn.sunion(USER_KEYS)
bgneal@423 86 except redis.RedisError, e:
bgneal@423 87 logger.error(e)
bgneal@423 88
bgneal@423 89 return set()
bgneal@423 90
bgneal@423 91
bgneal@423 92 def get_visitors_online():
bgneal@423 93 """
bgneal@423 94 Returns a set of visitor IP addresses which is the union of the current
bgneal@423 95 and old sets.
bgneal@423 96 """
bgneal@423 97 conn = _get_connection()
bgneal@423 98 if conn:
bgneal@423 99 try:
bgneal@423 100 # Note that keys that do not exist are considered empty sets
bgneal@423 101 return conn.sunion(VISITOR_KEYS)
bgneal@423 102 except redis.RedisError, e:
bgneal@423 103 logger.error(e)
bgneal@423 104
bgneal@423 105 return set()
bgneal@423 106
bgneal@423 107
bgneal@423 108 def _tick_set(conn, current, old):
bgneal@423 109 """
bgneal@423 110 This function renames the set named "current" to "old".
bgneal@423 111 """
bgneal@423 112 # An exception may be raised if the current key doesn't exist; if that
bgneal@423 113 # happens we have to delete the old set because no one is online.
bgneal@423 114 try:
bgneal@423 115 conn.rename(current, old)
bgneal@423 116 except redis.ResponseError:
bgneal@423 117 try:
bgneal@423 118 del conn[old]
bgneal@423 119 except redis.RedisError, e:
bgneal@423 120 logger.error(e)
bgneal@423 121 except redis.RedisError, e:
bgneal@423 122 logger.error(e)
bgneal@423 123
bgneal@423 124
bgneal@423 125 def tick():
bgneal@423 126 """
bgneal@423 127 Call this function to "age out" the old sets by renaming the current sets
bgneal@423 128 to the old.
bgneal@423 129 """
bgneal@423 130 conn = _get_connection()
bgneal@423 131 if conn:
bgneal@423 132 _tick_set(conn, USER_CURRENT_KEY, USER_OLD_KEY)
bgneal@423 133 _tick_set(conn, VISITOR_CURRENT_KEY, VISITOR_OLD_KEY)