Imported Upstream version 12.1.0
[contrib/python-twisted.git] / twisted / names / cache.py
1 # -*- test-case-name: twisted.names.test -*-
2 # Copyright (c) Twisted Matrix Laboratories.
3 # See LICENSE for details.
4
5 from zope.interface import implements
6
7 from twisted.names import dns, common
8 from twisted.python import failure, log
9 from twisted.internet import interfaces, defer
10
11
12
13 class CacheResolver(common.ResolverBase):
14     """
15     A resolver that serves records from a local, memory cache.
16
17     @ivar _reactor: A provider of L{interfaces.IReactorTime}.
18     """
19
20     implements(interfaces.IResolver)
21
22     cache = None
23
24     def __init__(self, cache=None, verbose=0, reactor=None):
25         common.ResolverBase.__init__(self)
26
27         self.cache = {}
28         self.verbose = verbose
29         self.cancel = {}
30         if reactor is None:
31             from twisted.internet import reactor
32         self._reactor = reactor
33
34         if cache:
35             for query, (seconds, payload) in cache.items():
36                 self.cacheResult(query, payload, seconds)
37
38
39     def __setstate__(self, state):
40         self.__dict__ = state
41
42         now = self._reactor.seconds()
43         for (k, (when, (ans, add, ns))) in self.cache.items():
44             diff = now - when
45             for rec in ans + add + ns:
46                 if rec.ttl < diff:
47                     del self.cache[k]
48                     break
49
50
51     def __getstate__(self):
52         for c in self.cancel.values():
53             c.cancel()
54         self.cancel.clear()
55         return self.__dict__
56
57
58     def _lookup(self, name, cls, type, timeout):
59         now = self._reactor.seconds()
60         q = dns.Query(name, type, cls)
61         try:
62             when, (ans, auth, add) = self.cache[q]
63         except KeyError:
64             if self.verbose > 1:
65                 log.msg('Cache miss for ' + repr(name))
66             return defer.fail(failure.Failure(dns.DomainError(name)))
67         else:
68             if self.verbose:
69                 log.msg('Cache hit for ' + repr(name))
70             diff = now - when
71             return defer.succeed((
72                 [dns.RRHeader(str(r.name), r.type, r.cls, max(0, r.ttl - diff), r.payload) for r in ans],
73                 [dns.RRHeader(str(r.name), r.type, r.cls, max(0, r.ttl - diff), r.payload) for r in auth],
74                 [dns.RRHeader(str(r.name), r.type, r.cls, max(0, r.ttl - diff), r.payload) for r in add]
75             ))
76
77
78     def lookupAllRecords(self, name, timeout = None):
79         return defer.fail(failure.Failure(dns.DomainError(name)))
80
81
82     def cacheResult(self, query, payload, cacheTime=None):
83         """
84         Cache a DNS entry.
85
86         @param query: a L{dns.Query} instance.
87
88         @param payload: a 3-tuple of lists of L{dns.RRHeader} records, the
89             matching result of the query (answers, authority and additional).
90
91         @param cacheTime: The time (seconds since epoch) at which the entry is
92             considered to have been added to the cache. If C{None} is given,
93             the current time is used.
94         """
95         if self.verbose > 1:
96             log.msg('Adding %r to cache' % query)
97
98         self.cache[query] = (cacheTime or self._reactor.seconds(), payload)
99
100         if self.cancel.has_key(query):
101             self.cancel[query].cancel()
102
103         s = list(payload[0]) + list(payload[1]) + list(payload[2])
104         if s:
105             m = s[0].ttl
106             for r in s:
107                 m = min(m, r.ttl)
108         else:
109             m = 0
110
111         self.cancel[query] = self._reactor.callLater(m, self.clearEntry, query)
112
113
114     def clearEntry(self, query):
115         del self.cache[query]
116         del self.cancel[query]