Browse Source

refactor: move archives related code from Manifest to Archives class

Thomas Waldmann 9 months ago
parent
commit
b82ced274f
1 changed files with 66 additions and 51 deletions
  1. 66 51
      src/borg/manifest.py

+ 66 - 51
src/borg/manifest.py

@@ -75,9 +75,71 @@ class Archives(abc.MutableMapping):
     str timestamps or datetime timestamps.
     """
 
-    def __init__(self):
+    def __init__(self, repository):
+        from .repository import Repository
+        from .remote import RemoteRepository
+
+        self.repository = repository
+        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
+
+    def prepare(self, manifest, m):
+        self.manifest = manifest
+        if not self.legacy:
+            self.load()
+        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 __len__(self):
         return len(self._archives)
@@ -222,7 +284,7 @@ class Manifest:
     MANIFEST_ID = b"\0" * 32
 
     def __init__(self, key, repository, item_keys=None, ro_cls=RepoObj):
-        self.archives = Archives()
+        self.archives = Archives(repository)
         self.config = {}
         self.key = key
         self.repo_objs = ro_cls(key)
@@ -242,8 +304,6 @@ class Manifest:
     def load(cls, repository, operations, key=None, *, ro_cls=RepoObj):
         from .item import ManifestItem
         from .crypto.key import key_factory
-        from .remote import RemoteRepository
-        from .repository import Repository
 
         cdata = repository.get_manifest()
         if not key:
@@ -255,25 +315,7 @@ class Manifest:
         manifest.id = manifest.repo_objs.id_hash(data)
         if m.get("version") not in (1, 2):
             raise ValueError("Invalid manifest version")
-
-        if isinstance(repository, (Repository, RemoteRepository)):
-            from .helpers import msgpack
-
-            archives = {}
-            try:
-                infos = list(repository.store_list("archives"))
-            except ObjectNotFound:
-                infos = []
-            for info in infos:
-                info = ItemInfo(*info)  # RPC does not give us a NamedTuple
-                value = repository.store_load(f"archives/{info.name}")
-                _, value = 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"])
-            manifest.archives.set_raw_dict(archives)
-        else:
-            manifest.archives.set_raw_dict(m.archives)
-
+        manifest.archives.prepare(manifest, m)
         manifest.timestamp = m.get("timestamp")
         manifest.config = m.config
         # valid item keys are whatever is known in the repo or every key we know
@@ -310,8 +352,6 @@ class Manifest:
 
     def write(self):
         from .item import ManifestItem
-        from .remote import RemoteRepository
-        from .repository import Repository
 
         # self.timestamp needs to be strictly monotonically increasing. Clocks often are not set correctly
         if self.timestamp is None:
@@ -326,32 +366,7 @@ class Manifest:
         assert all(len(name) <= 255 for name in self.archives)
         assert len(self.item_keys) <= 100
         self.config["item_keys"] = tuple(sorted(self.item_keys))
-
-        if isinstance(self.repository, (Repository, RemoteRepository)):
-            valid_keys = set()
-            for name, info in self.archives.get_raw_dict().items():
-                archive = dict(name=name, id=info["id"], time=info["time"])
-                value = self.key.pack_metadata(archive)
-                id = self.repo_objs.id_hash(value)
-                key = bin_to_hex(id)
-                value = self.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}")
-            manifest_archives = {}
-        else:
-            manifest_archives = StableDict(self.archives.get_raw_dict())
-
+        manifest_archives = self.archives.finish(self)
         manifest = ManifestItem(
             version=2, archives=manifest_archives, timestamp=self.timestamp, config=StableDict(self.config)
         )