Merge branch 'master' into python3
[wrw.git] / wrw / session.py
CommitLineData
b409a338 1import threading, time, pickle, random, os
3614ca83 2from . import cookie, env
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 = ""
c33f2d6c 14 for i in range(length):
b409a338
FT
15 nonce += chr(random.randint(0, 255))
16 return nonce
17
18class session(object):
9bc70dab 19 def __init__(self, lock, expire=86400 * 7):
b409a338
FT
20 self.id = hexencode(gennonce(16))
21 self.dict = {}
b65f311b 22 self.lock = lock
b409a338
FT
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
9bc70dab 42 def get(self, key, default=None):
b409a338
FT
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
b65f311b 71 # The proper lock is set by the thawer
b409a338 72
b9e22c33
FT
73 def __repr__(self):
74 return "<session %s>" % self.id
b409a338
FT
75
76class db(object):
9bc70dab 77 def __init__(self, backdb=None, cookiename="wrwsess", path="/"):
b409a338
FT
78 self.live = {}
79 self.cookiename = cookiename
80 self.path = path
81 self.lock = threading.Lock()
b409a338
FT
82 self.cthread = None
83 self.freezetime = 3600
f84a3f10 84 self.backdb = backdb
b409a338
FT
85
86 def clean(self):
87 now = int(time.time())
88 with self.lock:
ab92e396 89 clist = list(self.live.keys())
b65f311b
FT
90 for sessid in clist:
91 with self.lock:
92 try:
93 entry = self.live[sessid]
94 except KeyError:
95 continue
96 with entry[0]:
97 rm = False
98 if entry[1] == "retired":
99 pass
100 elif entry[1] is None:
101 pass
102 else:
103 sess = entry[1]
104 if sess.atime + self.freezetime < now:
105 try:
106 if sess.dirty():
107 self.freeze(sess)
108 except:
109 if sess.atime + sess.expire < now:
110 rm = True
111 else:
112 rm = True
113 if rm:
114 entry[1] = "retired"
115 with self.lock:
116 del self.live[sessid]
b409a338
FT
117
118 def cleanloop(self):
119 try:
188da534 120 while True:
b409a338
FT
121 time.sleep(300)
122 self.clean()
188da534
FT
123 if len(self.live) == 0:
124 break
b409a338
FT
125 finally:
126 with self.lock:
127 self.cthread = None
128
b65f311b
FT
129 def _fetch(self, sessid):
130 while True:
131 now = int(time.time())
132 with self.lock:
133 if sessid in self.live:
134 entry = self.live[sessid]
135 else:
136 entry = self.live[sessid] = [threading.RLock(), None]
137 with entry[0]:
138 if isinstance(entry[1], session):
139 entry[1].atime = now
140 return entry[1]
141 elif entry[1] == "retired":
142 continue
143 elif entry[1] is None:
144 try:
145 thawed = self.thaw(sessid)
146 if thawed.atime + thawed.expire < now:
147 raise KeyError()
148 thawed.lock = entry[0]
149 thawed.atime = now
150 entry[1] = thawed
151 return thawed
152 finally:
153 if entry[1] is None:
154 entry[1] = "retired"
155 with self.lock:
156 del self.live[sessid]
157 else:
158 raise Exception("Illegal session entry: " + repr(entry[1]))
159
dc7155d6 160 def checkclean(self):
b409a338
FT
161 with self.lock:
162 if self.cthread is None:
163 self.cthread = threading.Thread(target = self.cleanloop)
164 self.cthread.setDaemon(True)
165 self.cthread.start()
dc7155d6 166
afd93253
FT
167 def mksession(self, req):
168 return session(threading.RLock())
169
170 def mkcookie(self, req, sess):
c6e56d74
FT
171 cookie.add(req, self.cookiename, sess.id,
172 path=self.path,
173 expires=cookie.cdate(time.time() + sess.expire))
afd93253 174
dc7155d6
FT
175 def fetch(self, req):
176 now = int(time.time())
177 sessid = cookie.get(req, self.cookiename)
178 new = False
b65f311b
FT
179 try:
180 if sessid is None:
181 raise KeyError()
182 sess = self._fetch(sessid)
183 except KeyError:
afd93253 184 sess = self.mksession(req)
b65f311b 185 new = True
e70341b2
FT
186
187 def ckfreeze(req):
188 if sess.dirty():
bce33109 189 if new:
afd93253 190 self.mkcookie(req, sess)
bce33109 191 with self.lock:
b65f311b 192 self.live[sess.id] = [sess.lock, sess]
e70341b2 193 try:
e70341b2
FT
194 self.freeze(sess)
195 except:
196 pass
dc7155d6 197 self.checkclean()
e70341b2 198 req.oncommit(ckfreeze)
b409a338
FT
199 return sess
200
b409a338 201 def thaw(self, sessid):
f84a3f10
FT
202 if self.backdb is None:
203 raise KeyError()
b409a338
FT
204 data = self.backdb[sessid]
205 try:
206 return pickle.loads(data)
c33f2d6c 207 except:
b409a338
FT
208 raise KeyError()
209
210 def freeze(self, sess):
f84a3f10
FT
211 if self.backdb is None:
212 raise TypeError()
b65f311b
FT
213 with sess.lock:
214 data = pickle.dumps(sess, -1)
215 self.backdb[sess.id] = data
b409a338
FT
216 sess.frozen()
217
f84a3f10
FT
218 def get(self, req):
219 return req.item(self.fetch)
220
b409a338
FT
221class dirback(object):
222 def __init__(self, path):
223 self.path = path
224
225 def __getitem__(self, key):
226 try:
227 with open(os.path.join(self.path, key)) as inf:
228 return inf.read()
229 except IOError:
230 raise KeyError(key)
231
232 def __setitem__(self, key, value):
233 if not os.path.exists(self.path):
234 os.makedirs(self.path)
235 with open(os.path.join(self.path, key), "w") as out:
236 out.write(value)
237
9bc70dab 238default = env.var(db(backdb=dirback(os.path.join("/tmp", "wrwsess-" + str(os.getuid())))))
b409a338
FT
239
240def get(req):
1f61bf31 241 return default.val.get(req)