diff --git a/CHANGELOG.md b/CHANGELOG.md index 771c1c00..498c05bb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ### Improvements - General - code refactoring + - backup: + - single mode: only removing self generated snapshots (now the snapshots are associated with the comment "") [#244](https://github.com/vinifmor/bauh/issues/244) - Arch - text length of some popups reduced diff --git a/README.md b/README.md index bb1d78ba..778acb8d 100644 --- a/README.md +++ b/README.md @@ -438,7 +438,7 @@ disk: after_upgrade: false # it trims the disk after a successful packages upgrade (`fstrim -a -v`). 'true' will automatically perform the trim and 'null' will display a confirmation dialog backup: enabled: true # generate timeshift snapshots before an action (if timeshift is installed on the system) - mode: 'incremental' # incremental=generates a new snapshot based on another pre-exising one. 'only_one'=deletes all pre-existing snapshots and generates a fresh one. + mode: 'incremental' # incremental=generates a new snapshot based on another pre-exising one. 'only_one'=deletes all pre-existing self created snapshots and generates a fresh one. install: null # defines if the backup should be performed before installing a package. Allowed values: null (a dialog will be displayed asking if a snapshot should be generated), true: generates the backup without asking. false: disables the backup for this operation uninstall: null # defines if the backup should be performed before uninstalling a package. Allowed values: null (a dialog will be displayed asking if a snapshot should be generated), true: generates the backup without asking. false: disables the backup for this operation upgrade: null # defines if the backup should be performed before upgrading a package. Allowed values: null (a dialog will be displayed asking if a snapshot should be generated), true: generates the backup without asking. false: disables the backup for this operation diff --git a/bauh/view/core/timeshift.py b/bauh/view/core/timeshift.py index e2d4ad73..e0e551b8 100644 --- a/bauh/view/core/timeshift.py +++ b/bauh/view/core/timeshift.py @@ -1,7 +1,11 @@ +import re import shutil -from typing import Optional +from typing import Optional, Generator -from bauh.commons.system import SimpleProcess +from bauh import __app_name__ +from bauh.commons.system import SimpleProcess, new_root_subprocess + +RE_SNAPSHOTS = re.compile(r'\d+\s+>\s+([\w\-_]+)\s+.+<{}>'.format(__app_name__)) def is_available() -> bool: @@ -12,5 +16,23 @@ def delete_all_snapshots(root_password: Optional[str]) -> SimpleProcess: return SimpleProcess(['timeshift', '--delete-all', '--scripted'], root_password=root_password) +def delete(snapshot_name: str, root_password: Optional[str]) -> SimpleProcess: + return SimpleProcess(('timeshift', '--delete', '--snapshot', snapshot_name), + shell=True, root_password=root_password) + + def create_snapshot(root_password: Optional[str], mode: str) -> SimpleProcess: - return SimpleProcess(['timeshift', '--create', '--scripted', '--{}'.format(mode)], root_password=root_password) + return SimpleProcess(('timeshift', '--create', '--scripted', f'--{mode}', '--comments', f'<{__app_name__}>'), + root_password=root_password) + + +def read_created_snapshots(root_password: Optional[str]) -> Generator[str, None, None]: + proc = new_root_subprocess(cmd=('timeshift', '--list'), root_password=root_password, shell=True) + proc.wait() + + if proc.returncode == 0: + output = '\n'.join((o.decode() for o in proc.stdout)) + + if output: + for name in RE_SNAPSHOTS.findall(output): + yield name diff --git a/bauh/view/qt/thread.py b/bauh/view/qt/thread.py index 27f59ad1..495ddf55 100644 --- a/bauh/view/qt/thread.py +++ b/bauh/view/qt/thread.py @@ -5,7 +5,7 @@ import traceback from datetime import datetime, timedelta from io import StringIO from pathlib import Path -from typing import List, Type, Set, Tuple, Optional, Iterable +from typing import List, Type, Set, Tuple, Optional import requests from PyQt5.QtCore import QThread, pyqtSignal, QObject @@ -140,16 +140,26 @@ class AsyncAction(QThread, ProcessWatcher): handler = ProcessHandler(self) if app_config['backup']['mode'] == 'only_one': - self.change_substatus('[{}] {}'.format(self.i18n['core.config.tab.backup'].lower(), self.i18n['action.backup.substatus.delete'])) - deleted, _ = handler.handle_simple(timeshift.delete_all_snapshots(root_password)) + previous_snapshots = tuple(timeshift.read_created_snapshots(root_password)) - if not deleted and not self.request_confirmation(title=self.i18n['core.config.tab.backup'], - body='{}. {}'.format(self.i18n['action.backup.error.delete'], - self.i18n['action.backup.error.proceed']), - confirmation_label=self.i18n['yes'].capitalize(), - deny_label=self.i18n['no'].capitalize()): - self.change_substatus('') - return False + if previous_snapshots: + substatus = f"[{self.i18n['core.config.tab.backup'].lower()}] {self.i18n['action.backup.substatus.delete']}" + self.change_substatus(substatus) + + delete_failed = False + for snapshot in reversed(previous_snapshots): + deleted, _ = handler.handle_simple(timeshift.delete(snapshot, root_password)) + + if not deleted: + delete_failed = True + + if delete_failed and not self.request_confirmation(title=self.i18n['core.config.tab.backup'], + body=f"{self.i18n['action.backup.error.delete']}. " + f"{self.i18n['action.backup.error.proceed']}", + confirmation_label=self.i18n['yes'].capitalize(), + deny_label=self.i18n['no'].capitalize()): + self.change_substatus('') + return False self.change_substatus('[{}] {}'.format(self.i18n['core.config.tab.backup'].lower(), self.i18n['action.backup.substatus.create'])) created, _ = handler.handle_simple(timeshift.create_snapshot(root_password, app_config['backup']['type'])) diff --git a/bauh/view/resources/locale/ca b/bauh/view/resources/locale/ca index 87b6267e..854eebf2 100644 --- a/bauh/view/resources/locale/ca +++ b/bauh/view/resources/locale/ca @@ -45,7 +45,7 @@ Ukraine=Ukraine United Kingdom=United Kingdom United States=United States action.backup.error.create=It was not possible to generate a new system copy -action.backup.error.delete=It was not possible to delete the old system copies +action.backup.error.delete=It was not possible to delete all the old system copies action.backup.error.proceed=Proceed anyway ? action.backup.invalid_mode=Invalid backup mode action.backup.msg=Do you want to generate a system copy before proceeding ? diff --git a/bauh/view/resources/locale/de b/bauh/view/resources/locale/de index aac43a58..551a2385 100644 --- a/bauh/view/resources/locale/de +++ b/bauh/view/resources/locale/de @@ -45,7 +45,7 @@ Ukraine=Ukraine United Kingdom=United Kingdom United States=United States action.backup.error.create=It was not possible to generate a new system copy -action.backup.error.delete=It was not possible to delete the old system copies +action.backup.error.delete=It was not possible to delete all the old system copies action.backup.error.proceed=Proceed anyway ? action.backup.invalid_mode=Invalid backup mode action.backup.msg=Do you want to generate a system copy before proceeding ? diff --git a/bauh/view/resources/locale/en b/bauh/view/resources/locale/en index bb4e9893..537d7aa4 100644 --- a/bauh/view/resources/locale/en +++ b/bauh/view/resources/locale/en @@ -45,7 +45,7 @@ Ukraine=Ukraine United Kingdom=United Kingdom United States=United States action.backup.error.create=It was not possible to generate a new system copy -action.backup.error.delete=It was not possible to delete the old system copies +action.backup.error.delete=It was not possible to delete all the old system copies action.backup.error.proceed=Proceed anyway ? action.backup.invalid_mode=Invalid backup mode action.backup.msg=Do you want to generate a system copy before proceeding ? diff --git a/bauh/view/resources/locale/es b/bauh/view/resources/locale/es index 6278a542..2312ba60 100644 --- a/bauh/view/resources/locale/es +++ b/bauh/view/resources/locale/es @@ -45,7 +45,7 @@ Ukraine=Ucrania United Kingdom=Reino Unido United States=Estados Unidos action.backup.error.create=No fue posible generar una nueva copia de seguridad -action.backup.error.delete=No fue posible eliminar las copias de seguridad antiguas del sistema +action.backup.error.delete=No fue posible eliminar todas las copias de seguridad antiguas del sistema action.backup.error.proceed=¿Continuar de todos modos? action.backup.invalid_mode=Modo de copia de seguridad inválido action.backup.msg=¿Desea generar una copia de seguridad del sistema antes de continuar? diff --git a/bauh/view/resources/locale/it b/bauh/view/resources/locale/it index be84a0d1..4c28603f 100644 --- a/bauh/view/resources/locale/it +++ b/bauh/view/resources/locale/it @@ -45,7 +45,7 @@ Ukraine=Ukraine United Kingdom=United Kingdom United States=United States action.backup.error.create=It was not possible to generate a new system copy -action.backup.error.delete=It was not possible to delete the old system copies +action.backup.error.delete=It was not possible to delete all the old system copies action.backup.error.proceed=Proceed anyway ? action.backup.invalid_mode=Invalid backup mode action.backup.msg=Do you want to generate a system copy before proceeding ? diff --git a/bauh/view/resources/locale/pt b/bauh/view/resources/locale/pt index 2ff8566a..25a6276f 100644 --- a/bauh/view/resources/locale/pt +++ b/bauh/view/resources/locale/pt @@ -45,7 +45,7 @@ Ukraine=Ucrânia United Kingdom=Reino Unido United States=Estados Unidos action.backup.error.create=Não foi possível gerar uma nova cópia de segurança do sistema -action.backup.error.delete=Não foi possível remover as cópias de segurança antigas do sistema +action.backup.error.delete=Não foi possível remover todas as cópias de segurança antigas do sistema action.backup.error.proceed=Continuar mesmo assim ? action.backup.invalid_mode=Modo de cópia de segurança inválido action.backup.msg=Você deseja gerar uma cópia de segurança do sistema antes de proceder ? diff --git a/bauh/view/resources/locale/ru b/bauh/view/resources/locale/ru index d9a834e7..b0234230 100644 --- a/bauh/view/resources/locale/ru +++ b/bauh/view/resources/locale/ru @@ -45,7 +45,7 @@ Ukraine=Украина United Kingdom=Великобритания United States=Соединённые Штаты Америки action.backup.error.create=It was not possible to generate a new system copy -action.backup.error.delete=It was not possible to delete the old system copies +action.backup.error.delete=It was not possible to delete all the old system copies action.backup.error.proceed=Proceed anyway ? action.backup.invalid_mode=Invalid backup mode action.backup.msg=Do you want to generate a system copy before proceeding ?