92fb5b302324cd14796ae4b473602182f0781479
[p2pool.git] / p2pool / util / expiring_dict.py
1 from __future__ import division
2
3 import time
4 import weakref
5
6 from twisted.internet import task
7
8 class Node(object):
9     def __init__(self, contents, prev=None, next=None):
10         self.contents, self.prev, self.next = contents, prev, next
11     
12     def insert_before(self, contents):
13         self.prev.next = self.prev = node = Node(contents, self.prev, self)
14         return node
15     
16     def insert_after(self, contents):
17         self.next.prev = self.next = node = Node(contents, self, self.next)
18         return node
19     
20     @staticmethod
21     def connect(prev, next):
22         if prev.next is not None or next.prev is not None:
23             raise ValueError('node already connected')
24         prev.next, next.prev = next, prev
25     
26     def replace(self, contents):
27         self.contents = contents
28     
29     def delete(self):
30         if self.prev.next is None or self.next.prev is None:
31             raise ValueError('node not connected')
32         self.prev.next, self.next.prev = self.next, self.prev
33         self.next = self.prev = None
34
35
36 class LinkedList(object):
37     def __init__(self, iterable=[]):
38         self.start, self.end = Node(None), Node(None)
39         Node.connect(self.start, self.end)
40         
41         for item in iterable:
42             self.append(item)
43     
44     def __repr__(self):
45         return 'LinkedList(%r)' % (list(self),)
46     
47     def __len__(self):
48         return sum(1 for x in self)
49     
50     def __iter__(self):
51         cur = self.start.next
52         while cur is not self.end:
53             cur2 = cur
54             cur = cur.next
55             yield cur2 # in case cur is deleted, but items inserted after are ignored
56     
57     def __reversed__(self):
58         cur = self.end.prev
59         while cur is not self.start:
60             cur2 = cur
61             cur = cur.prev
62             yield cur2
63     
64     def __getitem__(self, index):
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, 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         self_ref = weakref.ref(self, lambda _: expire_loop.stop() if expire_loop.running else None)
109         self._expire_loop = expire_loop = task.LoopingCall(lambda: self_ref().expire())
110         expire_loop.start(1)
111     
112     def stop(self):
113         self._expire_loop.stop()
114     
115     def __repr__(self):
116         return 'ExpiringDict' + repr(self.__dict__)
117     
118     def __len__(self):
119         return len(self.d)
120     
121     _nothing = object()
122     def touch(self, key, value=_nothing):
123         'Updates expiry node, optionally replacing value, returning new value'
124         if value is self._nothing or key in self.d:
125             node, old_value = self.d[key]
126             node.delete()
127         
128         new_value = old_value if value is self._nothing else value
129         self.d[key] = self.expiry_deque.append((time.time() + self.expiry_time, key)), new_value
130         return new_value
131     
132     def expire(self):
133         t = time.time()
134         for node in self.expiry_deque:
135             timestamp, key = node.contents
136             if timestamp > t:
137                 break
138             del self.d[key]
139             node.delete()
140     
141     def __contains__(self, key):
142         return key in self.d
143     
144     def __getitem__(self, key):
145         if self.get_touches:
146             value = self.touch(key)
147         else:
148             node, value = self.d[key]
149         return value
150     
151     def __setitem__(self, key, value):
152         self.touch(key, value)
153     
154     def __delitem__(self, key):
155         node, value = self.d.pop(key)
156         node.delete()
157     
158     def get(self, key, default_value=None):
159         if key in self.d:
160             res = self[key]
161         else:
162             res = default_value
163         return res
164     
165     def setdefault(self, key, default_value):
166         if key in self.d:
167             return self[key]
168         else:
169             self[key] = default_value
170             return default_value
171     
172     def keys(self):
173         return self.d.keys()
174     
175     def values(self):
176         return [value for node, value in self.d.itervalues()]
177     
178     def itervalues(self):
179         for node, value in self.d.itervalues():
180             yield value