Skip to content

Commit 84fbf14

Browse files
committed
Fixes #2120 (ammended)
1 parent f56921f commit 84fbf14

5 files changed

Lines changed: 56 additions & 12 deletions

File tree

src/vorta/application.py

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
import os
33
import sys
44
from pathlib import Path
5-
from threading import Thread
65
from typing import Any, Dict, List, Tuple
76

87
from PyQt6 import QtCore
@@ -21,7 +20,7 @@
2120
from vorta.store.connection import cleanup_db
2221
from vorta.store.models import BackupProfileModel, SettingsModel
2322
from vorta.tray_menu import TrayMenu
24-
from vorta.utils import borg_compat, parse_args
23+
from vorta.utils import borg_compat, parse_args, AsyncRunner
2524
from vorta.views.main_window import MainWindow
2625

2726
logger = logging.getLogger(__name__)
@@ -43,7 +42,6 @@ class VortaApp(QtSingleApplication):
4342
backup_log_event = QtCore.pyqtSignal(str, dict)
4443
backup_progress_event = QtCore.pyqtSignal(str)
4544
check_failed_event = QtCore.pyqtSignal(dict)
46-
create_backup_event = QtCore.pyqtSignal()
4745
pre_backup_event = QtCore.pyqtSignal(int)
4846
post_backup_event = QtCore.pyqtSignal(int, bool)
4947

@@ -88,7 +86,6 @@ def __init__(self, args_raw, single_app=False):
8886
self.message_received_event.connect(self.message_received_event_response)
8987
self.check_failed_event.connect(self.check_failed_response)
9088
self.backup_log_event.connect(self.react_to_log)
91-
self.create_backup_event.connect(lambda: Thread(target=self.create_backup_action).start())
9289
self.pre_backup_event.connect(self.pre_backup_event_response)
9390
self.post_backup_event.connect(self.post_backup_event_response)
9491
self.aboutToQuit.connect(self.quit_app_action)
@@ -101,7 +98,7 @@ def create_backups_cmdline(self, profile_name):
10198
if profile is not None:
10299
if profile.repo is None:
103100
logger.warning(f"Add a repository to {profile_name}")
104-
Thread(target=self.create_backup_action, kwargs={'profile_id': profile.id}).start()
101+
self.create_backup_action(profile_id=profile.id)
105102
else:
106103
logger.warning(f"Invalid profile name {profile_name}")
107104

@@ -112,7 +109,8 @@ def quit_app_action(self):
112109
del self.tray
113110
cleanup_db()
114111

115-
def create_backup_action(self, profile_id=None):
112+
@AsyncRunner
113+
def create_backup_action(self, profile_id=None, app=None):
116114
if not profile_id:
117115
profile_id = self.main_window.current_profile.id
118116

src/vorta/borg/create.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ def process_result(self, result):
5353
self.app.backup_log_event.emit('', {})
5454
self.app.backup_progress_event.emit(f"[{self.params['profile_name']}] {self.tr('Backup finished.')}")
5555

56-
def progress_event(self, fmt=None):
56+
def progress_event(self, fmt):
5757
self.app.backup_progress_event.emit(f"[{self.params['profile_name']}] {fmt}")
5858

5959
def started_event(self):

src/vorta/scheduler.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
from vorta.i18n import translate
2020
from vorta.notifications import VortaNotifications
2121
from vorta.store.models import BackupProfileModel, EventLogModel
22-
from vorta.utils import borg_compat
22+
from vorta.utils import borg_compat, AsyncRunner
2323

2424
logger = logging.getLogger(__name__)
2525

@@ -301,7 +301,7 @@ def set_timer_for_profile(self, profile_id: int):
301301
profile.name,
302302
profile_id,
303303
)
304-
threading.Thread(target=self.create_backup, args=(profile_id,)).start()
304+
self.create_backup(profile_id)
305305
finally:
306306
self.lock.acquire() # with-statement will try to release
307307

