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