Create database upgrade framework
[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_COLUMNS = ['id', 'data', 'expiration_time']
14
15
16 class SessionStore(Store):
17     def _initialize_schema(self):
18         q = self._query(self._db, 'sessions', SESSION_COLUMNS,
19                         trans=False)
20         q.create()
21
22     def _upgrade_schema(self, old_version):
23         raise NotImplementedError()
24
25
26 class SqlSession(Session):
27
28     dburi = None
29     _db = None
30     _store = None
31     _proto = 2
32     locks = {}
33
34     @classmethod
35     def setup(cls, **kwargs):
36         """Initialization from cherrypy"""
37
38         for k, v in kwargs.items():
39             if k == 'storage_dburi':
40                 cls.dburi = v
41
42         cls._store = SessionStore(database_url=cls.dburi)
43         # pylint: disable=protected-access
44         cls._db = cls._store._db
45
46     def _exists(self):
47         q = SqlQuery(self._db, 'sessions', SESSION_COLUMNS)
48         result = q.select({'id': self.id})
49         return True if result.fetchone() else False
50
51     def _load(self):
52         q = SqlQuery(self._db, 'sessions', SESSION_COLUMNS)
53         result = q.select({'id': self.id})
54         r = result.fetchone()
55         if r:
56             data = str(base64.b64decode(r[1]))
57             return pickle.loads(data)
58
59     def _save(self, expiration_time):
60         q = None
61         try:
62             q = SqlQuery(self._db, 'sessions', SESSION_COLUMNS, trans=True)
63             q.delete({'id': self.id})
64             data = pickle.dumps((self._data, expiration_time), self._proto)
65             q.insert((self.id, base64.b64encode(data), expiration_time))
66             q.commit()
67         except Exception:  # pylint: disable=broad-except
68             if q:
69                 q.rollback()
70             raise
71
72     def _delete(self):
73         q = SqlQuery(self._db, 'sessions', SESSION_COLUMNS)
74         q.delete({'id': self.id})
75
76     # copy what RamSession does for now
77     def acquire_lock(self):
78         """Acquire an exclusive lock on the currently-loaded session data."""
79         self.locked = True
80         self.locks.setdefault(self.id, threading.RLock()).acquire()
81
82     def release_lock(self):
83         """Release the lock on the currently-loaded session data."""
84         self.locks[self.id].release()
85         self.locked = False