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)
|