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