|  | @@ -1,4 +1,5 @@
 | 
	
		
			
				|  |  |  from ConfigParser import RawConfigParser
 | 
	
		
			
				|  |  | +import errno
 | 
	
		
			
				|  |  |  import fcntl
 | 
	
		
			
				|  |  |  import os
 | 
	
		
			
				|  |  |  import shutil
 | 
	
	
		
			
				|  | @@ -45,7 +46,6 @@ class Store(object):
 | 
	
		
			
				|  |  |              fd.write('This is a DARC store')
 | 
	
		
			
				|  |  |          os.mkdir(os.path.join(path, 'bands'))
 | 
	
		
			
				|  |  |          os.mkdir(os.path.join(path, 'indexes'))
 | 
	
		
			
				|  |  | -        BandIndex.create(os.path.join(path, 'indexes', 'bands'))
 | 
	
		
			
				|  |  |          config = RawConfigParser()
 | 
	
		
			
				|  |  |          config.add_section('store')
 | 
	
		
			
				|  |  |          config.set('store', 'version', '1')
 | 
	
	
		
			
				|  | @@ -75,18 +75,18 @@ class Store(object):
 | 
	
		
			
				|  |  |          max_band_size = self.config.getint('store', 'max_band_size')
 | 
	
		
			
				|  |  |          bands_per_dir = self.config.getint('store', 'bands_per_dir')
 | 
	
		
			
				|  |  |          self.io = BandIO(self.path, next_band, max_band_size, bands_per_dir)
 | 
	
		
			
				|  |  | +        self.io.cleanup()
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      def delete_bands(self):
 | 
	
		
			
				|  |  |          delete_path = os.path.join(self.path, 'indexes', 'delete')
 | 
	
		
			
				|  |  |          if os.path.exists(delete_path):
 | 
	
		
			
				|  |  | +            bands = self.get_index('bands')
 | 
	
		
			
				|  |  |              for band in read_set(delete_path):
 | 
	
		
			
				|  |  | -                assert self.bands.pop(band) == 0
 | 
	
		
			
				|  |  | -                self.io.delete_band(band)
 | 
	
		
			
				|  |  | +                assert bands.pop(band, 0) == 0
 | 
	
		
			
				|  |  | +                self.io.delete_band(band, missing_ok=True)
 | 
	
		
			
				|  |  |              os.unlink(delete_path)
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      def begin_txn(self):
 | 
	
		
			
				|  |  | -        self.io.cleanup()
 | 
	
		
			
				|  |  | -        self.delete_bands()
 | 
	
		
			
				|  |  |          txn_dir = os.path.join(self.path, 'txn.tmp')
 | 
	
		
			
				|  |  |          # Initialize transaction snapshot
 | 
	
		
			
				|  |  |          os.mkdir(txn_dir)
 | 
	
	
		
			
				|  | @@ -97,7 +97,6 @@ class Store(object):
 | 
	
		
			
				|  |  |                    os.path.join(self.path, 'txn.active'))
 | 
	
		
			
				|  |  |          self.compact = set()
 | 
	
		
			
				|  |  |          self.txn_active = True
 | 
	
		
			
				|  |  | -        self.bands = BandIndex(os.path.join(self.path, 'indexes', 'bands'))
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      def close(self):
 | 
	
		
			
				|  |  |          self.rollback()
 | 
	
	
		
			
				|  | @@ -115,13 +114,11 @@ class Store(object):
 | 
	
		
			
				|  |  |              self.config.write(fd)
 | 
	
		
			
				|  |  |          for i in self.indexes.values():
 | 
	
		
			
				|  |  |              i.flush()
 | 
	
		
			
				|  |  | -        self.bands.flush()
 | 
	
		
			
				|  |  | +        # If we crash before this line, the transaction will be
 | 
	
		
			
				|  |  | +        # rolled back by open()
 | 
	
		
			
				|  |  |          os.rename(os.path.join(self.path, 'txn.active'),
 | 
	
		
			
				|  |  | -                  os.path.join(self.path, 'txn.tmp'))
 | 
	
		
			
				|  |  | -        shutil.rmtree(os.path.join(self.path, 'txn.tmp'))
 | 
	
		
			
				|  |  | -        self.indexes = {}
 | 
	
		
			
				|  |  | -        self.txn_active = False
 | 
	
		
			
				|  |  | -        self.delete_bands()
 | 
	
		
			
				|  |  | +                  os.path.join(self.path, 'txn.commit'))
 | 
	
		
			
				|  |  | +        self.rollback()
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      def compact_bands(self):
 | 
	
		
			
				|  |  |          """Compact sparse bands by copying data into new bands
 | 
	
	
		
			
				|  | @@ -131,22 +128,25 @@ class Store(object):
 | 
	
		
			
				|  |  |          self.io.close_band()
 | 
	
		
			
				|  |  |          def lookup(ns, key):
 | 
	
		
			
				|  |  |              return key in self.get_index(ns)
 | 
	
		
			
				|  |  | +        bands = self.get_index('bands')
 | 
	
		
			
				|  |  |          for band in self.compact:
 | 
	
		
			
				|  |  | -            if self.bands[band] > 0:
 | 
	
		
			
				|  |  | +            if bands[band] > 0:
 | 
	
		
			
				|  |  |                  for ns, key, data in self.io.iter_objects(band, lookup):
 | 
	
		
			
				|  |  |                      new_band, offset = self.io.write(ns, key, data)
 | 
	
		
			
				|  |  |                      self.indexes[ns][key] = new_band, offset
 | 
	
		
			
				|  |  | -                    self.bands[band] -= 1
 | 
	
		
			
				|  |  | -                    self.bands.setdefault(new_band, 0)
 | 
	
		
			
				|  |  | -                    self.bands[new_band] += 1
 | 
	
		
			
				|  |  | +                    bands[band] -= 1
 | 
	
		
			
				|  |  | +                    bands.setdefault(new_band, 0)
 | 
	
		
			
				|  |  | +                    bands[new_band] += 1
 | 
	
		
			
				|  |  |          write_set(self.compact, os.path.join(self.path, 'indexes', 'delete'))
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      def rollback(self):
 | 
	
		
			
				|  |  |          """
 | 
	
		
			
				|  |  |          """
 | 
	
		
			
				|  |  | -        # Remove partial transaction
 | 
	
		
			
				|  |  | -        if os.path.exists(os.path.join(self.path, 'txn.tmp')):
 | 
	
		
			
				|  |  | -            shutil.rmtree(os.path.join(self.path, 'txn.tmp'))
 | 
	
		
			
				|  |  | +        # Commit any half committed transaction
 | 
	
		
			
				|  |  | +        if os.path.exists(os.path.join(self.path, 'txn.commit')):
 | 
	
		
			
				|  |  | +            self.delete_bands()
 | 
	
		
			
				|  |  | +            os.rename(os.path.join(self.path, 'txn.commit'),
 | 
	
		
			
				|  |  | +                      os.path.join(self.path, 'txn.tmp'))
 | 
	
		
			
				|  |  |          # Roll back active transaction
 | 
	
		
			
				|  |  |          txn_dir = os.path.join(self.path, 'txn.active')
 | 
	
		
			
				|  |  |          if os.path.exists(txn_dir):
 | 
	
	
		
			
				|  | @@ -154,7 +154,10 @@ class Store(object):
 | 
	
		
			
				|  |  |              shutil.copytree(os.path.join(txn_dir, 'indexes'),
 | 
	
		
			
				|  |  |                              os.path.join(self.path, 'indexes'))
 | 
	
		
			
				|  |  |              shutil.copy(os.path.join(txn_dir, 'config'), self.path)
 | 
	
		
			
				|  |  | -            shutil.rmtree(txn_dir)
 | 
	
		
			
				|  |  | +            os.rename(txn_dir, os.path.join(self.path, 'txn.tmp'))
 | 
	
		
			
				|  |  | +        # Remove partially removed transaction
 | 
	
		
			
				|  |  | +        if os.path.exists(os.path.join(self.path, 'txn.tmp')):
 | 
	
		
			
				|  |  | +            shutil.rmtree(os.path.join(self.path, 'txn.tmp'))
 | 
	
		
			
				|  |  |          self.indexes = {}
 | 
	
		
			
				|  |  |          self.txn_active = False
 | 
	
		
			
				|  |  |  
 | 
	
	
		
			
				|  | @@ -162,11 +165,16 @@ class Store(object):
 | 
	
		
			
				|  |  |          try:
 | 
	
		
			
				|  |  |              return self.indexes[ns]
 | 
	
		
			
				|  |  |          except KeyError:
 | 
	
		
			
				|  |  | -            filename = os.path.join(self.path, 'indexes', 'ns%d' % ns)
 | 
	
		
			
				|  |  | +            if ns == 'bands':
 | 
	
		
			
				|  |  | +                filename = os.path.join(self.path, 'indexes', 'bands')
 | 
	
		
			
				|  |  | +                cls = BandIndex
 | 
	
		
			
				|  |  | +            else:
 | 
	
		
			
				|  |  | +                filename = os.path.join(self.path, 'indexes', 'ns%d' % ns)
 | 
	
		
			
				|  |  | +                cls = NSIndex
 | 
	
		
			
				|  |  |              if os.path.exists(filename):
 | 
	
		
			
				|  |  | -                self.indexes[ns] = NSIndex(filename)
 | 
	
		
			
				|  |  | +                self.indexes[ns] = cls(filename)
 | 
	
		
			
				|  |  |              else:
 | 
	
		
			
				|  |  | -                self.indexes[ns] = NSIndex.create(filename)
 | 
	
		
			
				|  |  | +                self.indexes[ns] = cls.create(filename)
 | 
	
		
			
				|  |  |              return self.indexes[ns]
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      def get(self, ns, id):
 | 
	
	
		
			
				|  | @@ -180,8 +188,9 @@ class Store(object):
 | 
	
		
			
				|  |  |          if not self.txn_active:
 | 
	
		
			
				|  |  |              self.begin_txn()
 | 
	
		
			
				|  |  |          band, offset = self.io.write(ns, id, data)
 | 
	
		
			
				|  |  | -        self.bands.setdefault(band, 0)
 | 
	
		
			
				|  |  | -        self.bands[band] += 1
 | 
	
		
			
				|  |  | +        bands = self.get_index('bands')
 | 
	
		
			
				|  |  | +        bands.setdefault(band, 0)
 | 
	
		
			
				|  |  | +        bands[band] += 1
 | 
	
		
			
				|  |  |          self.get_index(ns)[id] = band, offset
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      def delete(self, ns, id):
 | 
	
	
		
			
				|  | @@ -189,7 +198,7 @@ class Store(object):
 | 
	
		
			
				|  |  |              self.begin_txn()
 | 
	
		
			
				|  |  |          try:
 | 
	
		
			
				|  |  |              band, offset = self.get_index(ns).pop(id)
 | 
	
		
			
				|  |  | -            self.bands[band] -= 1
 | 
	
		
			
				|  |  | +            self.get_index('bands')[band] -= 1
 | 
	
		
			
				|  |  |              self.compact.add(band)
 | 
	
		
			
				|  |  |          except KeyError:
 | 
	
		
			
				|  |  |              raise self.DoesNotExist
 | 
	
	
		
			
				|  | @@ -242,8 +251,12 @@ class BandIO(object):
 | 
	
		
			
				|  |  |              self.fds[band] = fd
 | 
	
		
			
				|  |  |              return fd
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -    def delete_band(self, band):
 | 
	
		
			
				|  |  | -        os.unlink(self.band_filename(band))
 | 
	
		
			
				|  |  | +    def delete_band(self, band, missing_ok=False):
 | 
	
		
			
				|  |  | +        try:
 | 
	
		
			
				|  |  | +            os.unlink(self.band_filename(band))
 | 
	
		
			
				|  |  | +        except OSError, e:
 | 
	
		
			
				|  |  | +            if not missing_ok or e.errno != errno.ENOENT:
 | 
	
		
			
				|  |  | +                raise
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      def read(self, band, offset):
 | 
	
		
			
				|  |  |          fd = self.get_fd(band)
 |