]> git.rkrishnan.org Git - tahoe-lafs/tahoe-lafs.git/blob - src/allmydata/mutable/common.py
mutable WIP: improve logging a bit
[tahoe-lafs/tahoe-lafs.git] / src / allmydata / mutable / common.py
1
2 from allmydata.util import idlib
3
4 MODE_CHECK = "MODE_CHECK" # query all peers
5 MODE_ANYTHING = "MODE_ANYTHING" # one recoverable version
6 MODE_WRITE = "MODE_WRITE" # replace all shares, probably.. not for initial
7                           # creation
8 MODE_READ = "MODE_READ"
9
10 class NotMutableError(Exception):
11     pass
12
13 class NeedMoreDataError(Exception):
14     def __init__(self, needed_bytes, encprivkey_offset, encprivkey_length):
15         Exception.__init__(self)
16         self.needed_bytes = needed_bytes # up through EOF
17         self.encprivkey_offset = encprivkey_offset
18         self.encprivkey_length = encprivkey_length
19     def __str__(self):
20         return "<NeedMoreDataError (%d bytes)>" % self.needed_bytes
21
22 class UncoordinatedWriteError(Exception):
23     def __repr__(self):
24         return "<%s -- You, oh user, tried to change a file or directory at the same time as another process was trying to change it.  To avoid data loss, don't do this.  Please see docs/write_coordination.html for details.>" % (self.__class__.__name__,)
25
26 class UnrecoverableFileError(Exception):
27     pass
28
29 class CorruptShareError(Exception):
30     def __init__(self, peerid, shnum, reason):
31         self.args = (peerid, shnum, reason)
32         self.peerid = peerid
33         self.shnum = shnum
34         self.reason = reason
35     def __str__(self):
36         short_peerid = idlib.nodeid_b2a(self.peerid)[:8]
37         return "<CorruptShareError peerid=%s shnum[%d]: %s" % (short_peerid,
38                                                                self.shnum,
39                                                                self.reason)
40
41
42
43
44
45 class DictOfSets(dict):
46     def add(self, key, value):
47         if key in self:
48             self[key].add(value)
49         else:
50             self[key] = set([value])
51
52     def discard(self, key, value):
53         if not key in self:
54             return
55         self[key].discard(value)
56         if not self[key]:
57             del self[key]
58
59 class ResponseCache:
60     """I cache share data, to reduce the number of round trips used during
61     mutable file operations. All of the data in my cache is for a single
62     storage index, but I will keep information on multiple shares (and
63     multiple versions) for that storage index.
64
65     My cache is indexed by a (verinfo, shnum) tuple.
66
67     My cache entries contain a set of non-overlapping byteranges: (start,
68     data, timestamp) tuples.
69     """
70
71     def __init__(self):
72         self.cache = DictOfSets()
73
74     def _does_overlap(self, x_start, x_length, y_start, y_length):
75         if x_start < y_start:
76             x_start, y_start = y_start, x_start
77             x_length, y_length = y_length, x_length
78         x_end = x_start + x_length
79         y_end = y_start + y_length
80         # this just returns a boolean. Eventually we'll want a form that
81         # returns a range.
82         if not x_length:
83             return False
84         if not y_length:
85             return False
86         if x_start >= y_end:
87             return False
88         if y_start >= x_end:
89             return False
90         return True
91
92
93     def _inside(self, x_start, x_length, y_start, y_length):
94         x_end = x_start + x_length
95         y_end = y_start + y_length
96         if x_start < y_start:
97             return False
98         if x_start >= y_end:
99             return False
100         if x_end < y_start:
101             return False
102         if x_end > y_end:
103             return False
104         return True
105
106     def add(self, verinfo, shnum, offset, data, timestamp):
107         index = (verinfo, shnum)
108         self.cache.add(index, (offset, data, timestamp) )
109
110     def read(self, verinfo, shnum, offset, length):
111         """Try to satisfy a read request from cache.
112         Returns (data, timestamp), or (None, None) if the cache did not hold
113         the requested data.
114         """
115
116         # TODO: join multiple fragments, instead of only returning a hit if
117         # we have a fragment that contains the whole request
118
119         index = (verinfo, shnum)
120         end = offset+length
121         for entry in self.cache.get(index, set()):
122             (e_start, e_data, e_timestamp) = entry
123             if self._inside(offset, length, e_start, len(e_data)):
124                 want_start = offset - e_start
125                 want_end = offset+length - e_start
126                 return (e_data[want_start:want_end], e_timestamp)
127         return None, None
128
129