From 3ee8a35dedfaedfe2f7f0cd341b98c6f2fbe2ade Mon Sep 17 00:00:00 2001 From: Manu Date: Sat, 3 Nov 2018 16:55:38 +0800 Subject: [PATCH] Refactor BorgThread into new class to support different commands. --- README.md | 4 +- setup.cfg | 14 ++-- src/vorta/application.py | 21 ++++-- src/vorta/assets/icons/hdd-o-active.png | Bin 0 -> 18851 bytes src/vorta/borg/__init__.py | 0 src/vorta/{ => borg}/borg_thread.py | 65 +++++++++++++++++-- src/vorta/{borg_create.py => borg/create.py} | 32 +-------- src/vorta/borg/prune.py | 34 ++++++++++ src/vorta/models.py | 2 - src/vorta/scheduler.py | 2 +- src/vorta/tray_menu.py | 6 +- src/vorta/utils.py | 37 ++++++----- src/vorta/views/main_window.py | 22 +++---- src/vorta/views/repo_add.py | 31 ++++++--- src/vorta/views/repo_tab.py | 2 + src/vorta/views/snapshots_tab.py | 2 +- tests/fixtures.py | 6 -- tests/test_repo.py | 17 +++-- tests/test_schedule.py | 5 +- 19 files changed, 189 insertions(+), 113 deletions(-) create mode 100644 src/vorta/assets/icons/hdd-o-active.png create mode 100644 src/vorta/borg/__init__.py rename src/vorta/{ => borg}/borg_thread.py (63%) rename src/vorta/{borg_create.py => borg/create.py} (72%) create mode 100644 src/vorta/borg/prune.py diff --git a/README.md b/README.md index cd86f2b1..ca4b9a0e 100644 --- a/README.md +++ b/README.md @@ -42,7 +42,7 @@ $ vorta ## Development Install in development mode: ``` -$ pip install -e . +$ python setup.py develop ``` Then run via @@ -64,7 +64,7 @@ $ pyinstaller --clean --noconfirm vorta.spec ### Testing (work in progress) Tests are in the folder `/tests`. Testing happens at the level of UI components. Calls to `borg` are mocked and can be replaced with some example json-output. To run tests: ``` -$ pytest +$ python setup.py test ``` To update and view coverage information diff --git a/setup.cfg b/setup.cfg index 86264ae3..79729a3d 100644 --- a/setup.cfg +++ b/setup.cfg @@ -38,10 +38,8 @@ install_requires = python-dateutil keyring apscheduler - sentry_sdk - -[options.extras_require] -tests = + sentry-sdk +tests_require = pytest pytest-qt pytest-mock @@ -54,6 +52,9 @@ tests = gui_scripts = vorta = vorta.__main__:main +[aliases] +test=pytest + [tool:pytest] addopts = -vs testpaths = tests @@ -65,8 +66,11 @@ filterwarnings = source = src [tox:tox] -envlist = py37 +envlist = py36,py37 [tox:testenv] deps=pytest commands=pytest + +[tox:testenv:py36] +basepython = python3.4 diff --git a/src/vorta/application.py b/src/vorta/application.py index 18dcea6a..b34df148 100644 --- a/src/vorta/application.py +++ b/src/vorta/application.py @@ -1,13 +1,13 @@ -import sys -import os from PyQt5 import QtCore from PyQt5.QtWidgets import QApplication +from PyQt5.QtGui import QIcon from .tray_menu import TrayMenu from .scheduler import VortaScheduler -from .models import BackupProfileModel, SnapshotModel, BackupProfileMixin -from .borg_create import BorgCreateThread +from .models import BackupProfileMixin +from .borg.create import BorgCreateThread from .views.main_window import MainWindow +from .utils import get_asset class VortaApp(QApplication, BackupProfileMixin): @@ -33,11 +33,14 @@ class VortaApp(QApplication, BackupProfileMixin): self.main_window = MainWindow(self) self.main_window.show() + self.backup_started_event.connect(self.backup_started_event_response) + self.backup_finished_event.connect(self.backup_finished_event_response) + def create_backup_action(self): msg = BorgCreateThread.prepare() if msg['ok']: - self.thread = BorgCreateThread(msg['cmd'], msg['params'], parent=self) - self.thread.start() + thread = BorgCreateThread(msg['cmd'], msg['params'], parent=self) + thread.start() else: self.backup_log_event.emit(msg['message']) @@ -45,4 +48,10 @@ class VortaApp(QApplication, BackupProfileMixin): self.main_window.show() self.main_window.raise_() + def backup_started_event_response(self): + icon = QIcon(get_asset('icons/hdd-o-active.png')) + self.tray.setIcon(icon) + def backup_finished_event_response(self): + icon = QIcon(get_asset('icons/hdd-o.png')) + self.tray.setIcon(icon) diff --git a/src/vorta/assets/icons/hdd-o-active.png b/src/vorta/assets/icons/hdd-o-active.png new file mode 100644 index 0000000000000000000000000000000000000000..4c48ffd3eaa4580cdc4ffe828b747136b43a3970 GIT binary patch literal 18851 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 0: + ret['message'] = 'Current Wifi is not allowed.' + return ret + + if cls.prepare_bin() is None: + ret['message'] = 'Borg binary was not found.' + return ret + + ret['params'] = {'password': keyring.get_password("vorta-repo", profile.repo.url)} + + return ret + + @classmethod + def prepare_bin(cls): + """Find packaged borg binary. Prefer globally installed.""" + + # Look in current PATH. + if shutil.which('borg'): + return 'borg' + else: + # Look in pyinstaller package + cwd = getattr(sys, '_MEIPASS', os.getcwd()) + meipass_borg = os.path.join(cwd, 'bin', 'borg') + if os.path.isfile(meipass_borg): + return meipass_borg + else: + return None + def run(self): self.started_event() mutex.lock() diff --git a/src/vorta/borg_create.py b/src/vorta/borg/create.py similarity index 72% rename from src/vorta/borg_create.py rename to src/vorta/borg/create.py index 9e2a7a75..155dbdc3 100644 --- a/src/vorta/borg_create.py +++ b/src/vorta/borg/create.py @@ -4,8 +4,7 @@ import platform from dateutil import parser from datetime import datetime as dt -from .models import SourceDirModel, BackupProfileModel, WifiSettingModel, SnapshotModel, BackupProfileMixin -from .utils import get_current_wifi, keyring +from vorta.models import SourceDirModel, SnapshotModel, BackupProfileModel, BackupProfileMixin from .borg_thread import BorgThread @@ -49,34 +48,10 @@ class BorgCreateThread(BorgThread, BackupProfileMixin): Centralize it here and return the required arguments to the caller. """ profile = BackupProfileModel.get(id=1) - - ret = {'ok': False} - - if cls.is_running(): - ret['message'] = 'Backup is already in progress.' + ret = super().prepare() + if not ret['ok']: return ret - if profile.repo is None: - ret['message'] = 'Add a remote backup repository first.' - return ret - - n_backup_folders = SourceDirModel.select().count() - if n_backup_folders == 0: - ret['message'] = 'Add some folders to back up first.' - return ret - - current_wifi = get_current_wifi() - if current_wifi is not None: - wifi_is_disallowed = WifiSettingModel.select().where( - (WifiSettingModel.ssid == current_wifi) - & (WifiSettingModel.allowed == False) - & (WifiSettingModel.profile == profile.id) - ) - if wifi_is_disallowed.count() > 0: - ret['message'] = 'Current Wifi is not allowed.' - return ret - - params = {'password': keyring.get_password("vorta-repo", profile.repo.url)} cmd = ['borg', 'create', '--list', '--info', '--log-json', '--json', '-C', profile.compression] # Add excludes @@ -108,6 +83,5 @@ class BorgCreateThread(BorgThread, BackupProfileMixin): ret['message'] = 'Starting backup..' ret['ok'] = True ret['cmd'] = cmd - ret['params'] = params return ret diff --git a/src/vorta/borg/prune.py b/src/vorta/borg/prune.py new file mode 100644 index 00000000..5a2a0984 --- /dev/null +++ b/src/vorta/borg/prune.py @@ -0,0 +1,34 @@ +from .borg_thread import BorgThread + + +class BorgPruneThread(BorgThread): + def process_result(self, result): + pass + + def log_event(self, msg): + self.app.backup_log_event.emit(msg) + + def started_event(self): + self.app.backup_started_event.emit() + self.app.backup_log_event.emit('Backup started.') + + def finished_event(self, result): + self.app.backup_finished_event.emit(result) + + @classmethod + def prepare(cls): + ret, params, profile = super().prepare() + cmd = ['borg', 'prune', '--list', '--stats', '--info', '--log-json', '--json', ] + + # -H, --keep-hourly number of hourly archives to keep + # -d, --keep-daily number of daily archives to keep + # -w, --keep-weekly number of weekly archives to keep + # -m, --keep-monthly number of monthly archives to keep + # -y, --keep-yearly number of yearly archives to keep + + ret['message'] = 'Pruning repository..' + ret['ok'] = True + ret['cmd'] = cmd + ret['params'] = params + + return ret diff --git a/src/vorta/models.py b/src/vorta/models.py index 149447cc..9e3c2de6 100644 --- a/src/vorta/models.py +++ b/src/vorta/models.py @@ -163,8 +163,6 @@ def init_db(con): else: migrator = SqliteMigrator(con) - if current_schema.version < 3: # version 2 to 3 - pass if current_schema.version < 4: # version 3 to 4 _apply_schema_update( diff --git a/src/vorta/scheduler.py b/src/vorta/scheduler.py index 5e907122..a0afe643 100644 --- a/src/vorta/scheduler.py +++ b/src/vorta/scheduler.py @@ -1,7 +1,7 @@ from apscheduler.schedulers.qt import QtScheduler from apscheduler.triggers import cron -from .borg_create import BorgCreateThread +from vorta.borg.create import BorgCreateThread from .models import BackupProfileMixin diff --git a/src/vorta/tray_menu.py b/src/vorta/tray_menu.py index 0485f117..16594746 100644 --- a/src/vorta/tray_menu.py +++ b/src/vorta/tray_menu.py @@ -1,10 +1,8 @@ -from PyQt5 import QtWidgets, QtGui, QtCore -from PyQt5.QtWidgets import QMenu, QApplication, QSystemTrayIcon, QMessageBox, QDialog -from .views.main_window import MainWindow +from PyQt5.QtWidgets import QMenu, QSystemTrayIcon from PyQt5.QtGui import QIcon from .utils import get_asset -from .borg_thread import BorgThread +from vorta.borg.borg_thread import BorgThread class TrayMenu(QSystemTrayIcon): diff --git a/src/vorta/utils.py b/src/vorta/utils.py index e60c9153..056f0db6 100644 --- a/src/vorta/utils.py +++ b/src/vorta/utils.py @@ -77,15 +77,16 @@ def get_sorted_wifis(): """Get SSIDs from OS and merge with settings in DB.""" app = QApplication.instance() - plist_file = open('/Library/Preferences/SystemConfiguration/com.apple.airport.preferences.plist', 'rb') - wifis = plistlib.load(plist_file)['KnownNetworks'] - if wifis: - for wifi in wifis.values(): - timestamp = wifi.get('LastConnected', None) - ssid = wifi['SSIDString'] - WifiSettingModel.get_or_create(ssid=ssid, profile=app.profile, - defaults={'last_connected': timestamp, - 'allowed': True}) + if sys.platform == 'darwin': + plist_file = open('/Library/Preferences/SystemConfiguration/com.apple.airport.preferences.plist', 'rb') + wifis = plistlib.load(plist_file)['KnownNetworks'] + if wifis: + for wifi in wifis.values(): + timestamp = wifi.get('LastConnected', None) + ssid = wifi['SSIDString'] + WifiSettingModel.get_or_create(ssid=ssid, profile=app.profile, + defaults={'last_connected': timestamp, + 'allowed': True}) return WifiSettingModel.select().order_by(-WifiSettingModel.last_connected) @@ -96,11 +97,13 @@ def get_current_wifi(): From https://gist.github.com/keithweaver/00edf356e8194b89ed8d3b7bbead000c """ - cmd = ['/System/Library/PrivateFrameworks/Apple80211.framework/Versions/Current/Resources/airport','-I'] - process = subprocess.Popen(cmd, stdout=subprocess.PIPE) - out, err = process.communicate() - process.wait() - for line in out.decode("utf-8").split('\n'): - split_line = line.strip().split(':') - if split_line[0] == 'SSID': - return split_line[1].strip() + + if sys.platform == 'darwin': + cmd = ['/System/Library/PrivateFrameworks/Apple80211.framework/Versions/Current/Resources/airport','-I'] + process = subprocess.Popen(cmd, stdout=subprocess.PIPE) + out, err = process.communicate() + process.wait() + for line in out.decode("utf-8").split('\n'): + split_line = line.strip().split(':') + if split_line[0] == 'SSID': + return split_line[1].strip() diff --git a/src/vorta/views/main_window.py b/src/vorta/views/main_window.py index 63dc50e7..a8375425 100644 --- a/src/vorta/views/main_window.py +++ b/src/vorta/views/main_window.py @@ -7,7 +7,7 @@ from .source_tab import SourceTab from .snapshots_tab import SnapshotTab from .schedule_tab import ScheduleTab from ..utils import get_asset -from ..borg_thread import BorgThread +from vorta.borg.borg_thread import BorgThread uifile = get_asset('UI/mainwindow.ui') @@ -55,24 +55,24 @@ class MainWindow(MainWindowBase, MainWindowUI): self.createProgress.setRange(0, progress_max) self.createProgressText.repaint() + def _toggle_buttons(self, create_enabled=True): + self.createStartBtn.setEnabled(create_enabled) + self.createStartBtn.repaint() + self.cancelButton.setEnabled(not create_enabled) + self.cancelButton.repaint() + def backup_started_event(self): self.set_status(progress_max=0) - self.createStartBtn.setEnabled(False) - self.createStartBtn.repaint() - self.cancelButton.setEnabled(True) - self.cancelButton.repaint() + self._toggle_buttons(create_enabled=False) def backup_finished_event(self): - self.createStartBtn.setEnabled(True) - self.createStartBtn.repaint() self.set_status(progress_max=100) + self._toggle_buttons(create_enabled=True) self.snapshotTab.populate() + self.repoTab.init_repo_stats() def backup_cancelled_event(self): - self.createStartBtn.setEnabled(True) - self.createStartBtn.repaint() - self.cancelButton.setEnabled(False) - self.cancelButton.repaint() + self._toggle_buttons(create_enabled=True) self.set_status(progress_max=100) self.set_status('Backup cancelled') diff --git a/src/vorta/views/repo_add.py b/src/vorta/views/repo_add.py index dd54fcb5..834b5401 100644 --- a/src/vorta/views/repo_add.py +++ b/src/vorta/views/repo_add.py @@ -1,6 +1,6 @@ -from PyQt5 import uic, QtCore +from PyQt5 import uic from ..utils import get_private_keys, get_asset -from ..borg_thread import BorgThread +from vorta.borg.borg_thread import BorgThread uifile = get_asset('UI/repoadd.ui') AddRepoUI, AddRepoBase = uic.loadUiType(uifile) @@ -8,7 +8,7 @@ AddRepoUI, AddRepoBase = uic.loadUiType(uifile) class AddRepoWindow(AddRepoBase, AddRepoUI): connection_message = 'Setting up new repo...' - cmd = ["borg", "init", "--log-json"] + cmd = ["borg", "init", "--info", "--json", "--log-json"] def __init__(self, parent=None): super().__init__(parent) @@ -33,20 +33,24 @@ class AddRepoWindow(AddRepoBase, AddRepoUI): return out def run(self): + self.saveButton.setEnabled(False) if self.validate(): - self.set_status(self.connection_message) + self._set_status(self.connection_message) cmd = self.cmd + [self.values['repo_url']] thread = BorgThread(cmd, self.values, parent=self) - thread.updated.connect(self.set_status) + thread.updated.connect(self._set_status) thread.result.connect(self.run_result) - self.thread = thread + self.thread = thread # Needs to be connected for tests to work. self.thread.start() + else: + self.saveButton.setEnabled(True) - def set_status(self, text): + def _set_status(self, text): self.errorText.setText(text) self.errorText.repaint() def run_result(self, result): + self.saveButton.setEnabled(True) if result['returncode'] == 0: self.result = result self.accept() @@ -64,13 +68,20 @@ class AddRepoWindow(AddRepoBase, AddRepoUI): self.sshComboBox.addItem(f'{key["filename"]} ({key["format"]}:{key["fingerprint"]})', key['filename']) def validate(self): + """Pre-flight check for valid input and borg binary.""" + + # TODO: valid repo is xx.xx:xx. add rex if len(self.values['repo_url']) < 5 or ':' not in self.values['repo_url']: - self.set_status('Please enter a valid repo URL including hostname and path.') + self._set_status('Please enter a valid repo URL including hostname and path.') + return False + + if BorgThread.prepare_bin() is None: + self._set_status('Borg binary was not found.') return False if self.__class__ == AddRepoWindow: if self.values['encryption'] != 'none' and len(self.values['password']) < 8: - self.set_status('Please use a longer password.') + self._set_status('Please use a longer password.') return False self.cmd.append(f"--encryption={self.values['encryption']}") @@ -80,7 +91,7 @@ class AddRepoWindow(AddRepoBase, AddRepoUI): class ExistingRepoWindow(AddRepoWindow): connection_message = 'Validating existing repo...' - cmd = ["borg", "list", "--json"] + cmd = ["borg", "list", "--info", "--json", "--log-json"] def __init__(self): super().__init__() diff --git a/src/vorta/views/repo_tab.py b/src/vorta/views/repo_tab.py index eaf9ad66..75014887 100644 --- a/src/vorta/views/repo_tab.py +++ b/src/vorta/views/repo_tab.py @@ -156,6 +156,7 @@ class RepoTab(RepoBase, RepoUI, BackupProfileMixin): self.repoSelector.addItem(new_repo.url, new_repo.id) self.repoSelector.setCurrentIndex(self.repoSelector.count()-1) self.repo_changed.emit() + self.init_repo_stats() def repo_unlink_action(self): profile = self.profile @@ -177,5 +178,6 @@ class RepoTab(RepoBase, RepoUI, BackupProfileMixin): msg.exec_() self.repo_changed.emit() + self.init_repo_stats() diff --git a/src/vorta/views/snapshots_tab.py b/src/vorta/views/snapshots_tab.py index 8853d2df..d79ddcac 100644 --- a/src/vorta/views/snapshots_tab.py +++ b/src/vorta/views/snapshots_tab.py @@ -2,7 +2,7 @@ from datetime import timedelta from PyQt5 import uic from PyQt5.QtWidgets import QFileDialog, QTableWidgetItem, QTableView, QHeaderView -from ..borg_thread import BorgThread +from vorta.borg.borg_thread import BorgThread from ..utils import get_asset, keyring, pretty_bytes from ..models import BackupProfileMixin diff --git a/tests/fixtures.py b/tests/fixtures.py index 45b1ea62..b5b4f24e 100644 --- a/tests/fixtures.py +++ b/tests/fixtures.py @@ -11,9 +11,3 @@ def app(tmpdir): mock_db = peewee.SqliteDatabase(str(tmp_db)) vorta.models.init_db(mock_db) return VortaApp([]) - - -@pytest.fixture() -def main(app): - main = app.main_window - return main diff --git a/tests/test_repo.py b/tests/test_repo.py index b2c3c8db..4771bcf0 100644 --- a/tests/test_repo.py +++ b/tests/test_repo.py @@ -1,25 +1,23 @@ - -import pytest import io from PyQt5 import QtCore -import vorta.borg_thread +import vorta.borg.borg_thread import vorta.models from vorta.views.repo_add import AddRepoWindow from vorta.models import EventLogModel, RepoModel from .fixtures import * -def test_repo_tab(main, qtbot): +def test_repo_tab(app, qtbot): + main = app.main_window qtbot.mouseClick(main.createStartBtn, QtCore.Qt.LeftButton) assert main.createProgressText.text() == 'Add a remote backup repository first.' -def test_repo_add(main, qtbot, mocker): +def test_repo_add(app, qtbot, mocker): # Add new repo window - add_repo_window = AddRepoWindow(main) - qtbot.addWidget(add_repo_window) - add_repo_window.show() + main = app.main_window + add_repo_window = AddRepoWindow(main.repoTab) qtbot.keyClicks(add_repo_window.repoURL, 'aaa') qtbot.mouseClick(add_repo_window.saveButton, QtCore.Qt.LeftButton) assert add_repo_window.errorText.text() == 'Please enter a valid repo URL including hostname and path.' @@ -33,7 +31,7 @@ def test_repo_add(main, qtbot, mocker): popen_result =mocker.MagicMock(stdout=io.StringIO("some initial binary data"), stderr=io.StringIO("some initial binary data"), returncode=0) - mocker.patch.object(vorta.borg_thread, 'Popen', return_value=popen_result) + mocker.patch.object(vorta.borg.borg_thread, 'Popen', return_value=popen_result) qtbot.mouseClick(add_repo_window.saveButton, QtCore.Qt.LeftButton) @@ -44,3 +42,4 @@ def test_repo_add(main, qtbot, mocker): assert EventLogModel.select().count() == 1 assert RepoModel.get(id=1).url == 'aaabbb.com:repo' + diff --git a/tests/test_schedule.py b/tests/test_schedule.py index 00a0b1f5..4dbff173 100644 --- a/tests/test_schedule.py +++ b/tests/test_schedule.py @@ -6,9 +6,8 @@ from vorta.views.schedule_tab import ScheduleTab from .fixtures import * -def test_schedule_tab(main, qtbot): - tab = ScheduleTab(main.scheduleTabSlot) - # qtbot.addWidget(tab) +def test_schedule_tab(app, qtbot): + tab = ScheduleTab(app.main_window.scheduleTabSlot) qtbot.mouseClick(tab.scheduleApplyButton, QtCore.Qt.LeftButton) assert tab.nextBackupDateTimeLabel.text() == 'Manual Backups'