mirror of
https://git.selfprivacy.org/SelfPrivacy/selfprivacy-rest-api.git
synced 2025-03-11 09:03:50 +00:00
feature(backups): realtime progress updates of backups
This commit is contained in:
parent
1faaed992e
commit
d38b8180cb
2 changed files with 45 additions and 14 deletions
|
@ -7,6 +7,9 @@ from collections.abc import Iterable
|
||||||
|
|
||||||
from selfprivacy_api.backup.backuper import AbstractBackuper
|
from selfprivacy_api.backup.backuper import AbstractBackuper
|
||||||
from selfprivacy_api.models.backup.snapshot import Snapshot
|
from selfprivacy_api.models.backup.snapshot import Snapshot
|
||||||
|
from selfprivacy_api.backup.jobs import get_backup_job
|
||||||
|
from selfprivacy_api.services import get_service_by_id
|
||||||
|
from selfprivacy_api.jobs import Jobs, JobStatus
|
||||||
|
|
||||||
from selfprivacy_api.backup.local_secret import LocalBackupSecret
|
from selfprivacy_api.backup.local_secret import LocalBackupSecret
|
||||||
|
|
||||||
|
@ -78,6 +81,19 @@ class ResticBackuper(AbstractBackuper):
|
||||||
result.append(item)
|
result.append(item)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def output_yielder(command):
|
||||||
|
with subprocess.Popen(
|
||||||
|
command,
|
||||||
|
shell=False,
|
||||||
|
stdout=subprocess.PIPE,
|
||||||
|
stderr=subprocess.STDOUT,
|
||||||
|
universal_newlines=True,
|
||||||
|
) as handle:
|
||||||
|
for line in iter(handle.stdout.readline, ""):
|
||||||
|
if not "NOTICE:" in line:
|
||||||
|
yield line
|
||||||
|
|
||||||
def start_backup(self, folders: List[str], repo_name: str):
|
def start_backup(self, folders: List[str], repo_name: str):
|
||||||
"""
|
"""
|
||||||
Start backup with restic
|
Start backup with restic
|
||||||
|
@ -92,20 +108,25 @@ class ResticBackuper(AbstractBackuper):
|
||||||
folders,
|
folders,
|
||||||
branch_name=repo_name,
|
branch_name=repo_name,
|
||||||
)
|
)
|
||||||
with subprocess.Popen(
|
|
||||||
backup_command,
|
messages = []
|
||||||
shell=False,
|
try:
|
||||||
stdout=subprocess.PIPE,
|
for raw_message in ResticBackuper.output_yielder(backup_command):
|
||||||
stderr=subprocess.STDOUT,
|
message = self.parse_json_output(raw_message)
|
||||||
) as handle:
|
if message["message_type"] == "status":
|
||||||
output = handle.communicate()[0].decode("utf-8")
|
job = get_backup_job(get_service_by_id(repo_name))
|
||||||
try:
|
if job is not None: # only update status if we run under some job
|
||||||
messages = self.parse_json_output(output)
|
Jobs.update(
|
||||||
return ResticBackuper._snapshot_from_backup_messages(
|
job,
|
||||||
messages, repo_name
|
JobStatus.RUNNING,
|
||||||
)
|
progress=ResticBackuper.progress_from_status_message(
|
||||||
except ValueError as e:
|
message
|
||||||
raise ValueError("could not create a snapshot: ") from e
|
),
|
||||||
|
)
|
||||||
|
messages.append(message)
|
||||||
|
return ResticBackuper._snapshot_from_backup_messages(messages, repo_name)
|
||||||
|
except ValueError as e:
|
||||||
|
raise ValueError("could not create a snapshot: ", messages) from e
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _snapshot_from_backup_messages(messages, repo_name) -> Snapshot:
|
def _snapshot_from_backup_messages(messages, repo_name) -> Snapshot:
|
||||||
|
@ -114,6 +135,10 @@ class ResticBackuper(AbstractBackuper):
|
||||||
return ResticBackuper._snapshot_from_fresh_summary(message, repo_name)
|
return ResticBackuper._snapshot_from_fresh_summary(message, repo_name)
|
||||||
raise ValueError("no summary message in restic json output")
|
raise ValueError("no summary message in restic json output")
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def progress_from_status_message(message: object) -> int:
|
||||||
|
return int(message["percent_done"])
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _snapshot_from_fresh_summary(message: object, repo_name) -> Snapshot:
|
def _snapshot_from_fresh_summary(message: object, repo_name) -> Snapshot:
|
||||||
return Snapshot(
|
return Snapshot(
|
||||||
|
|
|
@ -235,6 +235,11 @@ def assert_job_has_run(job_type):
|
||||||
assert JobStatus.RUNNING in Jobs.status_updates(job)
|
assert JobStatus.RUNNING in Jobs.status_updates(job)
|
||||||
|
|
||||||
|
|
||||||
|
def assert_job_had_progress(job_type):
|
||||||
|
job = [job for job in finished_jobs() if job.type_id == job_type][0]
|
||||||
|
assert len(Jobs.progress_updates(job)) > 0
|
||||||
|
|
||||||
|
|
||||||
def test_backup_service_task(backups, dummy_service):
|
def test_backup_service_task(backups, dummy_service):
|
||||||
handle = start_backup(dummy_service)
|
handle = start_backup(dummy_service)
|
||||||
handle(blocking=True)
|
handle(blocking=True)
|
||||||
|
@ -246,6 +251,7 @@ def test_backup_service_task(backups, dummy_service):
|
||||||
job_type_id = f"services.{id}.backup"
|
job_type_id = f"services.{id}.backup"
|
||||||
assert_job_finished(job_type_id, count=1)
|
assert_job_finished(job_type_id, count=1)
|
||||||
assert_job_has_run(job_type_id)
|
assert_job_has_run(job_type_id)
|
||||||
|
assert_job_had_progress(job_type_id)
|
||||||
|
|
||||||
|
|
||||||
def test_restore_snapshot_task(backups, dummy_service):
|
def test_restore_snapshot_task(backups, dummy_service):
|
||||||
|
|
Loading…
Add table
Reference in a new issue