| 
					
				 | 
			
			
				@@ -77,7 +77,7 @@ class Archives: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     borg2 has separate items archives/* in the borgstore. 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     """ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    def __init__(self, repository): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    def __init__(self, repository, manifest): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         from .repository import Repository 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         from .remote import RemoteRepository 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -85,87 +85,98 @@ class Archives: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         self.legacy = not isinstance(repository, (Repository, RemoteRepository)) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         # key: str archive name, value: dict('id': bytes_id, 'time': str_iso_ts) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         self._archives = {} 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-        self.manifest = None 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        self.manifest = manifest 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     def prepare(self, manifest, m): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-        self.manifest = manifest 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         if not self.legacy: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            self._load() 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            pass 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         else: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				             self._set_raw_dict(m.archives) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     def finish(self, manifest): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-        self.manifest = manifest  # note: .prepare is not always called 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         if not self.legacy: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            self._save() 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				             manifest_archives = {} 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         else: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				             manifest_archives = StableDict(self._get_raw_dict()) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         return manifest_archives 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    def _load(self): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-        # load archives list from store 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-        from .helpers import msgpack 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				- 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-        archives = {} 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-        try: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            infos = list(self.repository.store_list("archives")) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-        except ObjectNotFound: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            infos = [] 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-        for info in infos: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            info = ItemInfo(*info)  # RPC does not give us a NamedTuple 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            value = self.repository.store_load(f"archives/{info.name}") 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            _, value = self.manifest.repo_objs.parse(hex_to_bin(info.name), value, ro_type=ROBJ_MANIFEST) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            archive = msgpack.unpackb(value) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            archives[archive["name"]] = dict(id=archive["id"], time=archive["time"]) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-        self._set_raw_dict(archives) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				- 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    def _save(self): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-        # save archives list to store 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-        valid_keys = set() 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-        for name, info in self._get_raw_dict().items(): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            archive = dict(name=name, id=info["id"], time=info["time"]) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            value = self.manifest.key.pack_metadata(archive)  # 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            id = self.manifest.repo_objs.id_hash(value) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            key = bin_to_hex(id) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            value = self.manifest.repo_objs.format(id, {}, value, ro_type=ROBJ_MANIFEST) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            self.repository.store_store(f"archives/{key}", value) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            valid_keys.add(key) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-        # now, delete all other keys in archives/ which are not in valid keys / in the manifest anymore. 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-        # TODO: this is a dirty hack to simulate the old manifest behaviour closely, but also means 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-        #       keeping its problems, like read-modify-write behaviour requiring an exclusive lock. 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-        try: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            infos = list(self.repository.store_list("archives")) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-        except ObjectNotFound: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            infos = [] 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-        for info in infos: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            info = ItemInfo(*info)  # RPC does not give us a NamedTuple 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            if info.name not in valid_keys: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                self.repository.store_delete(f"archives/{info.name}") 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				- 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     def count(self): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         # return the count of archives in the repo 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-        return len(self._archives) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        if not self.legacy: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            try: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                infos = list(self.repository.store_list("archives")) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            except ObjectNotFound: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                infos = [] 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            return len(infos)  # we do not check here if entries are valid 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        else: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            return len(self._archives) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     def exists(self, name): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         # check if an archive with this name exists 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         assert isinstance(name, str) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-        return name in self._archives 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        if not self.legacy: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            return name in self.names() 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        else: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            return name in self._archives 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    def _infos(self): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        # yield the infos of all archives: (store_key, archive_info) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        from .helpers import msgpack 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        if not self.legacy: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            try: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                infos = list(self.repository.store_list("archives")) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            except ObjectNotFound: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                infos = [] 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            for info in infos: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                info = ItemInfo(*info)  # RPC does not give us a NamedTuple 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                value = self.repository.store_load(f"archives/{info.name}") 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                _, value = self.manifest.repo_objs.parse(hex_to_bin(info.name), value, ro_type=ROBJ_MANIFEST) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                archive_info = msgpack.unpackb(value) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                yield info.name, archive_info 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        else: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            for name in self._archives: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                archive_info = dict(name=name, id=self._archives[name]["id"], time=self._archives[name]["time"]) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                yield None, archive_info 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    def _lookup_name(self, name, raw=False): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        assert isinstance(name, str) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        assert not self.legacy 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        for store_key, archive_info in self._infos(): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            if archive_info["name"] == name: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                if not raw: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    ts = parse_timestamp(archive_info["time"]) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    return store_key, ArchiveInfo(name=name, id=archive_info["id"], ts=ts) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                else: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    return store_key, archive_info 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        else: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            raise KeyError(name) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     def names(self): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         # yield the names of all archives 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-        yield from self._archives 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        if not self.legacy: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            for _, archive_info in self._infos(): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                yield archive_info["name"] 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        else: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            yield from self._archives 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     def get(self, name, raw=False): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         assert isinstance(name, str) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-        values = self._archives.get(name) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-        if values is None: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            raise KeyError 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-        if not raw: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            ts = parse_timestamp(values["time"]) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            return ArchiveInfo(name=name, id=values["id"], ts=ts) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        if not self.legacy: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            try: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                store_key, archive_info = self._lookup_name(name, raw=raw) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                return archive_info 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            except KeyError: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                return None 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         else: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            return dict(name=name, id=values["id"], time=values["time"]) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            values = self._archives.get(name) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            if values is None: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                return None 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            if not raw: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                ts = parse_timestamp(values["time"]) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                return ArchiveInfo(name=name, id=values["id"], ts=ts) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            else: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                return dict(name=name, id=values["id"], time=values["time"]) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     def create(self, name, id, ts, *, overwrite=False): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         assert isinstance(name, str) 
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -173,14 +184,36 @@ class Archives: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         if isinstance(ts, datetime): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				             ts = ts.isoformat(timespec="microseconds") 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         assert isinstance(ts, str) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-        if name in self._archives and not overwrite: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            raise KeyError("archive already exists") 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-        self._archives[name] = {"id": id, "time": ts} 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        if not self.legacy: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            try: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                store_key, _ = self._lookup_name(name) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            except KeyError: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                pass 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            else: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                # looks like we already have an archive list entry with that name 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                if not overwrite: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    raise KeyError("archive already exists") 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                else: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    self.repository.store_delete(f"archives/{store_key}") 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            archive = dict(name=name, id=id, time=ts) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            value = self.manifest.key.pack_metadata(archive) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            id = self.manifest.repo_objs.id_hash(value) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            key = bin_to_hex(id) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            value = self.manifest.repo_objs.format(id, {}, value, ro_type=ROBJ_MANIFEST) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            self.repository.store_store(f"archives/{key}", value) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        else: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            if self.exists(name) and not overwrite: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                raise KeyError("archive already exists") 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            self._archives[name] = {"id": id, "time": ts} 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     def delete(self, name): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         # delete an archive 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         assert isinstance(name, str) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-        self._archives.pop(name) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        if not self.legacy: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            store_key, archive_info = self._lookup_name(name) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            self.repository.store_delete(f"archives/{store_key}") 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        else: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            self._archives.pop(name) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     def list( 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         self, 
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -297,7 +330,7 @@ class Manifest: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     MANIFEST_ID = b"\0" * 32 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     def __init__(self, key, repository, item_keys=None, ro_cls=RepoObj): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-        self.archives = Archives(repository) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        self.archives = Archives(repository, self) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         self.config = {} 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         self.key = key 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         self.repo_objs = ro_cls(key) 
			 |