reworking
[p2pool.git] / p2pool / util / expiring_dict.py
1 from __future__ import division
2
3 import time
4
5 from twisted.internet import reactor
6
7 class Node(object):
8     def __init__(self, contents, prev=None, next=None):
9         self.contents, self.prev, self.next = contents, prev, next
10     
11     def insert_before(self, contents):
12         self.prev.next = self.prev = node = Node(contents, self.prev, self)
13         return node
14     
15     def insert_after(self, contents):
16         self.next.prev = self.next = node = Node(contents, self, self.next)
17         return node
18     
19     @staticmethod
20     def connect(prev, next):
21         if prev.next is not None or next.prev is not None:
22             raise ValueError('node already connected')
23         prev.next, next.prev = next, prev
24     
25     def replace(self, contents):
26         self.contents = contents
27     
28     def delete(self):
29         if self.prev.next is None or self.next.prev is None:
30             raise ValueError('node not connected')
31         self.prev.next, self.next.prev = self.next, self.prev
32         self.next = self.prev = None
33
34
35 class LinkedList(object):
36     def __init__(self, iterable=[]):
37         self.start, self.end = Node(None), Node(None)
38         Node.connect(self.start, self.end)
39         
40         for item in iterable:
41             self.append(item)
42     
43     def __repr__(self):
44         return 'LinkedList(%r)' % (list(self),)
45     
46     def __len__(self):
47         return sum(1 for x in self)
48     
49     def __iter__(self):
50         cur = self.start.next
51         while cur is not self.end:
52             cur2 = cur
53             cur = cur.next
54             yield cur2 # in case cur is deleted, but items inserted after are ignored
55     
56     def __reversed__(self):
57         cur = self.end.prev
58         while cur is not self.start:
59             cur2 = cur
60             cur = cur.prev
61             yield cur2
62     
63     def __getitem__(self, index):
64         # odd one out - probably should return Node instance instead of its contents
65         if index < 0:
66             cur = self.end
67             for i in xrange(-index):
68                 cur = cur.prev
69                 if cur is self.start:
70                     raise IndexError('index out of range')
71         else:
72             cur = self.start
73             for i in xrange(index + 1):
74                 cur = cur.next
75                 if cur is self.end:
76                     raise IndexError('index out of range')
77         return cur
78     
79     def appendleft(self, item):
80         return self.start.insert_after(item)
81     
82     def append(self, item):
83         return self.end.insert_before(item)
84     
85     def popleft(self):
86         node = self.start.next
87         if node is self.end:
88             raise IndexError('popleft from empty')
89         node.delete()
90         return node.contents
91     
92     def pop(self):
93         node = self.end.prev
94         if node is self.start:
95             raise IndexError('pop from empty')
96         node.delete()
97         return node.contents
98
99
100 class ExpiringDict(object):
101     def __init__(self, expiry_time=100, get_touches=True):
102         self.expiry_time = expiry_time
103         self.get_touches = get_touches
104         
105         self.expiry_deque = LinkedList()
106         self.d = dict() # key -> node, value
107     
108     def __repr__(self):
109         reactor.callLater(0, self.expire)
110         return 'ExpiringDict' + repr(self.__dict__)
111     
112     def __len__(self):
113         reactor.callLater(0, self.expire)
114         return len(self.d)
115     
116     _nothing = object()
117     def touch(self, key, value=_nothing):
118         'Updates expiry node, optionally replacing value, returning new value'
119         if value is self._nothing or key in self.d:
120             node, old_value = self.d[key]
121             node.delete()
122         
123         new_value = old_value if value is self._nothing else value
124         self.d[key] = self.expiry_deque.append((time.time(), key)), new_value
125         return new_value
126     
127     def expire(self):
128         for node in self.expiry_deque:
129             timestamp, key = node.contents
130             if timestamp + self.expiry_time > time.time():
131                 break
132             del self.d[key]
133             node.delete()
134     
135     def __contains__(self, key):
136         return key in self.d
137     
138     def __getitem__(self, key):
139         if self.get_touches:
140             value = self.touch(key)
141         else:
142             node, value = self.d[key]
143         reactor.callLater(0, self.expire)
144         return value
145     
146     def __setitem__(self, key, value):
147         self.touch(key, value)
148         reactor.callLater(0, self.expire)
149     
150     def __delitem__(self, key):
151         node, value = self.d.pop(key)
152         node.delete()
153         reactor.callLater(0, self.expire)
154     
155     def get(self, key, default_value=None):
156         if key in self.d:
157             res = self[key]
158         else:
159             res = default_value
160             reactor.callLater(0, self.expire)
161         return res
162     
163     def setdefault(self, key, default_value):
164         if key in self.d:
165             return self[key]
166         else:
167             self[key] = default_value
168             return default_value
169     
170     def keys(self):
171         return self.d.keys()
172     
173     def values(self):
174         return [value for node, value in self.d.itervalues()]
175     
176     def itervalues(self):
177         for node, value in self.d.itervalues():
178             yield value
179
180 if __name__ == '__main__':
181     x = ExpiringDict(5)
182     print x
183     
184     time.sleep(1)
185     
186     x[1] = 2
187     print 'x[1] = 2'
188     print x
189     
190     time.sleep(1)
191     
192     x[1] = 3
193     print 'x[1] = 3'
194     print x
195     
196     time.sleep(5)
197     
198     print x