Implement database upgrade for indexes
[cascardo/ipsilon.git] / ipsilon / util / sessions.py
1 # Copyright (C) 2014 Ipsilon project Contributors, for license see COPYING
2
3 import base64
4 from cherrypy.lib.sessions import Session
5 from ipsilon.util.data import Store, SqlQuery
6 import threading
7 try:
8     import cPickle as pickle
9 except ImportError:
10     import pickle
11
12
13 SESSION_TABLE = {'columns': ['id', 'data', 'expiration_time'],
14                  'primary_key': ('id', ),
15                  'indexes': [('expiration_time',)]
16                  }
17
18
19 class SessionStore(Store):
20     def _initialize_schema(self):
21         q = self._query(self._db, 'sessions', SESSION_TABLE,
22                         trans=False)
23         q.create()
24
25     def _upgrade_schema(self, old_version):
26         if old_version == 1:
27             # In schema version 2, we added indexes and primary keys
28             # pylint: disable=protected-access
29             table = self._query(self._db, 'sessions', SESSION_TABLE,
30                                 trans=False)._table
31             self._db.add_constraint(table.primary_key)
32             for index in table.indexes:
33                 self._db.add_index(index)
34             return 2
35         else:
36             raise NotImplementedError()
37
38
39 class SqlSession(Session):
40
41     dburi = None
42     _db = None
43     _store = None
44     _proto = 2
45     locks = {}
46
47     @classmethod
48     def setup(cls, **kwargs):
49         """Initialization from cherrypy"""
50
51         for k, v in kwargs.items():
52             if k == 'storage_dburi':
53                 cls.dburi = v
54
55         cls._store = SessionStore(database_url=cls.dburi)
56         # pylint: disable=protected-access
57         cls._db = cls._store._db
58
59     def _exists(self):
60         q = SqlQuery(self._db, 'sessions', SESSION_TABLE)
61         result = q.select({'id': self.id})
62         return True if result.fetchone() else False
63
64     def _load(self):
65         q = SqlQuery(self._db, 'sessions', SESSION_TABLE)
66         result = q.select({'id': self.id})
67         r = result.fetchone()
68         if r:
69             data = str(base64.b64decode(r[1]))
70             return pickle.loads(data)
71
72     def _save(self, expiration_time):
73         q = None
74         try:
75             q = SqlQuery(self._db, 'sessions', SESSION_TABLE, trans=True)
76             q.delete({'id': self.id})
77             data = pickle.dumps((self._data, expiration_time), self._proto)
78             q.insert((self.id, base64.b64encode(data), expiration_time))
79             q.commit()
80         except Exception:  # pylint: disable=broad-except
81             if q:
82                 q.rollback()
83             raise
84
85     def _delete(self):
86         q = SqlQuery(self._db, 'sessions', SESSION_TABLE)
87         q.delete({'id': self.id})
88
89     # copy what RamSession does for now
90     def acquire_lock(self):
91         """Acquire an exclusive lock on the currently-loaded session data."""
92         self.locked = True
93         self.locks.setdefault(self.id, threading.RLock()).acquire()
94
95     def release_lock(self):
96         """Release the lock on the currently-loaded session data."""
97         self.locks[self.id].release()
98         self.locked = False