diff --git a/selfprivacy_api/backup/__init__.py b/selfprivacy_api/backup/__init__.py index f575ac0..3b21a29 100644 --- a/selfprivacy_api/backup/__init__.py +++ b/selfprivacy_api/backup/__init__.py @@ -397,9 +397,8 @@ class Backups: # TODO: Can be optimized since there is forgetting of an array in one restic op # but most of the time this will be only one snap to forget. - for snap in auto_snaps: - if snap not in new_snaplist: - Backups.forget_snapshot(snap) + deletable_snaps = [snap for snap in auto_snaps if snap not in new_snaplist] + Backups.forget_snapshots(deletable_snaps) @staticmethod def _standardize_quotas(i: int) -> int: @@ -606,6 +605,19 @@ class Backups: return snap + @staticmethod + def forget_snapshots(snapshots: List[Snapshot]) -> None: + """ + Deletes a batch of snapshots from the repo and from cache + Optimized + """ + ids = [snapshot.id for snapshot in snapshots] + Backups.provider().backupper.forget_snapshots(ids) + + # less critical + for snapshot in snapshots: + Storage.delete_cached_snapshot(snapshot) + @staticmethod def forget_snapshot(snapshot: Snapshot) -> None: """Deletes a snapshot from the repo and from cache""" diff --git a/selfprivacy_api/backup/backuppers/__init__.py b/selfprivacy_api/backup/backuppers/__init__.py index 0067a41..46a719e 100644 --- a/selfprivacy_api/backup/backuppers/__init__.py +++ b/selfprivacy_api/backup/backuppers/__init__.py @@ -66,3 +66,8 @@ class AbstractBackupper(ABC): def forget_snapshot(self, snapshot_id) -> None: """Forget a snapshot""" raise NotImplementedError + + @abstractmethod + def forget_snapshots(self, snapshot_ids: List[str]) -> None: + """Maybe optimized deletion of a batch of snapshots, just cycling if unsupported""" + raise NotImplementedError diff --git a/selfprivacy_api/backup/backuppers/none_backupper.py b/selfprivacy_api/backup/backuppers/none_backupper.py index 429d9ab..86e25a6 100644 --- a/selfprivacy_api/backup/backuppers/none_backupper.py +++ b/selfprivacy_api/backup/backuppers/none_backupper.py @@ -39,4 +39,7 @@ class NoneBackupper(AbstractBackupper): raise NotImplementedError def forget_snapshot(self, snapshot_id): - raise NotImplementedError + raise NotImplementedError("forget_snapshot") + + def forget_snapshots(self, snapshots): + raise NotImplementedError("forget_snapshots") diff --git a/selfprivacy_api/backup/backuppers/restic_backupper.py b/selfprivacy_api/backup/backuppers/restic_backupper.py index afa6295..fd653e6 100644 --- a/selfprivacy_api/backup/backuppers/restic_backupper.py +++ b/selfprivacy_api/backup/backuppers/restic_backupper.py @@ -86,6 +86,10 @@ class ResticBackupper(AbstractBackupper): return f"echo {LocalBackupSecret.get()}" def restic_command(self, *args, tags: Optional[List[str]] = None) -> List[str]: + """ + Construct a restic command against the currently configured repo + Can support [nested] arrays as arguments, will flatten them into the final commmand + """ if tags is None: tags = [] @@ -384,15 +388,15 @@ class ResticBackupper(AbstractBackupper): output, ) + def forget_snapshot(self, snapshot_id: str) -> None: + self.forget_snapshots([snapshot_id]) + @unlocked_repo - def forget_snapshot(self, snapshot_id) -> None: - """ - Either removes snapshot or marks it for deletion later, - depending on server settings - """ + def forget_snapshots(self, snapshot_ids: List[str]) -> None: + # in case the backupper program supports batching, otherwise implement it by cycling forget_command = self.restic_command( "forget", - snapshot_id, + [snapshot_ids], # TODO: prune should be done in a separate process "--prune", ) @@ -414,7 +418,7 @@ class ResticBackupper(AbstractBackupper): if "no matching ID found" in err: raise ValueError( - "trying to delete, but no such snapshot: ", snapshot_id + "trying to delete, but no such snapshot(s): ", snapshot_ids ) assert (