1
0
Fork 0
mirror of https://github.com/borgbase/vorta synced 2025-03-15 08:29:42 +00:00

Improve the connection between BorgThread and all other Widgets to show an accurate status. Allow copying public SSH key to clipboard.

This commit is contained in:
Manu 2018-11-02 18:35:31 +08:00
parent 0da0af1dae
commit 54cd7c9dc9
9 changed files with 305 additions and 270 deletions

View file

@ -17,6 +17,7 @@ Vorta is a open source macOS/Linux GUI for [BorgBackup](https://borgbackup.readt
Planned features:
- [ ] Support multiple backup profiles (in progress)
- [ ] Repo pruning
- [ ] Repo checking
- [ ] Full test coverage (currently: 65%)

View file

@ -11,45 +11,37 @@ from .views.main_window import MainWindow
class VortaApp(QApplication, BackupProfileMixin):
backup_done = QtCore.pyqtSignal()
backup_log = QtCore.pyqtSignal(str)
"""
All windows and QWidgets are children of this app.
When running Borg-commands, the class `BorgThread` will emit events
via the `VortaApp` class to which other windows will subscribe to.
"""
backup_started_event = QtCore.pyqtSignal()
backup_finished_event = QtCore.pyqtSignal(dict)
backup_cancelled_event = QtCore.pyqtSignal()
backup_log_event = QtCore.pyqtSignal(str)
def __init__(self, args):
super().__init__(args)
self.setQuitOnLastWindowClosed(False)
self.scheduler = VortaScheduler(self)
# Prepare tray and connect events.
# Prepare tray and main window
self.tray = TrayMenu(self)
self.tray.start_backup.connect(self.create_backup)
self.tray.open_main_window.connect(self.on_open_main_window)
# Prepare main window
self.main_window = MainWindow(self)
self.main_window.show()
if not getattr(sys, 'frozen', False):
self.main_window.show()
def cancel_backup(self):
"""Can't cancel background backups."""
if self.thread and self.thread.isRunning():
self.thread.mutex.unlock()
self.thread.process.kill()
self.thread.terminate()
def create_backup(self):
msg = BorgThread.prepare_runner()
def create_backup_action(self):
msg = BorgThread.prepare_create_cmd()
if msg['ok']:
self.thread = BorgThread(msg['cmd'], msg['params'], parent=self)
self.thread.updated.connect(self.backup_log.emit)
self.thread.result.connect(self.create_backup_result)
self.thread.start()
return msg
def on_open_main_window(self):
def open_main_window_action(self):
self.main_window.show()
self.main_window.raise_()
def create_backup_result(self, result):
self.backup_done.emit()

View file

@ -169,6 +169,9 @@
</item>
<item row="1" column="0">
<widget class="QPushButton" name="cancelButton">
<property name="enabled">
<bool>false</bool>
</property>
<property name="font">
<font>
<pointsize>11</pointsize>

View file

@ -14,21 +14,24 @@ from .models import SourceDirModel, BackupProfileModel, EventLogModel, WifiSetti
from .utils import get_current_wifi, keyring
class BorgThread(QtCore.QThread, BackupProfileMixin):
updated = QtCore.pyqtSignal(str)
result = QtCore.pyqtSignal(object)
mutex = QtCore.QMutex()
def __init__(self, cmd, params, parent=None):
"""
Thread to run Borg operations in.
Thread to run Borg operations in. It will connect to the main app instance and
emit events via it.
Functions, like `prepare_create_cmd` and `process_create_result` are structured
around Borg subcommands. They may move to their own class in the future.
:param cmd: Borg command line
:param params: To pass extra options that are later formatted centrally.
:param parent: Parent window. Needs `thread.wait()` if none.
:param parent: Parent window. Needs `thread.wait()` if none. (scheduler)
"""
super().__init__(parent)
self.app = QApplication.instance()
self.app.backup_cancelled_event.connect(self.cancel)
# Find packaged borg binary. Prefer globally installed.
if not shutil.which('borg'):
@ -59,6 +62,8 @@ class BorgThread(QtCore.QThread, BackupProfileMixin):
return True
def run(self):
self.app.backup_started_event.emit()
self.app.backup_log_event.emit('Backup started.')
self.mutex.lock()
log_entry = EventLogModel(category='borg-run', subcommand=self.cmd[1])
log_entry.save()
@ -67,11 +72,11 @@ class BorgThread(QtCore.QThread, BackupProfileMixin):
try:
parsed = json.loads(line)
if parsed['type'] == 'log_message':
self.updated.emit(f'{parsed["levelname"]}: {parsed["message"]}')
self.app.backup_log_event.emit(f'{parsed["levelname"]}: {parsed["message"]}')
elif parsed['type'] == 'file_status':
self.updated.emit(f'{parsed["path"]} ({parsed["status"]})')
self.app.backup_log_event.emit(f'{parsed["path"]} ({parsed["status"]})')
except json.decoder.JSONDecodeError:
self.updated.emit(line.strip())
self.app.backup_log_event.emit(line.strip())
self.process.wait()
stdout = self.process.stdout.read()
@ -93,9 +98,15 @@ class BorgThread(QtCore.QThread, BackupProfileMixin):
if hasattr(self, result_func):
getattr(self, result_func)(result)
self.result.emit(result)
self.app.backup_finished_event.emit(result)
self.mutex.unlock()
def cancel(self):
if self.isRunning():
self.mutex.unlock()
self.process.kill()
self.terminate()
def process_create_result(self, result):
if result['returncode'] == 0:
new_snapshot, created = SnapshotModel.get_or_create(
@ -119,7 +130,7 @@ class BorgThread(QtCore.QThread, BackupProfileMixin):
repo.save()
@classmethod
def prepare_runner(cls):
def prepare_create_cmd(cls):
"""
`borg create` is called from different places and needs some preparation.
Centralize it here and return the required arguments to the caller.

View file

@ -35,7 +35,7 @@ class VortaScheduler(QtScheduler, BackupProfileMixin):
@classmethod
def create_backup(cls):
msg = BorgThread.prepare_runner()
msg = BorgThread.prepare_create_cmd()
if msg['ok']:
thread = BorgThread(msg['cmd'], msg['params'])
thread.start()

View file

@ -8,9 +8,6 @@ from .borg_runner import BorgThread
class TrayMenu(QSystemTrayIcon):
start_backup = QtCore.pyqtSignal()
open_main_window = QtCore.pyqtSignal()
def __init__(self, parent=None):
icon = QIcon(get_asset('icons/hdd-o.png'))
QSystemTrayIcon.__init__(self, icon, parent)
@ -21,15 +18,19 @@ class TrayMenu(QSystemTrayIcon):
self.status.setEnabled(False)
self.create_action = menu.addAction("Backup Now")
self.create_action.triggered.connect(self.start_backup.emit)
self.create_action.triggered.connect(self.app.create_backup_action)
self.cancel_action = menu.addAction("Cancel Backup")
self.cancel_action.triggered.connect(self.app.backup_cancelled_event.emit)
self.cancel_action.setVisible(False)
settings_action = menu.addAction("Settings")
settings_action.triggered.connect(self.open_main_window.emit)
settings_action.triggered.connect(self.app.open_main_window_action)
menu.addSeparator()
exit_action = menu.addAction("Exit")
exit_action.triggered.connect(self.on_exit_action)
exit_action.triggered.connect(self.app.quit)
self.activated.connect(self.on_user_click)
@ -37,14 +38,13 @@ class TrayMenu(QSystemTrayIcon):
self.setVisible(True)
self.show()
def on_exit_action(self):
self.app.quit()
def on_user_click(self):
"""Adjust labels to reflect current status."""
if BorgThread.is_running():
self.status.setText('Backup in Progress')
self.create_action.setText('Cancel Backup')
self.create_action.setVisible(False)
self.cancel_action.setVisible(True)
else:
self.status.setText(f'Next Task: {self.app.scheduler.next_job}')
self.create_action.setText('Backup Now')
self.create_action.setVisible(True)
self.cancel_action.setVisible(False)

View file

@ -9,6 +9,40 @@
from PyQt5 import QtCore
qt_resource_data = b"\
\x00\x00\x01\xfb\
\x3c\
\x3f\x78\x6d\x6c\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\
\x30\x22\x20\x65\x6e\x63\x6f\x64\x69\x6e\x67\x3d\x22\x75\x74\x66\
\x2d\x38\x22\x3f\x3e\x0a\x3c\x73\x76\x67\x20\x77\x69\x64\x74\x68\
\x3d\x22\x31\x37\x39\x32\x22\x20\x68\x65\x69\x67\x68\x74\x3d\x22\
\x31\x37\x39\x32\x22\x20\x76\x69\x65\x77\x42\x6f\x78\x3d\x22\x30\
\x20\x30\x20\x31\x37\x39\x32\x20\x31\x37\x39\x32\x22\x20\x78\x6d\
\x6c\x6e\x73\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\
\x77\x33\x2e\x6f\x72\x67\x2f\x32\x30\x30\x30\x2f\x73\x76\x67\x22\
\x3e\x3c\x70\x61\x74\x68\x20\x64\x3d\x22\x4d\x31\x32\x38\x20\x31\
\x34\x30\x38\x68\x31\x30\x32\x34\x76\x2d\x31\x32\x38\x68\x2d\x31\
\x30\x32\x34\x76\x31\x32\x38\x7a\x6d\x30\x2d\x35\x31\x32\x68\x31\
\x30\x32\x34\x76\x2d\x31\x32\x38\x68\x2d\x31\x30\x32\x34\x76\x31\
\x32\x38\x7a\x6d\x31\x35\x36\x38\x20\x34\x34\x38\x71\x30\x2d\x34\
\x30\x2d\x32\x38\x2d\x36\x38\x74\x2d\x36\x38\x2d\x32\x38\x2d\x36\
\x38\x20\x32\x38\x2d\x32\x38\x20\x36\x38\x20\x32\x38\x20\x36\x38\
\x20\x36\x38\x20\x32\x38\x20\x36\x38\x2d\x32\x38\x20\x32\x38\x2d\
\x36\x38\x7a\x6d\x2d\x31\x35\x36\x38\x2d\x39\x36\x30\x68\x31\x30\
\x32\x34\x76\x2d\x31\x32\x38\x68\x2d\x31\x30\x32\x34\x76\x31\x32\
\x38\x7a\x6d\x31\x35\x36\x38\x20\x34\x34\x38\x71\x30\x2d\x34\x30\
\x2d\x32\x38\x2d\x36\x38\x74\x2d\x36\x38\x2d\x32\x38\x2d\x36\x38\
\x20\x32\x38\x2d\x32\x38\x20\x36\x38\x20\x32\x38\x20\x36\x38\x20\
\x36\x38\x20\x32\x38\x20\x36\x38\x2d\x32\x38\x20\x32\x38\x2d\x36\
\x38\x7a\x6d\x30\x2d\x35\x31\x32\x71\x30\x2d\x34\x30\x2d\x32\x38\
\x2d\x36\x38\x74\x2d\x36\x38\x2d\x32\x38\x2d\x36\x38\x20\x32\x38\
\x2d\x32\x38\x20\x36\x38\x20\x32\x38\x20\x36\x38\x20\x36\x38\x20\
\x32\x38\x20\x36\x38\x2d\x32\x38\x20\x32\x38\x2d\x36\x38\x7a\x6d\
\x39\x36\x20\x38\x33\x32\x76\x33\x38\x34\x68\x2d\x31\x37\x39\x32\
\x76\x2d\x33\x38\x34\x68\x31\x37\x39\x32\x7a\x6d\x30\x2d\x35\x31\
\x32\x76\x33\x38\x34\x68\x2d\x31\x37\x39\x32\x76\x2d\x33\x38\x34\
\x68\x31\x37\x39\x32\x7a\x6d\x30\x2d\x35\x31\x32\x76\x33\x38\x34\
\x68\x2d\x31\x37\x39\x32\x76\x2d\x33\x38\x34\x68\x31\x37\x39\x32\
\x7a\x22\x2f\x3e\x3c\x2f\x73\x76\x67\x3e\
\x00\x00\x03\xfa\
\x3c\
\x3f\x78\x6d\x6c\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\
@ -75,76 +109,6 @@ qt_resource_data = b"\
\x71\x31\x30\x2d\x39\x20\x32\x33\x2d\x39\x74\x32\x33\x20\x39\x71\
\x39\x20\x31\x30\x20\x39\x20\x32\x33\x74\x2d\x39\x20\x32\x33\x7a\
\x22\x2f\x3e\x3c\x2f\x73\x76\x67\x3e\
\x00\x00\x02\x12\
\x3c\
\x3f\x78\x6d\x6c\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\
\x30\x22\x20\x65\x6e\x63\x6f\x64\x69\x6e\x67\x3d\x22\x75\x74\x66\
\x2d\x38\x22\x3f\x3e\x0a\x3c\x73\x76\x67\x20\x77\x69\x64\x74\x68\
\x3d\x22\x31\x37\x39\x32\x22\x20\x68\x65\x69\x67\x68\x74\x3d\x22\
\x31\x37\x39\x32\x22\x20\x76\x69\x65\x77\x42\x6f\x78\x3d\x22\x30\
\x20\x30\x20\x31\x37\x39\x32\x20\x31\x37\x39\x32\x22\x20\x78\x6d\
\x6c\x6e\x73\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\
\x77\x33\x2e\x6f\x72\x67\x2f\x32\x30\x30\x30\x2f\x73\x76\x67\x22\
\x3e\x3c\x70\x61\x74\x68\x20\x64\x3d\x22\x4d\x31\x30\x32\x34\x20\
\x31\x34\x30\x38\x68\x36\x34\x30\x76\x2d\x31\x32\x38\x68\x2d\x36\
\x34\x30\x76\x31\x32\x38\x7a\x6d\x2d\x33\x38\x34\x2d\x35\x31\x32\
\x68\x31\x30\x32\x34\x76\x2d\x31\x32\x38\x68\x2d\x31\x30\x32\x34\
\x76\x31\x32\x38\x7a\x6d\x36\x34\x30\x2d\x35\x31\x32\x68\x33\x38\
\x34\x76\x2d\x31\x32\x38\x68\x2d\x33\x38\x34\x76\x31\x32\x38\x7a\
\x6d\x35\x31\x32\x20\x38\x33\x32\x76\x32\x35\x36\x71\x30\x20\x32\
\x36\x2d\x31\x39\x20\x34\x35\x74\x2d\x34\x35\x20\x31\x39\x68\x2d\
\x31\x36\x36\x34\x71\x2d\x32\x36\x20\x30\x2d\x34\x35\x2d\x31\x39\
\x74\x2d\x31\x39\x2d\x34\x35\x76\x2d\x32\x35\x36\x71\x30\x2d\x32\
\x36\x20\x31\x39\x2d\x34\x35\x74\x34\x35\x2d\x31\x39\x68\x31\x36\
\x36\x34\x71\x32\x36\x20\x30\x20\x34\x35\x20\x31\x39\x74\x31\x39\
\x20\x34\x35\x7a\x6d\x30\x2d\x35\x31\x32\x76\x32\x35\x36\x71\x30\
\x20\x32\x36\x2d\x31\x39\x20\x34\x35\x74\x2d\x34\x35\x20\x31\x39\
\x68\x2d\x31\x36\x36\x34\x71\x2d\x32\x36\x20\x30\x2d\x34\x35\x2d\
\x31\x39\x74\x2d\x31\x39\x2d\x34\x35\x76\x2d\x32\x35\x36\x71\x30\
\x2d\x32\x36\x20\x31\x39\x2d\x34\x35\x74\x34\x35\x2d\x31\x39\x68\
\x31\x36\x36\x34\x71\x32\x36\x20\x30\x20\x34\x35\x20\x31\x39\x74\
\x31\x39\x20\x34\x35\x7a\x6d\x30\x2d\x35\x31\x32\x76\x32\x35\x36\
\x71\x30\x20\x32\x36\x2d\x31\x39\x20\x34\x35\x74\x2d\x34\x35\x20\
\x31\x39\x68\x2d\x31\x36\x36\x34\x71\x2d\x32\x36\x20\x30\x2d\x34\
\x35\x2d\x31\x39\x74\x2d\x31\x39\x2d\x34\x35\x76\x2d\x32\x35\x36\
\x71\x30\x2d\x32\x36\x20\x31\x39\x2d\x34\x35\x74\x34\x35\x2d\x31\
\x39\x68\x31\x36\x36\x34\x71\x32\x36\x20\x30\x20\x34\x35\x20\x31\
\x39\x74\x31\x39\x20\x34\x35\x7a\x22\x2f\x3e\x3c\x2f\x73\x76\x67\
\x3e\
\x00\x00\x01\xfb\
\x3c\
\x3f\x78\x6d\x6c\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\
\x30\x22\x20\x65\x6e\x63\x6f\x64\x69\x6e\x67\x3d\x22\x75\x74\x66\
\x2d\x38\x22\x3f\x3e\x0a\x3c\x73\x76\x67\x20\x77\x69\x64\x74\x68\
\x3d\x22\x31\x37\x39\x32\x22\x20\x68\x65\x69\x67\x68\x74\x3d\x22\
\x31\x37\x39\x32\x22\x20\x76\x69\x65\x77\x42\x6f\x78\x3d\x22\x30\
\x20\x30\x20\x31\x37\x39\x32\x20\x31\x37\x39\x32\x22\x20\x78\x6d\
\x6c\x6e\x73\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\
\x77\x33\x2e\x6f\x72\x67\x2f\x32\x30\x30\x30\x2f\x73\x76\x67\x22\
\x3e\x3c\x70\x61\x74\x68\x20\x64\x3d\x22\x4d\x31\x30\x32\x34\x20\
\x35\x34\x34\x76\x34\x34\x38\x71\x30\x20\x31\x34\x2d\x39\x20\x32\
\x33\x74\x2d\x32\x33\x20\x39\x68\x2d\x33\x32\x30\x71\x2d\x31\x34\
\x20\x30\x2d\x32\x33\x2d\x39\x74\x2d\x39\x2d\x32\x33\x76\x2d\x36\
\x34\x71\x30\x2d\x31\x34\x20\x39\x2d\x32\x33\x74\x32\x33\x2d\x39\
\x68\x32\x32\x34\x76\x2d\x33\x35\x32\x71\x30\x2d\x31\x34\x20\x39\
\x2d\x32\x33\x74\x32\x33\x2d\x39\x68\x36\x34\x71\x31\x34\x20\x30\
\x20\x32\x33\x20\x39\x74\x39\x20\x32\x33\x7a\x6d\x34\x31\x36\x20\
\x33\x35\x32\x71\x30\x2d\x31\x34\x38\x2d\x37\x33\x2d\x32\x37\x33\
\x74\x2d\x31\x39\x38\x2d\x31\x39\x38\x2d\x32\x37\x33\x2d\x37\x33\
\x2d\x32\x37\x33\x20\x37\x33\x2d\x31\x39\x38\x20\x31\x39\x38\x2d\
\x37\x33\x20\x32\x37\x33\x20\x37\x33\x20\x32\x37\x33\x20\x31\x39\
\x38\x20\x31\x39\x38\x20\x32\x37\x33\x20\x37\x33\x20\x32\x37\x33\
\x2d\x37\x33\x20\x31\x39\x38\x2d\x31\x39\x38\x20\x37\x33\x2d\x32\
\x37\x33\x7a\x6d\x32\x32\x34\x20\x30\x71\x30\x20\x32\x30\x39\x2d\
\x31\x30\x33\x20\x33\x38\x35\x2e\x35\x74\x2d\x32\x37\x39\x2e\x35\
\x20\x32\x37\x39\x2e\x35\x2d\x33\x38\x35\x2e\x35\x20\x31\x30\x33\
\x2d\x33\x38\x35\x2e\x35\x2d\x31\x30\x33\x2d\x32\x37\x39\x2e\x35\
\x2d\x32\x37\x39\x2e\x35\x2d\x31\x30\x33\x2d\x33\x38\x35\x2e\x35\
\x20\x31\x30\x33\x2d\x33\x38\x35\x2e\x35\x20\x32\x37\x39\x2e\x35\
\x2d\x32\x37\x39\x2e\x35\x20\x33\x38\x35\x2e\x35\x2d\x31\x30\x33\
\x20\x33\x38\x35\x2e\x35\x20\x31\x30\x33\x20\x32\x37\x39\x2e\x35\
\x20\x32\x37\x39\x2e\x35\x20\x31\x30\x33\x20\x33\x38\x35\x2e\x35\
\x7a\x22\x2f\x3e\x3c\x2f\x73\x76\x67\x3e\
\x00\x00\x03\x19\
\x3c\
\x3f\x78\x6d\x6c\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\
@ -197,6 +161,139 @@ qt_resource_data = b"\
\x31\x30\x20\x31\x30\x20\x32\x33\x20\x30\x20\x31\x38\x2d\x37\x35\
\x2e\x35\x20\x39\x33\x74\x2d\x39\x32\x2e\x35\x20\x37\x35\x7a\x22\
\x2f\x3e\x3c\x2f\x73\x76\x67\x3e\
\x00\x00\x01\x89\
\x3c\
\x3f\x78\x6d\x6c\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\
\x30\x22\x20\x65\x6e\x63\x6f\x64\x69\x6e\x67\x3d\x22\x75\x74\x66\
\x2d\x38\x22\x3f\x3e\x0a\x3c\x73\x76\x67\x20\x77\x69\x64\x74\x68\
\x3d\x22\x32\x30\x34\x38\x22\x20\x68\x65\x69\x67\x68\x74\x3d\x22\
\x31\x37\x39\x32\x22\x20\x76\x69\x65\x77\x42\x6f\x78\x3d\x22\x30\
\x20\x30\x20\x32\x30\x34\x38\x20\x31\x37\x39\x32\x22\x20\x78\x6d\
\x6c\x6e\x73\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\
\x77\x33\x2e\x6f\x72\x67\x2f\x32\x30\x30\x30\x2f\x73\x76\x67\x22\
\x3e\x3c\x70\x61\x74\x68\x20\x64\x3d\x22\x4d\x32\x35\x36\x20\x31\
\x35\x33\x36\x68\x37\x36\x38\x76\x2d\x35\x31\x32\x68\x2d\x37\x36\
\x38\x76\x35\x31\x32\x7a\x6d\x31\x30\x32\x34\x2d\x35\x31\x32\x68\
\x35\x31\x32\x76\x2d\x37\x36\x38\x68\x2d\x37\x36\x38\x76\x32\x35\
\x36\x68\x39\x36\x71\x36\x36\x20\x30\x20\x31\x31\x33\x20\x34\x37\
\x74\x34\x37\x20\x31\x31\x33\x76\x33\x35\x32\x7a\x6d\x37\x36\x38\
\x2d\x38\x36\x34\x76\x39\x36\x30\x71\x30\x20\x36\x36\x2d\x34\x37\
\x20\x31\x31\x33\x74\x2d\x31\x31\x33\x20\x34\x37\x68\x2d\x36\x30\
\x38\x76\x33\x35\x32\x71\x30\x20\x36\x36\x2d\x34\x37\x20\x31\x31\
\x33\x74\x2d\x31\x31\x33\x20\x34\x37\x68\x2d\x39\x36\x30\x71\x2d\
\x36\x36\x20\x30\x2d\x31\x31\x33\x2d\x34\x37\x74\x2d\x34\x37\x2d\
\x31\x31\x33\x76\x2d\x39\x36\x30\x71\x30\x2d\x36\x36\x20\x34\x37\
\x2d\x31\x31\x33\x74\x31\x31\x33\x2d\x34\x37\x68\x36\x30\x38\x76\
\x2d\x33\x35\x32\x71\x30\x2d\x36\x36\x20\x34\x37\x2d\x31\x31\x33\
\x74\x31\x31\x33\x2d\x34\x37\x68\x39\x36\x30\x71\x36\x36\x20\x30\
\x20\x31\x31\x33\x20\x34\x37\x74\x34\x37\x20\x31\x31\x33\x7a\x22\
\x2f\x3e\x3c\x2f\x73\x76\x67\x3e\
\x00\x00\x01\xfb\
\x3c\
\x3f\x78\x6d\x6c\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\
\x30\x22\x20\x65\x6e\x63\x6f\x64\x69\x6e\x67\x3d\x22\x75\x74\x66\
\x2d\x38\x22\x3f\x3e\x0a\x3c\x73\x76\x67\x20\x77\x69\x64\x74\x68\
\x3d\x22\x31\x37\x39\x32\x22\x20\x68\x65\x69\x67\x68\x74\x3d\x22\
\x31\x37\x39\x32\x22\x20\x76\x69\x65\x77\x42\x6f\x78\x3d\x22\x30\
\x20\x30\x20\x31\x37\x39\x32\x20\x31\x37\x39\x32\x22\x20\x78\x6d\
\x6c\x6e\x73\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\
\x77\x33\x2e\x6f\x72\x67\x2f\x32\x30\x30\x30\x2f\x73\x76\x67\x22\
\x3e\x3c\x70\x61\x74\x68\x20\x64\x3d\x22\x4d\x31\x30\x32\x34\x20\
\x35\x34\x34\x76\x34\x34\x38\x71\x30\x20\x31\x34\x2d\x39\x20\x32\
\x33\x74\x2d\x32\x33\x20\x39\x68\x2d\x33\x32\x30\x71\x2d\x31\x34\
\x20\x30\x2d\x32\x33\x2d\x39\x74\x2d\x39\x2d\x32\x33\x76\x2d\x36\
\x34\x71\x30\x2d\x31\x34\x20\x39\x2d\x32\x33\x74\x32\x33\x2d\x39\
\x68\x32\x32\x34\x76\x2d\x33\x35\x32\x71\x30\x2d\x31\x34\x20\x39\
\x2d\x32\x33\x74\x32\x33\x2d\x39\x68\x36\x34\x71\x31\x34\x20\x30\
\x20\x32\x33\x20\x39\x74\x39\x20\x32\x33\x7a\x6d\x34\x31\x36\x20\
\x33\x35\x32\x71\x30\x2d\x31\x34\x38\x2d\x37\x33\x2d\x32\x37\x33\
\x74\x2d\x31\x39\x38\x2d\x31\x39\x38\x2d\x32\x37\x33\x2d\x37\x33\
\x2d\x32\x37\x33\x20\x37\x33\x2d\x31\x39\x38\x20\x31\x39\x38\x2d\
\x37\x33\x20\x32\x37\x33\x20\x37\x33\x20\x32\x37\x33\x20\x31\x39\
\x38\x20\x31\x39\x38\x20\x32\x37\x33\x20\x37\x33\x20\x32\x37\x33\
\x2d\x37\x33\x20\x31\x39\x38\x2d\x31\x39\x38\x20\x37\x33\x2d\x32\
\x37\x33\x7a\x6d\x32\x32\x34\x20\x30\x71\x30\x20\x32\x30\x39\x2d\
\x31\x30\x33\x20\x33\x38\x35\x2e\x35\x74\x2d\x32\x37\x39\x2e\x35\
\x20\x32\x37\x39\x2e\x35\x2d\x33\x38\x35\x2e\x35\x20\x31\x30\x33\
\x2d\x33\x38\x35\x2e\x35\x2d\x31\x30\x33\x2d\x32\x37\x39\x2e\x35\
\x2d\x32\x37\x39\x2e\x35\x2d\x31\x30\x33\x2d\x33\x38\x35\x2e\x35\
\x20\x31\x30\x33\x2d\x33\x38\x35\x2e\x35\x20\x32\x37\x39\x2e\x35\
\x2d\x32\x37\x39\x2e\x35\x20\x33\x38\x35\x2e\x35\x2d\x31\x30\x33\
\x20\x33\x38\x35\x2e\x35\x20\x31\x30\x33\x20\x32\x37\x39\x2e\x35\
\x20\x32\x37\x39\x2e\x35\x20\x31\x30\x33\x20\x33\x38\x35\x2e\x35\
\x7a\x22\x2f\x3e\x3c\x2f\x73\x76\x67\x3e\
\x00\x00\x02\x18\
\x3c\
\x3f\x78\x6d\x6c\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\
\x30\x22\x20\x65\x6e\x63\x6f\x64\x69\x6e\x67\x3d\x22\x75\x74\x66\
\x2d\x38\x22\x3f\x3e\x0a\x3c\x73\x76\x67\x20\x77\x69\x64\x74\x68\
\x3d\x22\x31\x37\x39\x32\x22\x20\x68\x65\x69\x67\x68\x74\x3d\x22\
\x31\x37\x39\x32\x22\x20\x76\x69\x65\x77\x42\x6f\x78\x3d\x22\x30\
\x20\x30\x20\x31\x37\x39\x32\x20\x31\x37\x39\x32\x22\x20\x78\x6d\
\x6c\x6e\x73\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\
\x77\x33\x2e\x6f\x72\x67\x2f\x32\x30\x30\x30\x2f\x73\x76\x67\x22\
\x3e\x3c\x70\x61\x74\x68\x20\x64\x3d\x22\x4d\x31\x36\x39\x36\x20\
\x33\x38\x34\x71\x34\x30\x20\x30\x20\x36\x38\x20\x32\x38\x74\x32\
\x38\x20\x36\x38\x76\x31\x32\x31\x36\x71\x30\x20\x34\x30\x2d\x32\
\x38\x20\x36\x38\x74\x2d\x36\x38\x20\x32\x38\x68\x2d\x39\x36\x30\
\x71\x2d\x34\x30\x20\x30\x2d\x36\x38\x2d\x32\x38\x74\x2d\x32\x38\
\x2d\x36\x38\x76\x2d\x32\x38\x38\x68\x2d\x35\x34\x34\x71\x2d\x34\
\x30\x20\x30\x2d\x36\x38\x2d\x32\x38\x74\x2d\x32\x38\x2d\x36\x38\
\x76\x2d\x36\x37\x32\x71\x30\x2d\x34\x30\x20\x32\x30\x2d\x38\x38\
\x74\x34\x38\x2d\x37\x36\x6c\x34\x30\x38\x2d\x34\x30\x38\x71\x32\
\x38\x2d\x32\x38\x20\x37\x36\x2d\x34\x38\x74\x38\x38\x2d\x32\x30\
\x68\x34\x31\x36\x71\x34\x30\x20\x30\x20\x36\x38\x20\x32\x38\x74\
\x32\x38\x20\x36\x38\x76\x33\x32\x38\x71\x36\x38\x2d\x34\x30\x20\
\x31\x32\x38\x2d\x34\x30\x68\x34\x31\x36\x7a\x6d\x2d\x35\x34\x34\
\x20\x32\x31\x33\x6c\x2d\x32\x39\x39\x20\x32\x39\x39\x68\x32\x39\
\x39\x76\x2d\x32\x39\x39\x7a\x6d\x2d\x36\x34\x30\x2d\x33\x38\x34\
\x6c\x2d\x32\x39\x39\x20\x32\x39\x39\x68\x32\x39\x39\x76\x2d\x32\
\x39\x39\x7a\x6d\x31\x39\x36\x20\x36\x34\x37\x6c\x33\x31\x36\x2d\
\x33\x31\x36\x76\x2d\x34\x31\x36\x68\x2d\x33\x38\x34\x76\x34\x31\
\x36\x71\x30\x20\x34\x30\x2d\x32\x38\x20\x36\x38\x74\x2d\x36\x38\
\x20\x32\x38\x68\x2d\x34\x31\x36\x76\x36\x34\x30\x68\x35\x31\x32\
\x76\x2d\x32\x35\x36\x71\x30\x2d\x34\x30\x20\x32\x30\x2d\x38\x38\
\x74\x34\x38\x2d\x37\x36\x7a\x6d\x39\x35\x36\x20\x38\x30\x34\x76\
\x2d\x31\x31\x35\x32\x68\x2d\x33\x38\x34\x76\x34\x31\x36\x71\x30\
\x20\x34\x30\x2d\x32\x38\x20\x36\x38\x74\x2d\x36\x38\x20\x32\x38\
\x68\x2d\x34\x31\x36\x76\x36\x34\x30\x68\x38\x39\x36\x7a\x22\x2f\
\x3e\x3c\x2f\x73\x76\x67\x3e\
\x00\x00\x02\x12\
\x3c\
\x3f\x78\x6d\x6c\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\
\x30\x22\x20\x65\x6e\x63\x6f\x64\x69\x6e\x67\x3d\x22\x75\x74\x66\
\x2d\x38\x22\x3f\x3e\x0a\x3c\x73\x76\x67\x20\x77\x69\x64\x74\x68\
\x3d\x22\x31\x37\x39\x32\x22\x20\x68\x65\x69\x67\x68\x74\x3d\x22\
\x31\x37\x39\x32\x22\x20\x76\x69\x65\x77\x42\x6f\x78\x3d\x22\x30\
\x20\x30\x20\x31\x37\x39\x32\x20\x31\x37\x39\x32\x22\x20\x78\x6d\
\x6c\x6e\x73\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\
\x77\x33\x2e\x6f\x72\x67\x2f\x32\x30\x30\x30\x2f\x73\x76\x67\x22\
\x3e\x3c\x70\x61\x74\x68\x20\x64\x3d\x22\x4d\x31\x30\x32\x34\x20\
\x31\x34\x30\x38\x68\x36\x34\x30\x76\x2d\x31\x32\x38\x68\x2d\x36\
\x34\x30\x76\x31\x32\x38\x7a\x6d\x2d\x33\x38\x34\x2d\x35\x31\x32\
\x68\x31\x30\x32\x34\x76\x2d\x31\x32\x38\x68\x2d\x31\x30\x32\x34\
\x76\x31\x32\x38\x7a\x6d\x36\x34\x30\x2d\x35\x31\x32\x68\x33\x38\
\x34\x76\x2d\x31\x32\x38\x68\x2d\x33\x38\x34\x76\x31\x32\x38\x7a\
\x6d\x35\x31\x32\x20\x38\x33\x32\x76\x32\x35\x36\x71\x30\x20\x32\
\x36\x2d\x31\x39\x20\x34\x35\x74\x2d\x34\x35\x20\x31\x39\x68\x2d\
\x31\x36\x36\x34\x71\x2d\x32\x36\x20\x30\x2d\x34\x35\x2d\x31\x39\
\x74\x2d\x31\x39\x2d\x34\x35\x76\x2d\x32\x35\x36\x71\x30\x2d\x32\
\x36\x20\x31\x39\x2d\x34\x35\x74\x34\x35\x2d\x31\x39\x68\x31\x36\
\x36\x34\x71\x32\x36\x20\x30\x20\x34\x35\x20\x31\x39\x74\x31\x39\
\x20\x34\x35\x7a\x6d\x30\x2d\x35\x31\x32\x76\x32\x35\x36\x71\x30\
\x20\x32\x36\x2d\x31\x39\x20\x34\x35\x74\x2d\x34\x35\x20\x31\x39\
\x68\x2d\x31\x36\x36\x34\x71\x2d\x32\x36\x20\x30\x2d\x34\x35\x2d\
\x31\x39\x74\x2d\x31\x39\x2d\x34\x35\x76\x2d\x32\x35\x36\x71\x30\
\x2d\x32\x36\x20\x31\x39\x2d\x34\x35\x74\x34\x35\x2d\x31\x39\x68\
\x31\x36\x36\x34\x71\x32\x36\x20\x30\x20\x34\x35\x20\x31\x39\x74\
\x31\x39\x20\x34\x35\x7a\x6d\x30\x2d\x35\x31\x32\x76\x32\x35\x36\
\x71\x30\x20\x32\x36\x2d\x31\x39\x20\x34\x35\x74\x2d\x34\x35\x20\
\x31\x39\x68\x2d\x31\x36\x36\x34\x71\x2d\x32\x36\x20\x30\x2d\x34\
\x35\x2d\x31\x39\x74\x2d\x31\x39\x2d\x34\x35\x76\x2d\x32\x35\x36\
\x71\x30\x2d\x32\x36\x20\x31\x39\x2d\x34\x35\x74\x34\x35\x2d\x31\
\x39\x68\x31\x36\x36\x34\x71\x32\x36\x20\x30\x20\x34\x35\x20\x31\
\x39\x74\x31\x39\x20\x34\x35\x7a\x22\x2f\x3e\x3c\x2f\x73\x76\x67\
\x3e\
\x00\x00\x02\x9d\
\x3c\
\x3f\x78\x6d\x6c\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\
@ -241,103 +338,6 @@ qt_resource_data = b"\
\x32\x36\x74\x35\x34\x20\x36\x33\x6c\x37\x30\x20\x31\x36\x37\x68\
\x33\x30\x39\x71\x31\x34\x20\x30\x20\x32\x33\x20\x39\x74\x39\x20\
\x32\x33\x7a\x22\x2f\x3e\x3c\x2f\x73\x76\x67\x3e\
\x00\x00\x01\xfb\
\x3c\
\x3f\x78\x6d\x6c\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\
\x30\x22\x20\x65\x6e\x63\x6f\x64\x69\x6e\x67\x3d\x22\x75\x74\x66\
\x2d\x38\x22\x3f\x3e\x0a\x3c\x73\x76\x67\x20\x77\x69\x64\x74\x68\
\x3d\x22\x31\x37\x39\x32\x22\x20\x68\x65\x69\x67\x68\x74\x3d\x22\
\x31\x37\x39\x32\x22\x20\x76\x69\x65\x77\x42\x6f\x78\x3d\x22\x30\
\x20\x30\x20\x31\x37\x39\x32\x20\x31\x37\x39\x32\x22\x20\x78\x6d\
\x6c\x6e\x73\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\
\x77\x33\x2e\x6f\x72\x67\x2f\x32\x30\x30\x30\x2f\x73\x76\x67\x22\
\x3e\x3c\x70\x61\x74\x68\x20\x64\x3d\x22\x4d\x31\x32\x38\x20\x31\
\x34\x30\x38\x68\x31\x30\x32\x34\x76\x2d\x31\x32\x38\x68\x2d\x31\
\x30\x32\x34\x76\x31\x32\x38\x7a\x6d\x30\x2d\x35\x31\x32\x68\x31\
\x30\x32\x34\x76\x2d\x31\x32\x38\x68\x2d\x31\x30\x32\x34\x76\x31\
\x32\x38\x7a\x6d\x31\x35\x36\x38\x20\x34\x34\x38\x71\x30\x2d\x34\
\x30\x2d\x32\x38\x2d\x36\x38\x74\x2d\x36\x38\x2d\x32\x38\x2d\x36\
\x38\x20\x32\x38\x2d\x32\x38\x20\x36\x38\x20\x32\x38\x20\x36\x38\
\x20\x36\x38\x20\x32\x38\x20\x36\x38\x2d\x32\x38\x20\x32\x38\x2d\
\x36\x38\x7a\x6d\x2d\x31\x35\x36\x38\x2d\x39\x36\x30\x68\x31\x30\
\x32\x34\x76\x2d\x31\x32\x38\x68\x2d\x31\x30\x32\x34\x76\x31\x32\
\x38\x7a\x6d\x31\x35\x36\x38\x20\x34\x34\x38\x71\x30\x2d\x34\x30\
\x2d\x32\x38\x2d\x36\x38\x74\x2d\x36\x38\x2d\x32\x38\x2d\x36\x38\
\x20\x32\x38\x2d\x32\x38\x20\x36\x38\x20\x32\x38\x20\x36\x38\x20\
\x36\x38\x20\x32\x38\x20\x36\x38\x2d\x32\x38\x20\x32\x38\x2d\x36\
\x38\x7a\x6d\x30\x2d\x35\x31\x32\x71\x30\x2d\x34\x30\x2d\x32\x38\
\x2d\x36\x38\x74\x2d\x36\x38\x2d\x32\x38\x2d\x36\x38\x20\x32\x38\
\x2d\x32\x38\x20\x36\x38\x20\x32\x38\x20\x36\x38\x20\x36\x38\x20\
\x32\x38\x20\x36\x38\x2d\x32\x38\x20\x32\x38\x2d\x36\x38\x7a\x6d\
\x39\x36\x20\x38\x33\x32\x76\x33\x38\x34\x68\x2d\x31\x37\x39\x32\
\x76\x2d\x33\x38\x34\x68\x31\x37\x39\x32\x7a\x6d\x30\x2d\x35\x31\
\x32\x76\x33\x38\x34\x68\x2d\x31\x37\x39\x32\x76\x2d\x33\x38\x34\
\x68\x31\x37\x39\x32\x7a\x6d\x30\x2d\x35\x31\x32\x76\x33\x38\x34\
\x68\x2d\x31\x37\x39\x32\x76\x2d\x33\x38\x34\x68\x31\x37\x39\x32\
\x7a\x22\x2f\x3e\x3c\x2f\x73\x76\x67\x3e\
\x00\x00\x02\x18\
\x3c\
\x3f\x78\x6d\x6c\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\
\x30\x22\x20\x65\x6e\x63\x6f\x64\x69\x6e\x67\x3d\x22\x75\x74\x66\
\x2d\x38\x22\x3f\x3e\x0a\x3c\x73\x76\x67\x20\x77\x69\x64\x74\x68\
\x3d\x22\x31\x37\x39\x32\x22\x20\x68\x65\x69\x67\x68\x74\x3d\x22\
\x31\x37\x39\x32\x22\x20\x76\x69\x65\x77\x42\x6f\x78\x3d\x22\x30\
\x20\x30\x20\x31\x37\x39\x32\x20\x31\x37\x39\x32\x22\x20\x78\x6d\
\x6c\x6e\x73\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\
\x77\x33\x2e\x6f\x72\x67\x2f\x32\x30\x30\x30\x2f\x73\x76\x67\x22\
\x3e\x3c\x70\x61\x74\x68\x20\x64\x3d\x22\x4d\x31\x36\x39\x36\x20\
\x33\x38\x34\x71\x34\x30\x20\x30\x20\x36\x38\x20\x32\x38\x74\x32\
\x38\x20\x36\x38\x76\x31\x32\x31\x36\x71\x30\x20\x34\x30\x2d\x32\
\x38\x20\x36\x38\x74\x2d\x36\x38\x20\x32\x38\x68\x2d\x39\x36\x30\
\x71\x2d\x34\x30\x20\x30\x2d\x36\x38\x2d\x32\x38\x74\x2d\x32\x38\
\x2d\x36\x38\x76\x2d\x32\x38\x38\x68\x2d\x35\x34\x34\x71\x2d\x34\
\x30\x20\x30\x2d\x36\x38\x2d\x32\x38\x74\x2d\x32\x38\x2d\x36\x38\
\x76\x2d\x36\x37\x32\x71\x30\x2d\x34\x30\x20\x32\x30\x2d\x38\x38\
\x74\x34\x38\x2d\x37\x36\x6c\x34\x30\x38\x2d\x34\x30\x38\x71\x32\
\x38\x2d\x32\x38\x20\x37\x36\x2d\x34\x38\x74\x38\x38\x2d\x32\x30\
\x68\x34\x31\x36\x71\x34\x30\x20\x30\x20\x36\x38\x20\x32\x38\x74\
\x32\x38\x20\x36\x38\x76\x33\x32\x38\x71\x36\x38\x2d\x34\x30\x20\
\x31\x32\x38\x2d\x34\x30\x68\x34\x31\x36\x7a\x6d\x2d\x35\x34\x34\
\x20\x32\x31\x33\x6c\x2d\x32\x39\x39\x20\x32\x39\x39\x68\x32\x39\
\x39\x76\x2d\x32\x39\x39\x7a\x6d\x2d\x36\x34\x30\x2d\x33\x38\x34\
\x6c\x2d\x32\x39\x39\x20\x32\x39\x39\x68\x32\x39\x39\x76\x2d\x32\
\x39\x39\x7a\x6d\x31\x39\x36\x20\x36\x34\x37\x6c\x33\x31\x36\x2d\
\x33\x31\x36\x76\x2d\x34\x31\x36\x68\x2d\x33\x38\x34\x76\x34\x31\
\x36\x71\x30\x20\x34\x30\x2d\x32\x38\x20\x36\x38\x74\x2d\x36\x38\
\x20\x32\x38\x68\x2d\x34\x31\x36\x76\x36\x34\x30\x68\x35\x31\x32\
\x76\x2d\x32\x35\x36\x71\x30\x2d\x34\x30\x20\x32\x30\x2d\x38\x38\
\x74\x34\x38\x2d\x37\x36\x7a\x6d\x39\x35\x36\x20\x38\x30\x34\x76\
\x2d\x31\x31\x35\x32\x68\x2d\x33\x38\x34\x76\x34\x31\x36\x71\x30\
\x20\x34\x30\x2d\x32\x38\x20\x36\x38\x74\x2d\x36\x38\x20\x32\x38\
\x68\x2d\x34\x31\x36\x76\x36\x34\x30\x68\x38\x39\x36\x7a\x22\x2f\
\x3e\x3c\x2f\x73\x76\x67\x3e\
\x00\x00\x01\x89\
\x3c\
\x3f\x78\x6d\x6c\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\
\x30\x22\x20\x65\x6e\x63\x6f\x64\x69\x6e\x67\x3d\x22\x75\x74\x66\
\x2d\x38\x22\x3f\x3e\x0a\x3c\x73\x76\x67\x20\x77\x69\x64\x74\x68\
\x3d\x22\x32\x30\x34\x38\x22\x20\x68\x65\x69\x67\x68\x74\x3d\x22\
\x31\x37\x39\x32\x22\x20\x76\x69\x65\x77\x42\x6f\x78\x3d\x22\x30\
\x20\x30\x20\x32\x30\x34\x38\x20\x31\x37\x39\x32\x22\x20\x78\x6d\
\x6c\x6e\x73\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\
\x77\x33\x2e\x6f\x72\x67\x2f\x32\x30\x30\x30\x2f\x73\x76\x67\x22\
\x3e\x3c\x70\x61\x74\x68\x20\x64\x3d\x22\x4d\x32\x35\x36\x20\x31\
\x35\x33\x36\x68\x37\x36\x38\x76\x2d\x35\x31\x32\x68\x2d\x37\x36\
\x38\x76\x35\x31\x32\x7a\x6d\x31\x30\x32\x34\x2d\x35\x31\x32\x68\
\x35\x31\x32\x76\x2d\x37\x36\x38\x68\x2d\x37\x36\x38\x76\x32\x35\
\x36\x68\x39\x36\x71\x36\x36\x20\x30\x20\x31\x31\x33\x20\x34\x37\
\x74\x34\x37\x20\x31\x31\x33\x76\x33\x35\x32\x7a\x6d\x37\x36\x38\
\x2d\x38\x36\x34\x76\x39\x36\x30\x71\x30\x20\x36\x36\x2d\x34\x37\
\x20\x31\x31\x33\x74\x2d\x31\x31\x33\x20\x34\x37\x68\x2d\x36\x30\
\x38\x76\x33\x35\x32\x71\x30\x20\x36\x36\x2d\x34\x37\x20\x31\x31\
\x33\x74\x2d\x31\x31\x33\x20\x34\x37\x68\x2d\x39\x36\x30\x71\x2d\
\x36\x36\x20\x30\x2d\x31\x31\x33\x2d\x34\x37\x74\x2d\x34\x37\x2d\
\x31\x31\x33\x76\x2d\x39\x36\x30\x71\x30\x2d\x36\x36\x20\x34\x37\
\x2d\x31\x31\x33\x74\x31\x31\x33\x2d\x34\x37\x68\x36\x30\x38\x76\
\x2d\x33\x35\x32\x71\x30\x2d\x36\x36\x20\x34\x37\x2d\x31\x31\x33\
\x74\x31\x31\x33\x2d\x34\x37\x68\x39\x36\x30\x71\x36\x36\x20\x30\
\x20\x31\x31\x33\x20\x34\x37\x74\x34\x37\x20\x31\x31\x33\x7a\x22\
\x2f\x3e\x3c\x2f\x73\x76\x67\x3e\
"
qt_resource_name = b"\
@ -346,51 +346,51 @@ qt_resource_name = b"\
\x00\x69\
\x00\x63\x00\x6f\x00\x6e\x00\x73\
\x00\x0a\
\x0c\xca\x63\xe7\
\x00\x73\
\x00\x65\x00\x72\x00\x76\x00\x65\x00\x72\x00\x2e\x00\x73\x00\x76\x00\x67\
\x00\x0a\
\x00\x45\xd0\xa7\
\x00\x75\
\x00\x6e\x00\x6c\x00\x69\x00\x6e\x00\x6b\x00\x2e\x00\x73\x00\x76\x00\x67\
\x00\x09\
\x0a\x26\xaf\xc7\
\x00\x74\
\x00\x61\x00\x73\x00\x6b\x00\x73\x00\x2e\x00\x73\x00\x76\x00\x67\
\x00\x0b\
\x0f\x16\x31\xe7\
\x00\x63\
\x00\x6c\x00\x6f\x00\x63\x00\x6b\x00\x2d\x00\x6f\x00\x2e\x00\x73\x00\x76\x00\x67\
\x00\x08\
\x0f\xcc\x55\x67\
\x00\x77\
\x00\x69\x00\x66\x00\x69\x00\x2e\x00\x73\x00\x76\x00\x67\
\x00\x09\
\x08\x9b\xad\xc7\
\x00\x74\
\x00\x72\x00\x61\x00\x73\x00\x68\x00\x2e\x00\x73\x00\x76\x00\x67\
\x00\x0a\
\x0c\xca\x63\xe7\
\x00\x73\
\x00\x65\x00\x72\x00\x76\x00\x65\x00\x72\x00\x2e\x00\x73\x00\x76\x00\x67\
\x00\x08\
\x06\x7c\x57\x87\
\x00\x63\
\x00\x6f\x00\x70\x00\x79\x00\x2e\x00\x73\x00\x76\x00\x67\
\x00\x12\
\x05\x98\xe2\x07\
\x00\x77\
\x00\x69\x00\x6e\x00\x64\x00\x6f\x00\x77\x00\x2d\x00\x72\x00\x65\x00\x73\x00\x74\x00\x6f\x00\x72\x00\x65\x00\x2e\x00\x73\x00\x76\
\x00\x67\
\x00\x0b\
\x0f\x16\x31\xe7\
\x00\x63\
\x00\x6c\x00\x6f\x00\x63\x00\x6b\x00\x2d\x00\x6f\x00\x2e\x00\x73\x00\x76\x00\x67\
\x00\x08\
\x06\x7c\x57\x87\
\x00\x63\
\x00\x6f\x00\x70\x00\x79\x00\x2e\x00\x73\x00\x76\x00\x67\
\x00\x09\
\x0a\x26\xaf\xc7\
\x00\x74\
\x00\x61\x00\x73\x00\x6b\x00\x73\x00\x2e\x00\x73\x00\x76\x00\x67\
\x00\x09\
\x08\x9b\xad\xc7\
\x00\x74\
\x00\x72\x00\x61\x00\x73\x00\x68\x00\x2e\x00\x73\x00\x76\x00\x67\
"
qt_resource_struct_v1 = b"\
\x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x01\
\x00\x00\x00\x00\x00\x02\x00\x00\x00\x08\x00\x00\x00\x02\
\x00\x00\x00\x2a\x00\x00\x00\x00\x00\x01\x00\x00\x01\xff\
\x00\x00\x00\x5a\x00\x00\x00\x00\x00\x01\x00\x00\x09\x1a\
\x00\x00\x00\xa0\x00\x00\x00\x00\x00\x01\x00\x00\x0c\xa6\
\x00\x00\x00\xce\x00\x00\x00\x00\x00\x01\x00\x00\x10\xd8\
\x00\x00\x00\xb6\x00\x00\x00\x00\x00\x01\x00\x00\x0e\xc2\
\x00\x00\x00\x10\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\
\x00\x00\x00\xbc\x00\x00\x00\x00\x00\x01\x00\x00\x11\xec\
\x00\x00\x00\xa6\x00\x00\x00\x00\x00\x01\x00\x00\x0f\xd0\
\x00\x00\x00\x74\x00\x00\x00\x00\x00\x01\x00\x00\x0b\x30\
\x00\x00\x00\x2a\x00\x00\x00\x00\x00\x01\x00\x00\x03\xfe\
\x00\x00\x00\x8c\x00\x00\x00\x00\x00\x01\x00\x00\x0d\xd1\
\x00\x00\x00\x42\x00\x00\x00\x00\x00\x01\x00\x00\x06\x14\
\x00\x00\x00\x5e\x00\x00\x00\x00\x00\x01\x00\x00\x08\x13\
\x00\x00\x00\x84\x00\x00\x00\x00\x00\x01\x00\x00\x0a\xa7\
\x00\x00\x00\x44\x00\x00\x00\x00\x00\x01\x00\x00\x05\xfd\
"
qt_resource_struct_v2 = b"\
@ -398,21 +398,21 @@ qt_resource_struct_v2 = b"\
\x00\x00\x00\x00\x00\x00\x00\x00\
\x00\x00\x00\x00\x00\x02\x00\x00\x00\x08\x00\x00\x00\x02\
\x00\x00\x00\x00\x00\x00\x00\x00\
\x00\x00\x00\x10\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\
\x00\x00\x00\x2a\x00\x00\x00\x00\x00\x01\x00\x00\x01\xff\
\x00\x00\x01\x66\xd2\xec\x19\xd2\
\x00\x00\x00\xbc\x00\x00\x00\x00\x00\x01\x00\x00\x11\xec\
\x00\x00\x00\x5a\x00\x00\x00\x00\x00\x01\x00\x00\x09\x1a\
\x00\x00\x01\x66\xbf\xa1\xb1\x2c\
\x00\x00\x00\xa6\x00\x00\x00\x00\x00\x01\x00\x00\x0f\xd0\
\x00\x00\x00\xa0\x00\x00\x00\x00\x00\x01\x00\x00\x0c\xa6\
\x00\x00\x01\x66\xd2\xd9\x4e\x21\
\x00\x00\x00\x74\x00\x00\x00\x00\x00\x01\x00\x00\x0b\x30\
\x00\x00\x00\xce\x00\x00\x00\x00\x00\x01\x00\x00\x10\xd8\
\x00\x00\x01\x66\xd2\xd9\xe8\xfa\
\x00\x00\x00\x2a\x00\x00\x00\x00\x00\x01\x00\x00\x03\xfe\
\x00\x00\x00\xb6\x00\x00\x00\x00\x00\x01\x00\x00\x0e\xc2\
\x00\x00\x01\x66\xbf\x9f\xde\x3e\
\x00\x00\x00\x8c\x00\x00\x00\x00\x00\x01\x00\x00\x0d\xd1\
\x00\x00\x00\x10\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\
\x00\x00\x01\x66\xbf\xa1\x0f\xda\
\x00\x00\x00\x42\x00\x00\x00\x00\x00\x01\x00\x00\x06\x14\
\x00\x00\x00\x84\x00\x00\x00\x00\x00\x01\x00\x00\x0a\xa7\
\x00\x00\x01\x66\xbf\x9d\x97\x00\
\x00\x00\x00\x5e\x00\x00\x00\x00\x00\x01\x00\x00\x08\x13\
\x00\x00\x00\x44\x00\x00\x00\x00\x00\x01\x00\x00\x05\xfd\
\x00\x00\x01\x66\xbf\x9f\x19\x44\
"

View file

@ -28,12 +28,17 @@ class MainWindow(MainWindowBase, MainWindowUI):
self.scheduleTab = ScheduleTab(self.scheduleTabSlot)
self.repoTab.repo_changed.connect(lambda: self.snapshotTab.populate())
self.createStartBtn.clicked.connect(self.backup_start)
self.cancelButton.clicked.connect(self.backup_cancel)
self.createStartBtn.clicked.connect(self.app.create_backup_action)
self.cancelButton.clicked.connect(self.app.backup_cancelled_event.emit)
QShortcut(QKeySequence("Ctrl+W"), self).activated.connect(self.on_close_window)
QShortcut(QKeySequence("Ctrl+Q"), self).activated.connect(self.on_close_window)
self.app.backup_started_event.connect(self.backup_started_event)
self.app.backup_finished_event.connect(self.backup_finished_event)
self.app.backup_log_event.connect(self.set_status)
self.app.backup_cancelled_event.connect(self.backup_cancelled_event)
# Connect to existing thread.
if BorgThread.is_running():
self.createStartBtn.setEnabled(False)
@ -50,30 +55,24 @@ class MainWindow(MainWindowBase, MainWindowUI):
self.createProgress.setRange(0, progress_max)
self.createProgressText.repaint()
def backup_start(self):
msg = self.app.create_backup()
if msg['ok']:
self.set_status(msg['message'], progress_max=0)
def backup_started_event(self):
self.set_status(progress_max=0)
self.createStartBtn.setEnabled(False)
self.createStartBtn.repaint()
self.app.backup_done.connect(self.backup_done)
self.app.backup_log.connect(self.create_update_log)
else:
self.set_status(msg['message'])
self.cancelButton.setEnabled(True)
self.cancelButton.repaint()
def create_update_log(self, text):
self.set_status(text)
def backup_cancel(self):
self.app.cancel_backup()
self.createStartBtn.setEnabled(True)
self.createStartBtn.repaint()
self.set_status(progress_max=100)
self.set_status('Backup cancelled')
def backup_done(self):
def backup_finished_event(self):
self.createStartBtn.setEnabled(True)
self.createStartBtn.repaint()
self.set_status(progress_max=100)
self.snapshotTab.populate()
def backup_cancelled_event(self):
self.createStartBtn.setEnabled(True)
self.createStartBtn.repaint()
self.cancelButton.setEnabled(False)
self.cancelButton.repaint()
self.set_status(progress_max=100)
self.set_status('Backup cancelled')

View file

@ -1,5 +1,7 @@
import os
from dateutil import parser
from PyQt5 import uic, QtCore
from PyQt5.QtWidgets import QApplication, QMessageBox
from ..models import RepoModel, SnapshotModel, BackupProfileMixin
from .repo_add import AddRepoWindow, ExistingRepoWindow
@ -17,6 +19,7 @@ class RepoTab(RepoBase, RepoUI, BackupProfileMixin):
super().__init__(parent)
self.setupUi(parent)
# Populate dropdowns
self.repoSelector.model().item(0).setEnabled(False)
self.repoSelector.addItem('Initialize New Repository', 'init')
self.repoSelector.addItem('Add Existing Repository', 'existing')
@ -30,6 +33,7 @@ class RepoTab(RepoBase, RepoUI, BackupProfileMixin):
self.repoCompression.addItem('LZ4 (default)', 'lz4')
self.repoCompression.addItem('Zstandard (medium)', 'zstd')
self.repoCompression.addItem('LZMA (high)', 'lzma,6')
self.repoCompression.addItem('No Compression', 'none')
self.repoCompression.setCurrentIndex(self.repoCompression.findData(self.profile.compression))
self.repoCompression.currentIndexChanged.connect(self.compression_select_action)
@ -51,6 +55,7 @@ class RepoTab(RepoBase, RepoUI, BackupProfileMixin):
for key in keys:
self.sshComboBox.addItem(f'{key["filename"]} ({key["format"]}:{key["fingerprint"]})', key['filename'])
self.sshComboBox.currentIndexChanged.connect(self.ssh_select_action)
self.sshKeyToClipboardButton.clicked.connect(self.ssh_copy_to_clipboard_action)
def ssh_select_action(self, index):
if index == 1:
@ -64,6 +69,30 @@ class RepoTab(RepoBase, RepoUI, BackupProfileMixin):
profile.ssh_key = self.sshComboBox.itemData(index)
profile.save()
def ssh_copy_to_clipboard_action(self):
msg = QMessageBox()
msg.setStandardButtons(QMessageBox.Ok)
msg.setParent(self, QtCore.Qt.Sheet)
index = self.sshComboBox.currentIndex()
if index > 1:
ssh_key_filename = self.sshComboBox.itemData(index)
ssh_key_path = os.path.expanduser(f'~/.ssh/{ssh_key_filename}.pub')
if os.path.isfile(ssh_key_path):
pub_key = open(ssh_key_path).read().strip()
clipboard = QApplication.clipboard()
clipboard.setText(pub_key)
msg.setText("Public Key Copied to Clipboard")
msg.setInformativeText(
"The selected public SSH key was copied to the clipboard. Use it to set up remote repo permissions.")
else:
msg.setText("Couldn't find public key.")
else:
msg.setText("Select a public key from the dropdown first.")
msg.exec_()
def compression_select_action(self, index):
profile = self.profile