| 
				
				
					
				
				
				 | 
			
			 | 
			@@ -44,6 +44,18 @@ _NAMESPACE_MEDASHARE_CONTAINER = uuid.UUID('890a9d5c-0626-4de1-ab05-9e14947391eb | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
			# useful for debugging when stderr is redirected/captured | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
			_real_stderr = sys.stderr | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
			
  | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
			def _debprint(*args): # pragma: no cover | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
				import traceback, sys, os.path | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
				st = traceback.extract_stack(limit=2)[0] | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
			
  | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
				sep = '' | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
				if args: | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
					sep = ':' | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
			
  | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
				print('%s:%d%s' % (os.path.basename(st.filename), st.lineno, sep), | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
				    *args, file=_real_stderr) | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
				sys.stderr.flush() | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
			
  | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
			_defaulthash = 'sha512' | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
			_validhashes = set([ 'sha256', 'sha512' ]) | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
			_hashlengths = { len(getattr(hashlib, x)().hexdigest()): x for x in | 
		
		
	
	
		
			
				| 
				
					
				
				
					
				
				
				 | 
			
			 | 
			@@ -333,6 +345,11 @@ class Persona(object): | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
			
  | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
					return self.sign(Host(*args, **kwargs)) | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
			
  | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
				def Mapping(self, *args, **kwargs): | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
					kwargs['created_by_ref'] = self.uuid | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
			
  | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
					return self.sign(Mapping(*args, **kwargs)) | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
			
  | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
				def Container(self, *args, **kwargs): | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
					kwargs['created_by_ref'] = self.uuid | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
			
  | 
		
		
	
	
		
			
				| 
				
					
				
				
					
				
				
				 | 
			
			 | 
			@@ -478,6 +495,7 @@ class ObjectStore(object): | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
					self._uuids = {} | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
					self._hashes = {} | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
					self._hostuuids = {} | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
					self._hostmappings = [] | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
			
  | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
				def get_host(self, hostuuid): | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
					return self._hostuuids[hostuuid] | 
		
		
	
	
		
			
				| 
				
					
				
				
					
				
				
				 | 
			
			 | 
			@@ -569,7 +587,15 @@ class ObjectStore(object): | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
					elif obj.type == 'host': | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
						self._uuids[obj.hostuuid] = obj | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
						self._hostuuids[obj.hostuuid] = obj | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
			
  | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
					elif obj.type == 'mapping': | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
						hostid = _makeuuid(hostuuid()) | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
			
  | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
						maps = [ (lambda a, b: (uuid.UUID(a), pathlib.Path(b).resolve()))(*x.split(':', 1)) for x in obj.mapping ] | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
						for idx, (id, path) in enumerate(maps): | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
							if hostid == id: | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
								# add other to mapping | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
								other = tuple(maps[(idx + 1) % 2]) | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
								self._hostmappings.append((path, ) + other) | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
					try: | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
						hashes = obj.hashes | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
					except AttributeError: | 
		
		
	
	
		
			
				| 
				
					
				
				
					
				
				
				 | 
			
			 | 
			@@ -623,7 +649,7 @@ class ObjectStore(object): | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
					h = self.makehash(hash, strict=False) | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
					return self._hashes[h] | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
			
  | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
				def get_metadata(self, fname, persona): | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
				def get_metadata(self, fname, persona, create_metadata=True): | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
					'''Get all MetaData objects for fname, or create one if | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
					not found. | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
			
  | 
		
		
	
	
		
			
				| 
				
				
				
					
				
				 | 
			
			 | 
			@@ -632,6 +658,9 @@ class ObjectStore(object): | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
					A Persona must be passed in to create the FileObject and | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
					MetaData objects as needed. | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
			
  | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
					A MetaData object will be created if create_metadata is | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
					True, which is the default. | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
			
  | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
					Note: if a new MetaData object is created, it is not | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
					stored in the database automatically.  It is expected that | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
					it will be modified and then saved, so call ObjectStore.loadobj | 
		
		
	
	
		
			
				| 
				
				
				
					
				
				 | 
			
			 | 
			@@ -640,12 +669,8 @@ class ObjectStore(object): | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
			
  | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
					try: | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
						fobj = self.by_file(fname, ('file',))[0] | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
						#print('x:', repr(objs), file=_real_stderr) | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
					except KeyError: | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
						#print('b:', repr(fname), file=_real_stderr) | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
			
  | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
						fobj = persona.by_file(fname) | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
						#print('c:', repr(fobj), file=_real_stderr) | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
			
  | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
						self.loadobj(fobj) | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
			
  | 
		
		
	
	
		
			
				| 
				
				
				
					
				
				 | 
			
			 | 
			@@ -653,13 +678,19 @@ class ObjectStore(object): | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
					try: | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
						objs = self.by_file(fname) | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
					except KeyError: | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
						objs = [ persona.MetaData(hashes=fobj.hashes) ] | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
						if create_metadata: | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
							objs = [ persona.MetaData(hashes=fobj.hashes) ] | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
						else: | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
							objs = [ ] | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
			
  | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
					return objs | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
			
  | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
				def by_file(self, fname, types=('metadata', )): | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
					'''Return a metadata object for the file named fname. | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
			
  | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
					Will check the mapping database to get hashes, and possibly | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
					return that FileObject if requested. | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
			
  | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
					Will raise a KeyError if this file does not exist in | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
					the database. | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
			
  | 
		
		
	
	
		
			
				| 
				
				
				
					
				
				 | 
			
			 | 
			@@ -670,8 +701,26 @@ class ObjectStore(object): | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
					fid = FileObject.make_id(fname) | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
			
  | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
					#print('bf:', repr(fid), file=_real_stderr) | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
					fobj = self.by_id(fid) | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
					fobj.verify() | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
					try: | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
						fobj = self.by_id(fid) | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
						lclfile = None | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
					except KeyError: | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
						# check mappings | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
						fname = pathlib.Path(fname).resolve() | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
						for lclpath, hostid, rempath in self._hostmappings: | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
							if fname.parts[:len(lclpath.parts)] == lclpath.parts: | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
								try: | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
									rempath = pathlib.Path(*rempath.parts + fname.parts[len(lclpath.parts):]) | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
									fid = FileObject.make_id(rempath, hostid) | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
									fobj = self.by_id(fid) | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
									lclfile = fname | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
									break | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
								except KeyError: | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
									continue | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
						else: | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
							raise | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
			
  | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
					fobj.verify(lclfile) | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
			
  | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
					for i in fobj.hashes: | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
						j = self.by_hash(i) | 
		
		
	
	
		
			
				| 
				
					
				
				
					
				
				
				 | 
			
			 | 
			@@ -703,6 +752,9 @@ def _hashfile(fname): | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
			class Host(MDBase): | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
				_type = 'host' | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
			
  | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
			class Mapping(MDBase): | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
				_type = 'mapping' | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
			
  | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
			class FileObject(MDBase): | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
				_type = 'file' | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
			
  | 
		
		
	
	
		
			
				| 
				
				
				
					
				
				 | 
			
			 | 
			@@ -713,14 +765,17 @@ class FileObject(MDBase): | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
				} | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
			
  | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
				@staticmethod | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
				def make_id(fname): | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
				def make_id(fname, hostid=None): | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
					'''Take a local file name, and make the id for it.  Note that | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
					converts from the local path separator to a forward slash so | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
					that it will be the same between Windows and Unix systems.''' | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
			
  | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
					if hostid is None: | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
						hostid = hostuuid() | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
			
  | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
					fname = os.path.realpath(fname) | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
					return uuid.uuid5(_NAMESPACE_MEDASHARE_PATH, | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
					    str(hostuuid()) + '/'.join(os.path.split(fname))) | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
					    str(hostid) + '/'.join(os.path.split(fname))) | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
			
  | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
				@classmethod | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
				def from_file(cls, filename, created_by_ref): | 
		
		
	
	
		
			
				| 
				
				
				
					
				
				 | 
			
			 | 
			@@ -741,13 +796,17 @@ class FileObject(MDBase): | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
			
  | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
					return cls(obj) | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
			
  | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
				def verify(self, complete=False): | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
				def verify(self, lclfile=None): | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
					'''Verify that this FileObject is still valid.  It will | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
					by default, only do a mtime verification. | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
			
  | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
					It will raise a ValueError if the file does not match.''' | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
			
  | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
					s = os.stat(os.path.join(self.dir, self.filename)) | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
					if lclfile is None: | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
						s = os.stat(os.path.join(self.dir, self.filename)) | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
					else: | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
						s = os.stat(lclfile) | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
			
  | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
					mtimets = datetime.datetime.fromtimestamp(s.st_mtime, | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
					    tz=datetime.timezone.utc).timestamp() | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
			
  | 
		
		
	
	
		
			
				| 
				
					
				
				
					
				
				
				 | 
			
			 | 
			@@ -919,6 +978,36 @@ def cmd_modify(options): | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
			def printhost(host): | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
				print('%s\t%s' % (host.name, host.hostuuid)) | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
			
  | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
			def cmd_mapping(options): | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
				persona, objstr = get_objstore(options) | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
			
  | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
				if options.mapping is not None: | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
					parts = [ x.split(':', 1) for x in options.mapping ] | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
			
  | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
					if len(parts[0]) == 1: | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
						parts[0] = [ hostuuid(), parts[0][0] ] | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
			
  | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
					if parts[0][0] == hostuuid(): | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
						parts[0][1] = str(pathlib.Path(parts[0][1]).resolve()) | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
			
  | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
					if parts[1][1][0] != '/': | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
						print('ERROR: host path must be absolute, is %s.' % | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
						    repr(parts[1][1][0]), file=sys.stderr) | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
						sys.exit(1) | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
			
  | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
					try: | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
						[ objstr.get_host(x[0]) for x in parts ] | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
					except KeyError as e: | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
						print('ERROR: Unable to find host %s' % | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
						    repr(e.args[0]), file=sys.stderr) | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
						sys.exit(1) | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
			
  | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
					m = persona.Mapping(mapping=[ ':'.join(x) for x in parts ]) | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
			
  | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
				objstr.loadobj(m) | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
			
  | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
				write_objstore(options, objstr) | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
			
  | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
			def cmd_hosts(options): | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
				persona, objstr = get_objstore(options) | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
			
  | 
		
		
	
	
		
			
				| 
				
					
				
				
					
				
				
				 | 
			
			 | 
			@@ -987,11 +1076,6 @@ def cmd_list(options): | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
						except (FileNotFoundError, KeyError) as e: | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
							print('ERROR: file not found: %s' % repr(i), file=sys.stderr) | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
							sys.exit(1) | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
					except FileNotFoundError: | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
						# XXX - tell the difference? | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
						print('ERROR: file not found: %s' % repr(i), | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
						    file=sys.stderr) | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
						sys.exit(1) | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
			
  | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
					for j in objstr.by_file(i): | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
						for k, v in _iterdictlist(j): | 
		
		
	
	
		
			
				| 
				
					
				
				
					
				
				
				 | 
			
			 | 
			@@ -1149,6 +1233,11 @@ def main(): | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
				parser_hosts = subparsers.add_parser('hosts', help='dump all the hosts, self is always first') | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
				parser_hosts.set_defaults(func=cmd_hosts) | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
			
  | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
				parser_mapping = subparsers.add_parser('mapping', help='list mappings, or create a mapping') | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
				parser_mapping.add_argument('--create', dest='mapping', nargs=2, | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
				    help='mapping to add, host|hostuuid:path host|hostuuid:path') | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
				parser_mapping.set_defaults(func=cmd_mapping) | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
			
  | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
				parser_dump = subparsers.add_parser('dump', help='dump all the objects') | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
				parser_dump.set_defaults(func=cmd_dump) | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
			
  | 
		
		
	
	
		
			
				| 
				
					
				
				
					
				
				
				 | 
			
			 | 
			@@ -1633,6 +1722,13 @@ class _TestCases(unittest.TestCase): | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
						except KeyError: | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
							pass | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
			
  | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
						for i in cmd.get('format', []): | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
							if i in { 'cmd', 'files' }: | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
								vars = locals() | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
								cmd[i] = [ x.format(**vars) for x in cmd[i] ] | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
							else: | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
								cmd[i] = cmd[i].format(**locals()) | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
			
  | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
						try: | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
							special = cmd['special'] | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
						except KeyError: | 
		
		
	
	
		
			
				| 
				
					
				
				
					
				
				
				 | 
			
			 | 
			@@ -1673,6 +1769,21 @@ class _TestCases(unittest.TestCase): | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
								sd.mkdir(exist_ok=True) | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
			
  | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
								bttestcase.make_files(sd, btfiles) | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
							elif special == 'setup mapping paths': | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
								mappatha = self.tempdir / 'mapa' | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
								mappatha.mkdir() | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
			
  | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
								mappathb = self.tempdir / 'mapb' | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
								mappathb.mkdir() | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
			
  | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
								filea = mappatha / 'text.txt' | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
								filea.write_text('abc123\n') | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
								fileb = mappathb / 'text.txt' | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
								shutil.copyfile(filea, fileb) | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
								shutil.copystat(filea, fileb) | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
							elif special == 'delete files': | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
								for i in cmd['files']: | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
									os.unlink(i) | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
							else: # pragma: no cover | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
								raise ValueError('unhandled special: %s' % repr(special)) | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
			
  | 
		
		
	
	
		
			
				| 
				
					
				
				
				
				 | 
			
			 | 
			
  |