view gpp/core/whos_online.py @ 499:1a09a7bea000

For #236, switch to a settings package scheme.
author Brian Neal <bgneal@gmail.com>
date Wed, 30 Nov 2011 02:41:18 +0000
parents 3fe60148f75c
children 6f5fff924877
line wrap: on
line source
"""
This module keeps track of who is online. We maintain records for both
authenticated users ("users") and non-authenticated visitors ("visitors").
"""
import logging

from django.conf import settings
import redis

# Users and visitors each have 2 sets that we store in a Redis database:
# a current set and an old set. Whenever a user or visitor is seen, the
# current set is updated. At some interval, the current set is renamed
# to the old set, thus destroying it. At any given time, the union of the
# current and old sets is the "who's online" set.

# Redis connection and database settings

HOST = getattr(settings, 'WHOS_ONLINE_REDIS_HOST', 'localhost')
PORT = getattr(settings, 'WHOS_ONLINE_REDIS_PORT', 6379)
DB = getattr(settings, 'WHOS_ONLINE_REDIS_DB', 0)

# Redis key names:
USER_CURRENT_KEY = "wo_user_current"
USER_OLD_KEY = "wo_user_old"
USER_KEYS = [USER_CURRENT_KEY, USER_OLD_KEY]

VISITOR_CURRENT_KEY = "wo_visitor_current"
VISITOR_OLD_KEY = "wo_visitor_old"
VISITOR_KEYS = [VISITOR_CURRENT_KEY, VISITOR_OLD_KEY]


# Logging: we don't want a Redis malfunction to bring down the site. So we
# catch all Redis exceptions, log them, and press on.
logger = logging.getLogger(__name__)


def _get_connection():
    """
    Create and return a Redis connection. Returns None on failure.
    """
    try:
        conn = redis.Redis(host=HOST, port=PORT, db=DB)
        return conn
    except redis.RedisError, e:
        logger.error(e)

    return None


def report_user(username):
    """
    Call this function when a user has been seen. The username will be added to
    the current set.
    """
    conn = _get_connection()
    if conn:
        try:
            conn.sadd(USER_CURRENT_KEY, username)
        except redis.RedisError, e:
            logger.error(e)


def report_visitor(ip):
    """
    Call this function when a visitor has been seen. The IP address will be
    added the current set.
    """
    conn = _get_connection()
    if conn:
        try:
            conn.sadd(VISITOR_CURRENT_KEY, ip)
        except redis.RedisError, e:
            logger.error(e)


def get_users_online():
    """
    Returns a set of user names which is the union of the current and old
    sets.
    """
    conn = _get_connection()
    if conn:
        try:
            # Note that keys that do not exist are considered empty sets
            return conn.sunion(USER_KEYS)
        except redis.RedisError, e:
            logger.error(e)

    return set()


def get_visitors_online():
    """
    Returns a set of visitor IP addresses which is the union of the current
    and old sets.
    """
    conn = _get_connection()
    if conn:
        try:
            # Note that keys that do not exist are considered empty sets
            return conn.sunion(VISITOR_KEYS)
        except redis.RedisError, e:
            logger.error(e)

    return set()


def _tick_set(conn, current, old):
    """
    This function renames the set named "current" to "old".
    """
    # An exception may be raised if the current key doesn't exist; if that
    # happens we have to delete the old set because no one is online.
    try:
        conn.rename(current, old)
    except redis.ResponseError:
        try:
            del conn[old]
        except redis.RedisError, e:
            logger.error(e)
    except redis.RedisError, e:
        logger.error(e)


def tick():
    """
    Call this function to "age out" the old sets by renaming the current sets
    to the old.
    """
    conn = _get_connection()
    if conn:
        _tick_set(conn, USER_CURRENT_KEY, USER_OLD_KEY)
        _tick_set(conn, VISITOR_CURRENT_KEY, VISITOR_OLD_KEY)