Commit | Line | Data |
---|---|---|
b409a338 FT |
1 | import threading, time, pickle, random, os |
2 | import cookie | |
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 FT |
112 | sessid = cookie.get(req, self.cookiename) |
113 | with self.lock: | |
114 | if self.cthread is None: | |
115 | self.cthread = threading.Thread(target = self.cleanloop) | |
116 | self.cthread.setDaemon(True) | |
117 | self.cthread.start() | |
118 | try: | |
119 | if sessid is None: | |
120 | raise KeyError() | |
121 | elif sessid in self.live: | |
122 | sess = self.live[sessid] | |
123 | else: | |
124 | sess = self.thaw(sessid) | |
125 | self.live[sessid] = sess | |
126 | if sess.atime + sess.expire < now: | |
127 | raise KeyError() | |
128 | sess.atime = now | |
129 | except KeyError: | |
130 | sess = session() | |
131 | self.live[sess.id] = sess | |
13b37ed3 | 132 | sess.new = True |
b409a338 FT |
133 | req.oncommit(self.ckfreeze) |
134 | return sess | |
135 | ||
b409a338 FT |
136 | def ckfreeze(self, req): |
137 | sess = req.item(self.fetch) | |
138 | if sess.dirty(): | |
139 | try: | |
13b37ed3 FT |
140 | if getattr(sess, "new", False): |
141 | cookie.add(req, self.cookiename, sess.id, self.path) | |
142 | del sess.new | |
b409a338 FT |
143 | self.freeze(sess) |
144 | except: | |
145 | pass | |
146 | ||
147 | def thaw(self, sessid): | |
148 | raise KeyError() | |
149 | ||
150 | def freeze(self, sess): | |
151 | raise TypeError() | |
152 | ||
153 | class backeddb(db): | |
154 | def __init__(self, backdb, *args, **kw): | |
155 | super(backeddb, self).__init__(*args, **kw) | |
156 | self.backdb = backdb | |
157 | ||
158 | def thaw(self, sessid): | |
159 | data = self.backdb[sessid] | |
160 | try: | |
161 | return pickle.loads(data) | |
162 | except Exception, e: | |
163 | raise KeyError() | |
164 | ||
165 | def freeze(self, sess): | |
166 | self.backdb[sess.id] = pickle.dumps(sess) | |
167 | sess.frozen() | |
168 | ||
169 | class dirback(object): | |
170 | def __init__(self, path): | |
171 | self.path = path | |
172 | ||
173 | def __getitem__(self, key): | |
174 | try: | |
175 | with open(os.path.join(self.path, key)) as inf: | |
176 | return inf.read() | |
177 | except IOError: | |
178 | raise KeyError(key) | |
179 | ||
180 | def __setitem__(self, key, value): | |
181 | if not os.path.exists(self.path): | |
182 | os.makedirs(self.path) | |
183 | with open(os.path.join(self.path, key), "w") as out: | |
184 | out.write(value) | |
185 | ||
186 | default = backeddb(dirback(os.path.join("/tmp", "wrwsess-" + str(os.getuid())))) | |
187 | ||
188 | def get(req): | |
189 | return req.item(default.fetch) |