Don't commit sessions to db.live before they are dirty.
[wrw.git] / wrw / session.py
CommitLineData
b409a338 1import threading, time, pickle, random, os
241bc38a 2import cookie
b409a338
FT
3
4__all__ = ["db", "get"]
5
6def hexencode(str):
7 ret = ""
8 for byte in str:
9 ret += "%02X" % (ord(byte),)
10 return ret
11
12def gennonce(length):
13 nonce = ""
14 for i in xrange(length):
15 nonce += chr(random.randint(0, 255))
16 return nonce
17
18class session(object):
19 def __init__(self, expire = 86400 * 7):
20 self.id = hexencode(gennonce(16))
21 self.dict = {}
22 self.lock = threading.Lock()
23 self.ctime = self.atime = self.mtime = int(time.time())
24 self.expire = expire
25 self.dctl = set()
26 self.dirtyp = False
27
28 def dirty(self):
29 for d in self.dctl:
30 if d.sessdirty():
31 return True
32 return self.dirtyp
33
34 def frozen(self):
35 for d in self.dctl:
36 d.sessfrozen()
37 self.dirtyp = False
38
39 def __getitem__(self, key):
40 return self.dict[key]
41
42 def get(self, key, default = None):
43 return self.dict.get(key, default)
44
45 def __setitem__(self, key, value):
46 self.dict[key] = value
47 if hasattr(value, "sessdirty"):
48 self.dctl.add(value)
49 else:
50 self.dirtyp = True
51
52 def __delitem__(self, key):
53 old = self.dict.pop(key)
54 if old in self.dctl:
55 self.dctl.remove(old)
56 self.dirtyp = True
57
58 def __contains__(self, key):
59 return key in self.dict
60
61 def __getstate__(self):
62 ret = []
63 for k, v in self.__dict__.items():
64 if k == "lock": continue
65 ret.append((k, v))
66 return ret
67
68 def __setstate__(self, st):
69 for k, v in st:
70 self.__dict__[k] = v
71 self.lock = threading.Lock()
72
73class db(object):
74 def __init__(self, cookiename = "wrwsess", path = "/"):
75 self.live = {}
76 self.cookiename = cookiename
77 self.path = path
78 self.lock = threading.Lock()
b409a338
FT
79 self.cthread = None
80 self.freezetime = 3600
81
82 def clean(self):
83 now = int(time.time())
84 with self.lock:
85 dlist = []
86 for sess in self.live.itervalues():
87 if sess.atime + self.freezetime < now:
88 try:
89 if sess.dirty():
90 self.freeze(sess)
91 except:
92 if sess.atime + sess.expire < now:
93 dlist.append(sess)
94 else:
95 dlist.append(sess)
96 for sess in dlist:
97 del self.live[sess.id]
98
99 def cleanloop(self):
100 try:
188da534 101 while True:
b409a338
FT
102 time.sleep(300)
103 self.clean()
188da534
FT
104 if len(self.live) == 0:
105 break
b409a338
FT
106 finally:
107 with self.lock:
108 self.cthread = None
109
110 def fetch(self, req):
111 now = int(time.time())
b409a338 112 sessid = cookie.get(req, self.cookiename)
e70341b2 113 new = False
b409a338
FT
114 with self.lock:
115 if self.cthread is None:
116 self.cthread = threading.Thread(target = self.cleanloop)
117 self.cthread.setDaemon(True)
118 self.cthread.start()
119 try:
120 if sessid is None:
121 raise KeyError()
122 elif sessid in self.live:
123 sess = self.live[sessid]
124 else:
125 sess = self.thaw(sessid)
126 self.live[sessid] = sess
127 if sess.atime + sess.expire < now:
128 raise KeyError()
129 sess.atime = now
130 except KeyError:
131 sess = session()
e70341b2
FT
132 new = True
133
134 def ckfreeze(req):
135 if sess.dirty():
bce33109
FT
136 if new:
137 cookie.add(req, self.cookiename, sess.id, self.path)
138 with self.lock:
139 self.live[sess.id] = sess
e70341b2 140 try:
e70341b2
FT
141 self.freeze(sess)
142 except:
143 pass
144 req.oncommit(ckfreeze)
b409a338
FT
145 return sess
146
b409a338
FT
147 def thaw(self, sessid):
148 raise KeyError()
149
150 def freeze(self, sess):
151 raise TypeError()
152
dfad24d0
FT
153 def get(self, req):
154 return req.item(self.fetch)
155
b409a338
FT
156class backeddb(db):
157 def __init__(self, backdb, *args, **kw):
158 super(backeddb, self).__init__(*args, **kw)
159 self.backdb = backdb
160
161 def thaw(self, sessid):
162 data = self.backdb[sessid]
163 try:
164 return pickle.loads(data)
165 except Exception, e:
166 raise KeyError()
167
168 def freeze(self, sess):
169 self.backdb[sess.id] = pickle.dumps(sess)
170 sess.frozen()
171
172class dirback(object):
173 def __init__(self, path):
174 self.path = path
175
176 def __getitem__(self, key):
177 try:
178 with open(os.path.join(self.path, key)) as inf:
179 return inf.read()
180 except IOError:
181 raise KeyError(key)
182
183 def __setitem__(self, key, value):
184 if not os.path.exists(self.path):
185 os.makedirs(self.path)
186 with open(os.path.join(self.path, key), "w") as out:
187 out.write(value)
188
189default = backeddb(dirback(os.path.join("/tmp", "wrwsess-" + str(os.getuid()))))
190
191def get(req):
dfad24d0 192 return default.get(req)