Commit | Line | Data |
---|---|---|
b409a338 | 1 | import threading, time, pickle, random, os |
241bc38a | 2 | import cookie |
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): | |
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 |
156 | class 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 | ||
172 | class 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 | ||
189 | default = backeddb(dirback(os.path.join("/tmp", "wrwsess-" + str(os.getuid())))) | |
190 | ||
191 | def get(req): | |
dfad24d0 | 192 | return default.get(req) |