diff --git a/lang/en_us.yml b/lang/en_us.yml index 9b3ef2c..460003a 100644 --- a/lang/en_us.yml +++ b/lang/en_us.yml @@ -133,7 +133,7 @@ prime_backup: crontab: 'Crontab: {}' jitter: 'Jitter: {}' next_run_date: 'Next at: {}' - quick_actions: 'Quick actions: {} {}' + quick_actions: 'Quick actions: {}' pause: pause pause.hover: Click to pause job "{}" resume: resume diff --git a/prime_backup/mcdr/crontab_job/basic_job.py b/prime_backup/mcdr/crontab_job/basic_job.py index cebbca2..0505e0d 100644 --- a/prime_backup/mcdr/crontab_job/basic_job.py +++ b/prime_backup/mcdr/crontab_job/basic_job.py @@ -2,7 +2,7 @@ import datetime import threading from abc import ABC, abstractmethod -from typing import TYPE_CHECKING, List, Optional, Any +from typing import TYPE_CHECKING, List, Optional, Any, Callable from apscheduler.job import Job from apscheduler.schedulers.base import BaseScheduler @@ -167,7 +167,12 @@ def report(self): """ ... - def run_task_with_retry(self, task: Task, can_retry: bool, delays: Optional[List[float]] = None, broadcast: bool = False) -> RunTaskWithRetryResult: + def run_task_with_retry( + self, task: Task, can_retry: bool, *, + requirement: Callable[[], bool] = None, + delays: Optional[List[float]] = None, + broadcast: bool = False + ) -> RunTaskWithRetryResult: if delays is None: delays = self.__create_run_tasks_delays() misc_utils.assert_true(len(delays) > 0, 'delay should not be empty') @@ -196,10 +201,12 @@ def report(self): else: log_info(base_tr('found_ongoing.skip', current_task, this.get_name_text())) - for delay in delays: + for i, delay in enumerate(delays): self.abort_event.wait(delay) if self.abort_event.is_set(): break + if requirement is not None and not requirement(): + break try: def callback(*args): wv.set(args) @@ -207,10 +214,12 @@ def callback(*args): self.task_manager.add_task(task, callback, handle_tmo_err=False) except TaskQueue.TooManyOngoingTask as e: current_task = e.current_item.task_name() if e.current_item is not None else self.tr('found_ongoing.unknown').set_color(RColor.gray) - if delay is None or not can_retry: # <= 5min, no need to retry - break + is_not_last = i < len(delays) - 1 + if is_not_last and can_retry: + next_wait = delays[i + 1] + log_info(self.__base_tr('found_ongoing.wait_retry', current_task, self.get_name_text(), TextComponents.number(f'{next_wait}s'))) else: - log_info(self.__base_tr('found_ongoing.wait_retry', current_task, self.get_name_text(), TextComponents.number(f'{delay}s'))) + break else: ret, err = wv.wait() return RunTaskWithRetryResultImpl(True, ret, err) diff --git a/prime_backup/mcdr/crontab_job/scheduled_backup_job.py b/prime_backup/mcdr/crontab_job/scheduled_backup_job.py index 8db1281..af67c8b 100644 --- a/prime_backup/mcdr/crontab_job/scheduled_backup_job.py +++ b/prime_backup/mcdr/crontab_job/scheduled_backup_job.py @@ -25,6 +25,7 @@ def __init__(self, scheduler: BaseScheduler, task_manager: 'TaskManager'): self.config: ScheduledBackupConfig = self._root_config.scheduled_backup self.is_executing = threading.Event() self.is_aborted = threading.Event() + self.found_created_backup = threading.Event() @property def id(self) -> CrontabJobId: @@ -50,7 +51,8 @@ def run(self): operator = Operator.pb(PrimeBackupOperatorNames.scheduled_backup) task = CreateBackupTask(self.get_command_source(), comment, operator=operator) - self.run_task_with_retry(task, True, broadcast=True).report() + self.found_created_backup.clear() + self.run_task_with_retry(task, True, requirement=lambda: not self.found_created_backup.is_set(), broadcast=True).report() def on_event(self, event: CrontabJobEvent): super().on_event(event) @@ -59,6 +61,7 @@ def on_event(self, event: CrontabJobEvent): return if event == CrontabJobEvent.manual_backup_created: + self.found_created_backup.set() if not self.is_executing.is_set() and self.config.reset_timer_on_backup: if self.reschedule(): broadcast_message(self.tr('reset_on_backup', self.get_next_run_date())) diff --git a/prime_backup/mcdr/task/backup/delete_backup_range_task.py b/prime_backup/mcdr/task/backup/delete_backup_range_task.py index aa8b2a6..bed4c06 100644 --- a/prime_backup/mcdr/task/backup/delete_backup_range_task.py +++ b/prime_backup/mcdr/task/backup/delete_backup_range_task.py @@ -38,6 +38,7 @@ def run(self) -> None: backups = [backup for backup in backups if not backup.tags.is_protected()] # double check if len(backups) == 0: self.reply(self.tr('no_backup')) + return self.reply(self.tr('to_delete_count', TextComponents.number(len(backups)))) n = 10 diff --git a/prime_backup/mcdr/task/backup/show_backup_tag_task.py b/prime_backup/mcdr/task/backup/show_backup_tag_task.py index 50a4cd6..269543b 100644 --- a/prime_backup/mcdr/task/backup/show_backup_tag_task.py +++ b/prime_backup/mcdr/task/backup/show_backup_tag_task.py @@ -4,7 +4,7 @@ from prime_backup.action.get_backup_action import GetBackupAction from prime_backup.mcdr.task.basic_task import ReaderTask -from prime_backup.mcdr.text_components import TextComponents +from prime_backup.mcdr.text_components import TextComponents, TextColors from prime_backup.types.backup_info import BackupInfo from prime_backup.types.backup_tags import BackupTagName, BackupTags from prime_backup.utils.mcdr_utils import mkcmd @@ -46,18 +46,18 @@ def __show_tag(self, backup: BackupInfo, key: str, value: Any): t_key = RText(key).h(t_value_type) else: t_value_type = self.tr('value_type', tag_name.value.type.__name__) - t_key = TextComponents.tag_name(tag_name).h(RTextList(tag_name.value.text, '\n', t_value_type)) + t_key = TextComponents.tag_name(tag_name).h(RTextList(tag_name.value.text.copy().set_color(TextColors.backup_tag), '\n', t_value_type)) if value is not BackupTags.NONE: t_value = TextComponents.auto(value) buttons = [ RText('[_]', RColor.yellow).h(self.tr('edit', t_key)).c(RAction.suggest_command, mkcmd(f'tag {backup.id} {key} set ')), - RText('[x]', RColor.gold).h(self.tr('clear', t_key)).c(RAction.suggest_command, mkcmd(f'tag {backup.id} {key} clear')), + RText('[x]', RColor.red).h(self.tr('clear', t_key)).c(RAction.suggest_command, mkcmd(f'tag {backup.id} {key} clear')), ] else: t_value = self.tr('not_exists').set_color(RColor.gray) buttons = [ - RText('[+]', RColor.yellow).h(self.tr('create', t_key)).c(RAction.suggest_command, mkcmd(f'tag {backup.id} {key} set ')), + RText('[+]', RColor.dark_green).h(self.tr('create', t_key)).c(RAction.suggest_command, mkcmd(f'tag {backup.id} {key} set ')), ] self.reply(RTextBase.format('{} {}: {}', RTextBase.join(' ', buttons), t_key, t_value)) diff --git a/prime_backup/mcdr/text_components.py b/prime_backup/mcdr/text_components.py index ce6f7c2..2067f59 100644 --- a/prime_backup/mcdr/text_components.py +++ b/prime_backup/mcdr/text_components.py @@ -16,6 +16,7 @@ class TextColors: backup_id = RColor.gold + backup_tag = RColor.aqua byte_count = RColor.green date = RColor.aqua file = RColor.dark_aqua @@ -247,7 +248,7 @@ def percent(cls, value: float, total: float) -> RTextBase: @classmethod def tag_name(cls, tag_name: BackupTagName) -> RTextBase: - return RText(tag_name.name, RColor.aqua).h(tag_name.value.text) + return RText(tag_name.name, TextColors.backup_tag).h(tag_name.value.text) @classmethod def title(cls, text: Any) -> RTextBase: