|  | @@ -45,7 +45,6 @@ class Repository(object):
 | 
	
		
			
				|  |  |      class CheckNeeded(Error):
 | 
	
		
			
				|  |  |          '''Inconsistency detected. Please run "attic check {}"'''
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  |      def __init__(self, path, create=False):
 | 
	
		
			
				|  |  |          self.path = path
 | 
	
		
			
				|  |  |          self.io = None
 | 
	
	
		
			
				|  | @@ -88,6 +87,12 @@ class Repository(object):
 | 
	
		
			
				|  |  |      def get_transaction_id(self):
 | 
	
		
			
				|  |  |          index_transaction_id = self.get_index_transaction_id()
 | 
	
		
			
				|  |  |          segments_transaction_id = self.io.get_segments_transaction_id()
 | 
	
		
			
				|  |  | +        # Attempt to automatically rebuild index if we crashed between commit
 | 
	
		
			
				|  |  | +        # tag write and index save
 | 
	
		
			
				|  |  | +        if (index_transaction_id if index_transaction_id is not None else -1) < (segments_transaction_id if segments_transaction_id is not None else -1):
 | 
	
		
			
				|  |  | +            self.replay_segments(index_transaction_id, segments_transaction_id)
 | 
	
		
			
				|  |  | +            index_transaction_id = self.get_index_transaction_id()
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |          if index_transaction_id != segments_transaction_id:
 | 
	
		
			
				|  |  |              raise self.CheckNeeded(self.path)
 | 
	
		
			
				|  |  |          return index_transaction_id
 | 
	
	
		
			
				|  | @@ -127,14 +132,16 @@ class Repository(object):
 | 
	
		
			
				|  |  |              return {}
 | 
	
		
			
				|  |  |          return NSIndex((os.path.join(self.path, 'index.%d') % transaction_id).encode('utf-8'), readonly=True)
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -    def get_index(self, transaction_id):
 | 
	
		
			
				|  |  | +    def get_index(self, transaction_id, do_cleanup=True):
 | 
	
		
			
				|  |  | +        self._active_txn = True
 | 
	
		
			
				|  |  |          self.lock.upgrade()
 | 
	
		
			
				|  |  |          if transaction_id is None:
 | 
	
		
			
				|  |  |              self.index = NSIndex.create(os.path.join(self.path, 'index.tmp').encode('utf-8'))
 | 
	
		
			
				|  |  |              self.segments = {}
 | 
	
		
			
				|  |  |              self.compact = set()
 | 
	
		
			
				|  |  |          else:
 | 
	
		
			
				|  |  | -            self.io.cleanup(transaction_id)
 | 
	
		
			
				|  |  | +            if do_cleanup:
 | 
	
		
			
				|  |  | +                self.io.cleanup(transaction_id)
 | 
	
		
			
				|  |  |              shutil.copy(os.path.join(self.path, 'index.%d' % transaction_id),
 | 
	
		
			
				|  |  |                          os.path.join(self.path, 'index.tmp'))
 | 
	
		
			
				|  |  |              self.index = NSIndex(os.path.join(self.path, 'index.tmp').encode('utf-8'))
 | 
	
	
		
			
				|  | @@ -161,6 +168,7 @@ class Repository(object):
 | 
	
		
			
				|  |  |              if name.endswith(current):
 | 
	
		
			
				|  |  |                  continue
 | 
	
		
			
				|  |  |              os.unlink(os.path.join(self.path, name))
 | 
	
		
			
				|  |  | +        self.index = None
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      def compact_segments(self):
 | 
	
		
			
				|  |  |          """Compact sparse segments by copying data into new segments
 | 
	
	
		
			
				|  | @@ -186,6 +194,41 @@ class Repository(object):
 | 
	
		
			
				|  |  |              self.io.delete_segment(segment)
 | 
	
		
			
				|  |  |          self.compact = set()
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +    def replay_segments(self, index_transaction_id, segments_transaction_id):
 | 
	
		
			
				|  |  | +        self.get_index(index_transaction_id, do_cleanup=False)
 | 
	
		
			
				|  |  | +        for segment, filename in self.io.segment_iterator():
 | 
	
		
			
				|  |  | +            if index_transaction_id is not None and segment <= index_transaction_id:
 | 
	
		
			
				|  |  | +                continue
 | 
	
		
			
				|  |  | +            if segment > segments_transaction_id:
 | 
	
		
			
				|  |  | +                break
 | 
	
		
			
				|  |  | +            self.segments[segment] = 0
 | 
	
		
			
				|  |  | +            for tag, key, offset in self.io.iter_objects(segment):
 | 
	
		
			
				|  |  | +                if tag == TAG_PUT:
 | 
	
		
			
				|  |  | +                    try:
 | 
	
		
			
				|  |  | +                        s, _ = self.index[key]
 | 
	
		
			
				|  |  | +                        self.compact.add(s)
 | 
	
		
			
				|  |  | +                        self.segments[s] -= 1
 | 
	
		
			
				|  |  | +                    except KeyError:
 | 
	
		
			
				|  |  | +                        pass
 | 
	
		
			
				|  |  | +                    self.index[key] = segment, offset
 | 
	
		
			
				|  |  | +                    self.segments[segment] += 1
 | 
	
		
			
				|  |  | +                elif tag == TAG_DELETE:
 | 
	
		
			
				|  |  | +                    try:
 | 
	
		
			
				|  |  | +                        s, _ = self.index.pop(key)
 | 
	
		
			
				|  |  | +                    except KeyError:
 | 
	
		
			
				|  |  | +                        raise self.CheckNeeded(self.path)
 | 
	
		
			
				|  |  | +                    self.segments[s] -= 1
 | 
	
		
			
				|  |  | +                    self.compact.add(s)
 | 
	
		
			
				|  |  | +                    self.compact.add(segment)
 | 
	
		
			
				|  |  | +                elif tag == TAG_COMMIT:
 | 
	
		
			
				|  |  | +                    continue
 | 
	
		
			
				|  |  | +                else:
 | 
	
		
			
				|  |  | +                    raise self.CheckNeeded(self.path)
 | 
	
		
			
				|  |  | +            if self.segments[segment] == 0:
 | 
	
		
			
				|  |  | +                self.compact.add(segment)
 | 
	
		
			
				|  |  | +        self.write_index()
 | 
	
		
			
				|  |  | +        self.rollback()
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |      def check(self, progress=False, repair=False):
 | 
	
		
			
				|  |  |          """Check repository consistency
 | 
	
		
			
				|  |  |  
 | 
	
	
		
			
				|  | @@ -220,11 +263,6 @@ class Repository(object):
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |          for segment, filename in self.io.segment_iterator():
 | 
	
		
			
				|  |  |              if segment > transaction_id:
 | 
	
		
			
				|  |  | -                if repair:
 | 
	
		
			
				|  |  | -                    report_progress('Deleting uncommitted segment {}'.format(segment), error=True)
 | 
	
		
			
				|  |  | -                    self.io.delete_segment(segment)
 | 
	
		
			
				|  |  | -                else:
 | 
	
		
			
				|  |  | -                    report_progress('Uncommitted segment {} found'.format(segment), error=True)
 | 
	
		
			
				|  |  |                  continue
 | 
	
		
			
				|  |  |              try:
 | 
	
		
			
				|  |  |                  objects = list(self.io.iter_objects(segment))
 | 
	
	
		
			
				|  | @@ -241,7 +279,6 @@ class Repository(object):
 | 
	
		
			
				|  |  |                          s, _ = self.index[key]
 | 
	
		
			
				|  |  |                          self.compact.add(s)
 | 
	
		
			
				|  |  |                          self.segments[s] -= 1
 | 
	
		
			
				|  |  | -                        report_progress('Key found in more than one segment. Segment={}, key={}'.format(segment, hexlify(key)), error=True)
 | 
	
		
			
				|  |  |                      except KeyError:
 | 
	
		
			
				|  |  |                          pass
 | 
	
		
			
				|  |  |                      self.index[key] = segment, offset
 | 
	
	
		
			
				|  | @@ -264,15 +301,19 @@ class Repository(object):
 | 
	
		
			
				|  |  |              self.io.segment = transaction_id + 1
 | 
	
		
			
				|  |  |              self.io.write_commit()
 | 
	
		
			
				|  |  |              self.io.close_segment()
 | 
	
		
			
				|  |  | -        if current_index and len(current_index) != len(self.index):
 | 
	
		
			
				|  |  | -            report_progress('Index object count mismatch. {} != {}'.format(len(current_index), len(self.index)), error=True)
 | 
	
		
			
				|  |  | +        if current_index and not repair:
 | 
	
		
			
				|  |  | +            if len(current_index) != len(self.index) and False:
 | 
	
		
			
				|  |  | +                report_progress('Index object count mismatch. {} != {}'.format(len(current_index), len(self.index)), error=True)
 | 
	
		
			
				|  |  | +            elif current_index:
 | 
	
		
			
				|  |  | +                for key, value in self.index.iteritems():
 | 
	
		
			
				|  |  | +                    if current_index.get(key, (-1, -1)) != value:
 | 
	
		
			
				|  |  | +                        report_progress('Index mismatch for key {}. {} != {}'.format(key, value, current_index.get(key, (-1, -1))), error=True)
 | 
	
		
			
				|  |  |          if not error_found:
 | 
	
		
			
				|  |  |              report_progress('Repository check complete, no problems found.')
 | 
	
		
			
				|  |  |          if repair:
 | 
	
		
			
				|  |  | +            self.compact_segments()
 | 
	
		
			
				|  |  |              self.write_index()
 | 
	
		
			
				|  |  |          else:
 | 
	
		
			
				|  |  | -            # Delete temporary index file
 | 
	
		
			
				|  |  | -            self.index = None
 | 
	
		
			
				|  |  |              os.unlink(os.path.join(self.path, 'index.tmp'))
 | 
	
		
			
				|  |  |          self.rollback()
 | 
	
		
			
				|  |  |          return not error_found or repair
 | 
	
	
		
			
				|  | @@ -309,7 +350,6 @@ class Repository(object):
 | 
	
		
			
				|  |  |      def put(self, id, data, wait=True):
 | 
	
		
			
				|  |  |          if not self._active_txn:
 | 
	
		
			
				|  |  |              self.get_index(self.get_transaction_id())
 | 
	
		
			
				|  |  | -            self._active_txn = True
 | 
	
		
			
				|  |  |          try:
 | 
	
		
			
				|  |  |              segment, _ = self.index[id]
 | 
	
		
			
				|  |  |              self.segments[segment] -= 1
 | 
	
	
		
			
				|  | @@ -327,7 +367,6 @@ class Repository(object):
 | 
	
		
			
				|  |  |      def delete(self, id, wait=True):
 | 
	
		
			
				|  |  |          if not self._active_txn:
 | 
	
		
			
				|  |  |              self.get_index(self.get_transaction_id())
 | 
	
		
			
				|  |  | -            self._active_txn = True
 | 
	
		
			
				|  |  |          try:
 | 
	
		
			
				|  |  |              segment, offset = self.index.pop(id)
 | 
	
		
			
				|  |  |              self.segments[segment] -= 1
 |