"""This module defines a very basic store that's used by the CGI interface
to store session and one-time-key information.

Yes, it's called "sessions" - because originally it only defined a session
class. It's now also used for One Time Key handling too.
"""
__docformat__ = 'restructuredtext'
import time

from roundup.anypy.html import html_escape as escape
from roundup.backends.sessions_common import SessionCommon


class BasicDatabase(SessionCommon):
    ''' Provide a nice encapsulation of an RDBMS table.

        Keys are id strings, values are automatically marshalled data.
    '''
    name = None

    def __init__(self, db):
        self.db = db
        self.conn, self.cursor = self.db.sql_open_connection()

    def clear(self):
        self.cursor.execute('delete from %ss' % self.name)

    def exists(self, infoid):
        n = self.name
        self.cursor.execute('select count(*) from %ss where %s_key=%s' %
                            (n, n, self.db.arg), (infoid,))
        return int(self.cursor.fetchone()[0])

    _marker = []

    def get(self, infoid, value, default=_marker):
        n = self.name
        self.cursor.execute('select %s_value from %ss where %s_key=%s' %
                            (n, n, n, self.db.arg), (infoid,))
        res = self.cursor.fetchone()
        if not res:
            if default != self._marker:
                return default
            raise KeyError('No such %s "%s"' % (self.name, escape(infoid)))
        values = eval(res[0])
        return values.get(value, None)

    def getall(self, infoid):
        n = self.name
        self.cursor.execute('select %s_value from %ss where %s_key=%s' %
                            (n, n, n, self.db.arg), (infoid,))
        res = self.cursor.fetchone()
        if not res:
            raise KeyError('No such %s "%s"' % (self.name, escape(infoid)))
        return eval(res[0])

    def set(self, infoid, **newvalues):
        """ Store all newvalues under key infoid with a timestamp in database.

            If newvalues['__timestamp'] exists and is representable as
            a floating point number (i.e. could be generated by time.time()),
            that value is used for the <name>_time column in the database.
        """
        c = self.cursor
        n = self.name
        a = self.db.arg
        c.execute('select %s_value from %ss where %s_key=%s' %
                  (n, n, n, a), (infoid,))
        res = c.fetchone()

        timestamp = time.time()
        if res:
            values = eval(res[0])
        else:
            values = {}

        if '__timestamp' in newvalues:
            try:
                # __timestamp must be representable as a float. Check it.
                timestamp = float(newvalues['__timestamp'])
            except ValueError:
                if res:
                    # keep the original timestamp
                    del(newvalues['__timestamp'])
                else:
                    # here timestamp is the new timestamp
                    newvalues['__timestamp'] = timestamp
        values.update(newvalues)
        if res:
            sql = ('update %ss set %s_value=%s, %s_time=%s '
                   'where %s_key=%s' % (n, n, a, n, a, n, a))
            args = (repr(values), timestamp, infoid)
        else:
            sql = 'insert into %ss (%s_key, %s_time, %s_value) '\
                'values (%s, %s, %s)' % (n, n, n, n, a, a, a)
            args = (infoid, timestamp, repr(values))
        c.execute(sql, args)

    def list(self):
        c = self.cursor
        n = self.name
        c.execute('select %s_key from %ss' % (n, n))
        return [res[0] for res in c.fetchall()]

    def destroy(self, infoid):
        self.cursor.execute('delete from %ss where %s_key=%s' %
                            (self.name, self.name, self.db.arg), (infoid,))

    def updateTimestamp(self, infoid):
        """ don't update every hit - once a minute should be OK """
        now = time.time()
        self.cursor.execute('''update %ss set %s_time=%s where %s_key=%s '''
            '''and %s_time < %s''' %
                            (self.name, self.name, self.db.arg, self.name,
                             self.db.arg, self.name, self.db.arg),
                            (now, infoid, now-60))

    def clean(self):
        ''' Remove session records that haven't been used for a week. '''
        now = time.time()
        week = 60*60*24*7
        old = now - week
        self.cursor.execute('delete from %ss where %s_time < %s' %
                            (self.name, self.name, self.db.arg), (old, ))

    def commit(self):
        self.log_info('commit %s' % self.name)
        self.conn.commit()
        self.cursor = self.conn.cursor()

    def lifetime(self, item_lifetime=0):
        """Return the proper timestamp for a key with key_lifetime specified
           in seconds. Default lifetime is 0.
        """
        now = time.time()
        week = 60*60*24*7
        return now - week + item_lifetime

    def close(self):
        self.conn.close()


class Sessions(BasicDatabase):
    name = 'session'


class OneTimeKeys(BasicDatabase):
    name = 'otk'

# vim: set et sts=4 sw=4 :
