comparison gpp/core/whos_online.py @ 423:3fe60148f75c

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