Commit | Line | Data |
---|---|---|
b409a338 | 1 | import threading, time, pickle, random, os |
1f61bf31 | 2 | import cookie, env |
b409a338 FT |
3 | |
4 | __all__ = ["db", "get"] | |
5 | ||
6 | def hexencode(str): | |
7 | ret = "" | |
8 | for byte in str: | |
9 | ret += "%02X" % (ord(byte),) | |
10 | return ret | |
11 | ||
12 | def gennonce(length): | |
13 | nonce = "" | |
14 | for i in xrange(length): | |
15 | nonce += chr(random.randint(0, 255)) | |
16 | return nonce | |
17 | ||
18 | class 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 | ||
73 | class db(object): | |
f84a3f10 | 74 | def __init__(self, backdb = None, cookiename = "wrwsess", path = "/"): |
b409a338 FT |
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 | |
f84a3f10 | 81 | self.backdb = backdb |
b409a338 FT |
82 | |
83 | def clean(self): | |
84 | now = int(time.time()) | |
85 | with self.lock: | |
86 | dlist = [] | |
87 | for sess in self.live.itervalues(): | |
88 | if sess.atime + self.freezetime < now: | |
89 | try: | |
90 | if sess.dirty(): | |
91 | self.freeze(sess) | |
92 | except: | |
93 | if sess.atime + sess.expire < now: | |
94 | dlist.append(sess) | |
95 | else: | |
96 | dlist.append(sess) | |
97 | for sess in dlist: | |
98 | del self.live[sess.id] | |
99 | ||
100 | def cleanloop(self): | |
101 | try: | |
188da534 | 102 | while True: |
b409a338 FT |
103 | time.sleep(300) |
104 | self.clean() | |
188da534 FT |
105 | if len(self.live) == 0: |
106 | break | |
b409a338 FT |
107 | finally: |
108 | with self.lock: | |
109 | self.cthread = None | |
110 | ||
111 | def fetch(self, req): | |
112 | now = int(time.time()) | |
b409a338 | 113 | sessid = cookie.get(req, self.cookiename) |
e70341b2 | 114 | new = False |
b409a338 FT |
115 | with self.lock: |
116 | if self.cthread is None: | |
117 | self.cthread = threading.Thread(target = self.cleanloop) | |
118 | self.cthread.setDaemon(True) | |
119 | self.cthread.start() | |
120 | try: | |
121 | if sessid is None: | |
122 | raise KeyError() | |
123 | elif sessid in self.live: | |
124 | sess = self.live[sessid] | |
125 | else: | |
126 | sess = self.thaw(sessid) | |
127 | self.live[sessid] = sess | |
128 | if sess.atime + sess.expire < now: | |
129 | raise KeyError() | |
130 | sess.atime = now | |
131 | except KeyError: | |
132 | sess = session() | |
e70341b2 FT |
133 | new = True |
134 | ||
135 | def ckfreeze(req): | |
136 | if sess.dirty(): | |
bce33109 FT |
137 | if new: |
138 | cookie.add(req, self.cookiename, sess.id, self.path) | |
139 | with self.lock: | |
140 | self.live[sess.id] = sess | |
e70341b2 | 141 | try: |
e70341b2 FT |
142 | self.freeze(sess) |
143 | except: | |
144 | pass | |
145 | req.oncommit(ckfreeze) | |
b409a338 FT |
146 | return sess |
147 | ||
b409a338 | 148 | def thaw(self, sessid): |
f84a3f10 FT |
149 | if self.backdb is None: |
150 | raise KeyError() | |
b409a338 FT |
151 | data = self.backdb[sessid] |
152 | try: | |
153 | return pickle.loads(data) | |
154 | except Exception, e: | |
155 | raise KeyError() | |
156 | ||
157 | def freeze(self, sess): | |
f84a3f10 FT |
158 | if self.backdb is None: |
159 | raise TypeError() | |
f2e2dc9e | 160 | self.backdb[sess.id] = pickle.dumps(sess, -1) |
b409a338 FT |
161 | sess.frozen() |
162 | ||
f84a3f10 FT |
163 | def get(self, req): |
164 | return req.item(self.fetch) | |
165 | ||
b409a338 FT |
166 | class dirback(object): |
167 | def __init__(self, path): | |
168 | self.path = path | |
169 | ||
170 | def __getitem__(self, key): | |
171 | try: | |
172 | with open(os.path.join(self.path, key)) as inf: | |
173 | return inf.read() | |
174 | except IOError: | |
175 | raise KeyError(key) | |
176 | ||
177 | def __setitem__(self, key, value): | |
178 | if not os.path.exists(self.path): | |
179 | os.makedirs(self.path) | |
180 | with open(os.path.join(self.path, key), "w") as out: | |
181 | out.write(value) | |
182 | ||
1f61bf31 | 183 | default = env.var(db(backdb = dirback(os.path.join("/tmp", "wrwsess-" + str(os.getuid()))))) |
b409a338 FT |
184 | |
185 | def get(req): | |
1f61bf31 | 186 | return default.val.get(req) |