1 import threading, time, pickle, random, os, io
2 from . import cookie, env, proto
4 __all__ = ["db", "get"]
7 return os.urandom(length)
9 class itempickler(pickle.Pickler):
10 def persistent_id(self, obj):
11 if isinstance(obj, session):
12 return ("session", obj.id)
15 class itemunpickler(pickle.Unpickler):
16 def __init__(self, *args, session, **kwargs):
17 super().__init__(*args, **kwargs)
18 self.session = session
20 def persistent_load(self, oid):
23 if oid[1] == self.session.id:
25 raise pickle.UnpicklingError("unexpected persistent id: " + repr(oid))
27 class session(object):
28 def __init__(self, lock, expire=86400 * 7):
29 self.id = proto.enhex(gennonce(16))
32 self.ctime = self.atime = self.mtime = int(time.time())
48 def __getitem__(self, key):
51 def get(self, key, default=None):
52 return self.dict.get(key, default)
54 def __setitem__(self, key, value):
55 self.dict[key] = value
56 if hasattr(value, "sessdirty"):
61 def __delitem__(self, key):
62 old = self.dict.pop(key)
67 def __contains__(self, key):
68 return key in self.dict
70 def __getstate__(self):
72 for k, v in self.dict.items():
74 itempickler(buf).dump((k, v))
75 items.append(buf.getvalue())
77 for k in ["id", "ctime", "mtime", "atime", "expire"]:
78 ret[k] = getattr(self, k)
81 def __setstate__(self, st):
82 if isinstance(st, list):
83 # Only for the old session format; remove me in due time.
90 for k in ["id", "ctime", "mtime", "atime", "expire"]:
91 setattr(self, k, st[k])
92 for item in st["data"]:
94 k, v = itemunpickler(io.BytesIO(item), session=self).load()
98 if hasattr(v, "sessdirty"):
100 # The proper lock is set by the thawer
103 return "<session %s>" % self.id
106 def __init__(self, backdb=None, cookiename="wrwsess", path="/"):
108 self.cookiename = cookiename
110 self.lock = threading.Lock()
112 self.freezetime = 3600
116 now = int(time.time())
118 clist = list(self.live.keys())
122 entry = self.live[sessid]
127 if entry[1] == "retired":
129 elif entry[1] is None:
133 if sess.atime + self.freezetime < now:
138 if sess.atime + sess.expire < now:
145 del self.live[sessid]
152 if len(self.live) == 0:
158 def _fetch(self, sessid):
160 now = int(time.time())
162 if sessid in self.live:
163 entry = self.live[sessid]
165 entry = self.live[sessid] = [threading.RLock(), None]
167 if isinstance(entry[1], session):
170 elif entry[1] == "retired":
172 elif entry[1] is None:
174 thawed = self.thaw(sessid)
175 if thawed.atime + thawed.expire < now:
177 thawed.lock = entry[0]
185 del self.live[sessid]
187 raise Exception("Illegal session entry: " + repr(entry[1]))
189 def checkclean(self):
191 if self.cthread is None:
192 self.cthread = threading.Thread(target = self.cleanloop)
193 self.cthread.setDaemon(True)
196 def mksession(self, req):
197 return session(threading.RLock())
199 def mkcookie(self, req, sess):
200 cookie.add(req, self.cookiename, sess.id,
202 expires=cookie.cdate(time.time() + sess.expire))
204 def fetch(self, req):
205 now = int(time.time())
206 sessid = cookie.get(req, self.cookiename)
211 sess = self._fetch(sessid)
213 sess = self.mksession(req)
219 self.mkcookie(req, sess)
221 self.live[sess.id] = [sess.lock, sess]
227 req.oncommit(ckfreeze)
230 def thaw(self, sessid):
231 if self.backdb is None:
233 data = self.backdb[sessid]
235 return pickle.loads(data)
239 def freeze(self, sess):
240 if self.backdb is None:
243 data = pickle.dumps(sess, -1)
244 self.backdb[sess.id] = data
248 return req.item(self.fetch)
250 class dirback(object):
251 def __init__(self, path):
254 def __getitem__(self, key):
256 with open(os.path.join(self.path, key)) as inf:
261 def __setitem__(self, key, value):
262 if not os.path.exists(self.path):
263 os.makedirs(self.path)
264 with open(os.path.join(self.path, key), "w") as out:
267 default = env.var(db(backdb=dirback(os.path.join("/tmp", "wrwsess-" + str(os.getuid())))))
270 return default.val.get(req)