@@ -337,7 +337,7 @@ def set_timer_for_profile(self, profile_id: int):
337337
timer = QTimer()
338338
timer.setSingleShot(True)
339339
timer.setInterval(int(timer_ms))
340-
timer.timeout.connect(lambda: threading.Thread(target=self.create_backup, args=(profile_id,)).start())
340+
timer.timeout.connect(lambda: self.create_backup(profile_id))
341341
timer.start()
342342

343343
self.timers[profile_id] = {
@@ -389,6 +389,7 @@ def next_job_for_profile(self, profile_id: int) -> ScheduleStatus:
389389
return ScheduleStatus(ScheduleStatusType.UNSCHEDULED)
390390
return ScheduleStatus(job['type'], time=job.get('dt'))
391391

392+
@AsyncRunner
392393
def create_backup(self, profile_id):
393394
notifier = VortaNotifications.pick()
394395
profile = BackupProfileModel.get_or_none(id=profile_id)

src/vorta/utils.py

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414

1515
import psutil
1616
from PyQt6 import QtCore
17-
from PyQt6.QtCore import QFileInfo, QThread, pyqtSignal
17+
from PyQt6.QtCore import QFileInfo, QObject, QThread, pyqtSignal
1818
from PyQt6.QtWidgets import QApplication, QFileDialog, QSystemTrayIcon
1919

2020
from vorta.borg._compatibility import BorgCompatibility
@@ -31,6 +31,51 @@
3131
_network_status_monitor = None
3232

3333

34+
class AsyncRunner(QObject):
35+
'''
36+
Wrapper to run functions asynchronously from GUI thread, based on
37+
https://gist.github.com/andgineer/026a617528c5740da24ec984ac282ee6#file-universal_decorator-py
38+
39+
NB Only apply it to void functions, otherwise return values will be lost.
40+
'''
41+
runner_thread = None
42+
43+
def __init__(self, orig_func):
44+
super(AsyncRunner, self).__init__()
45+
self.orig_func = orig_func
46+
self.__name__ = "AsyncRunner"
47+
48+
def __call__(self, *args):
49+
return self.orig_func(*args)
50+
51+
def __get__(self, wrapped_instance, owner):
52+
return AsyncRunner.Helper(self, wrapped_instance)
53+
54+
class Helper(QObject):
55+
def __init__(self, decorator_instance, wrapped_instance):
56+
super(AsyncRunner.Helper, self).__init__()
57+
self.decorator_instance = decorator_instance
58+
self.wrapped_instance = wrapped_instance
59+
60+
def __call__(self, *args, **kwargs):
61+
global runner_thread
62+
runner_thread = AsyncRunner.Runner(self.decorator_instance, self.wrapped_instance, *args, **kwargs)
63+
runner_thread.start()
64+
65+
class Runner(QtCore.QThread):
66+
def __init__(self, decorator_instance, wrapped_instance, *args, **kwargs):
67+
QtCore.QThread.__init__(self)
68+
self.decorator_instance = decorator_instance
69+
self.wrapped_instance = wrapped_instance
70+
self.args = args
71+
self.kwargs = kwargs
72+
73+
def run(self):
74+
self.decorator_instance(self.wrapped_instance, *self.args, **self.kwargs)
75+
self.terminate()
76+
self.wait()
77+
78+
3479
class FilePathInfoAsync(QThread):
3580
signal = pyqtSignal(str, str, str)
3681

src/vorta/views/main_window.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ def __init__(self, parent=None):
8888
self.miscTab.refresh_archive.connect(self.archiveTab.populate_from_profile)
8989

9090
self.miscButton.clicked.connect(self.toggle_misc_visibility)
91-
self.createStartBtn.clicked.connect(self.app.create_backup_event.emit)
91+
self.createStartBtn.clicked.connect(self.app.create_backup_action)
9292
self.cancelButton.clicked.connect(self.app.backup_cancelled_event.emit)
9393

9494
QShortcut(QKeySequence("Ctrl+W"), self).activated.connect(self.on_close_window)

0 commit comments

Comments
 (0)