mirror of https://github.com/borgbase/vorta
Fix scheduler issues.
This commit is contained in:
parent
c896388ae8
commit
44cbd0e127
|
@ -14,11 +14,10 @@ class VortaApp(QApplication):
|
|||
self.thread = None
|
||||
self.setQuitOnLastWindowClosed(False)
|
||||
self.scheduler = VortaScheduler(self)
|
||||
self.profile = BackupProfileModel.get(id=1)
|
||||
|
||||
# Prepare tray and connect events.
|
||||
self.tray = TrayMenu(self)
|
||||
self.tray.start_backup.connect(self.on_create_backup)
|
||||
self.tray.start_backup.connect(self.create_backup)
|
||||
self.tray.open_main_window.connect(self.on_open_main_window)
|
||||
|
||||
# Prepare main window
|
||||
|
@ -27,16 +26,53 @@ class VortaApp(QApplication):
|
|||
if not getattr(sys, 'frozen', False):
|
||||
self.main_window.show()
|
||||
|
||||
def on_create_backup(self):
|
||||
if self.thread and self.app.isRunning():
|
||||
self.app.process.kill()
|
||||
self.app.terminate()
|
||||
@property
|
||||
def profile(self):
|
||||
return BackupProfileModel.get(id=1)
|
||||
|
||||
def cancel_backup(self):
|
||||
if self.thread and self.thread.isRunning():
|
||||
self.thread.process.kill()
|
||||
self.thread.terminate()
|
||||
|
||||
def create_backup(self):
|
||||
if self.thread and self.thread.isRunning():
|
||||
print('backup already in progress')
|
||||
else:
|
||||
msg = BorgThread.prepare_runner()
|
||||
if msg['ok']:
|
||||
self.thread = BorgThread(msg['cmd'], msg['params'])
|
||||
self.thread.updated.connect(self.create_backup_log)
|
||||
self.thread.result.connect(self.create_backup_result)
|
||||
self.thread.start()
|
||||
# TODO: error dialog
|
||||
|
||||
def on_open_main_window(self):
|
||||
self.main_window.show()
|
||||
|
||||
def create_backup_log(self, text):
|
||||
print(text)
|
||||
|
||||
def create_backup_result(self, result):
|
||||
self.createStartBtn.setEnabled(True)
|
||||
self.createStartBtn.repaint()
|
||||
self.set_status(progress_max=100)
|
||||
if result['returncode'] == 0:
|
||||
new_snapshot, created = SnapshotModel.get_or_create(
|
||||
snapshot_id=result['data']['archive']['id'],
|
||||
defaults={
|
||||
'name': result['data']['archive']['name'],
|
||||
'time': parser.parse(result['data']['archive']['start']),
|
||||
'repo': self.profile.repo
|
||||
}
|
||||
)
|
||||
new_snapshot.save()
|
||||
if 'cache' in result['data'] and created:
|
||||
stats = result['data']['cache']['stats']
|
||||
repo = self.profile.repo
|
||||
repo.total_size = stats['total_size']
|
||||
repo.unique_csize = stats['unique_csize']
|
||||
repo.unique_size = stats['unique_size']
|
||||
repo.total_unique_chunks = stats['total_unique_chunks']
|
||||
repo.save()
|
||||
self.snapshotTab.populate()
|
||||
|
||||
|
|
|
@ -43,13 +43,13 @@
|
|||
<number>10</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>10</number>
|
||||
<number>15</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>10</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>10</number>
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QTabWidget" name="tabWidget">
|
||||
|
|
|
@ -32,7 +32,10 @@
|
|||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Remote repository. The place to store your backups.</string>
|
||||
<string>Configure your remote backup repository. First you will need a SSH key to login without a password. If you already have one, just keep it at the default. Next you can add a new or existing repository.</string>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
|
@ -53,14 +56,14 @@
|
|||
<property name="rightMargin">
|
||||
<number>25</number>
|
||||
</property>
|
||||
<item row="0" column="0">
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Repository:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<item row="2" column="1">
|
||||
<widget class="QComboBox" name="repoSelector">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
||||
|
@ -75,7 +78,7 @@
|
|||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<item row="3" column="1">
|
||||
<widget class="QLabel" name="label_5">
|
||||
<property name="text">
|
||||
<string><html><head/><body><p>Need hosting for your Borg repository? <a href="https://www.borgbackup.org/support/commercial.html"><span style=" text-decoration: underline; color:#0000ff;">View providers</span></a>.</p></body></html></string>
|
||||
|
@ -85,14 +88,24 @@
|
|||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="label_14">
|
||||
<item row="6" column="0">
|
||||
<widget class="QLabel" name="label_3">
|
||||
<property name="text">
|
||||
<string>SSH Key:</string>
|
||||
<string>Compression:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<item row="6" column="1">
|
||||
<widget class="QComboBox" name="repoCompression"/>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_14">
|
||||
<property name="text">
|
||||
<string>SSH Login Key:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QComboBox" name="sshComboBox">
|
||||
<item>
|
||||
<property name="text">
|
||||
|
@ -106,20 +119,10 @@
|
|||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="0">
|
||||
<widget class="QLabel" name="label_3">
|
||||
<property name="text">
|
||||
<string>Compression:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="1">
|
||||
<widget class="QComboBox" name="repoCompression"/>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<item row="1" column="1">
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string>To access repository securely.</string>
|
||||
<string>To access repository securely. Keep at default if you already have one. Or create new key.</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
|
@ -139,6 +142,9 @@
|
|||
<property name="leftMargin">
|
||||
<number>10</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>20</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>25</number>
|
||||
</property>
|
||||
|
|
|
@ -252,7 +252,7 @@ font-weight: bold;
|
|||
<item>
|
||||
<widget class="QLabel" name="label_11">
|
||||
<property name="text">
|
||||
<string><html><head/><body><p>(<a href="https://borgbackup.readthedocs.io/en/stable/usage.html#borg-prune"><span style=" text-decoration: underline; color:#0000ff;">More</span></a>)</p></body></html></string>
|
||||
<string><html><head/><body><p>(<a href="https://borgbackup.readthedocs.io/en/stable/usage.html#borg-prune"><span style=" text-decoration: underline; color:#0000ff;">How?</span></a>)</p></body></html></string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
|
|
|
@ -20,6 +20,7 @@ class BorgThread(QtCore.QThread):
|
|||
|
||||
def __init__(self, cmd, params):
|
||||
super().__init__(QApplication.instance())
|
||||
|
||||
# Find packaged borg binary. Prefer globally installed.
|
||||
if not shutil.which('borg'):
|
||||
meipass_borg = os.path.join(sys._MEIPASS, 'bin', 'borg')
|
||||
|
@ -29,9 +30,8 @@ class BorgThread(QtCore.QThread):
|
|||
|
||||
env = os.environ.copy()
|
||||
env['BORG_HOSTNAME_IS_UNIQUE'] = '1'
|
||||
if params.get('password') and params['password']:
|
||||
if params.get('password') and params['password'] is not None:
|
||||
env['BORG_PASSPHRASE'] = params['password']
|
||||
params['password'] = '***'
|
||||
|
||||
env['BORG_RSH'] = 'ssh -oStrictHostKeyChecking=no'
|
||||
if params.get('ssh_key') and params['ssh_key']:
|
||||
|
@ -43,7 +43,7 @@ class BorgThread(QtCore.QThread):
|
|||
self.process = None
|
||||
|
||||
def run(self):
|
||||
log_entry = EventLogModel(category='borg-run', subcommand=self.cmd[1], params=self.params)
|
||||
log_entry = EventLogModel(category='borg-run', subcommand=self.cmd[1])
|
||||
log_entry.save()
|
||||
self.process = Popen(self.cmd, stdout=PIPE, stderr=PIPE, bufsize=1, universal_newlines=True, env=self.env)
|
||||
for line in iter(self.process.stderr.readline, ''):
|
||||
|
@ -103,7 +103,7 @@ class BorgThread(QtCore.QThread):
|
|||
wifi_is_disallowed = WifiSettingModel.select().where(
|
||||
WifiSettingModel.ssid == current_wifi
|
||||
& WifiSettingModel.allowed == 0
|
||||
# & WifiSettingModel.profile == profile
|
||||
& WifiSettingModel.profile == profile
|
||||
)
|
||||
if wifi_is_disallowed.count() > 0:
|
||||
ret['message'] = 'Current Wifi is not allowed.'
|
||||
|
@ -122,11 +122,10 @@ class BorgThread(QtCore.QThread):
|
|||
exclude_dirs.append(expanded_directory)
|
||||
|
||||
if exclude_dirs:
|
||||
pattern_file = tempfile.NamedTemporaryFile('w')
|
||||
pattern_file = tempfile.NamedTemporaryFile('w', delete=False)
|
||||
pattern_file.write('\n'.join(exclude_dirs))
|
||||
pattern_file.flush()
|
||||
cmd.extend(['--exclude-from', pattern_file.name])
|
||||
params['pattern_file'] = pattern_file
|
||||
|
||||
if profile.exclude_if_present is not None:
|
||||
for f in profile.exclude_if_present.split('\n'):
|
||||
|
@ -139,7 +138,7 @@ class BorgThread(QtCore.QThread):
|
|||
for f in SourceDirModel.select():
|
||||
cmd.append(f.dir)
|
||||
|
||||
ret['message'] = 'Ready to start backup..'
|
||||
ret['message'] = 'Starting backup..'
|
||||
ret['ok'] = True
|
||||
ret['cmd'] = cmd
|
||||
ret['params'] = params
|
||||
|
|
|
@ -6,11 +6,14 @@ class VortaScheduler(QtScheduler):
|
|||
def __init__(self, parent):
|
||||
super().__init__()
|
||||
self.app = parent
|
||||
self.start()
|
||||
self.reload()
|
||||
|
||||
def reload(self):
|
||||
self.remove_all_jobs()
|
||||
profile = self.app.profile
|
||||
if profile.schedule_mode == 'off':
|
||||
self.next_job = 'Manual Backups'
|
||||
return None
|
||||
elif profile.schedule_mode == 'interval':
|
||||
trigger = cron.CronTrigger(hour=f'*/{profile.schedule_interval_hours}',
|
||||
|
@ -19,12 +22,6 @@ class VortaScheduler(QtScheduler):
|
|||
trigger = cron.CronTrigger(hour=profile.schedule_fixed_hour,
|
||||
minute=profile.schedule_fixed_minute)
|
||||
|
||||
self.add_job(self.app.on_create_backup, trigger, id='create-backup', misfire_grace_time=180)
|
||||
self.start()
|
||||
|
||||
def next_job(self):
|
||||
if self.get_jobs():
|
||||
job = self.scheduler.get_job('create-backup')
|
||||
return f"Next run: {job.next_run_time.strftime('%Y-%m-%d %H:%M')}"
|
||||
else:
|
||||
return 'Manual Backups'
|
||||
self.add_job(self.app.create_backup, trigger, id='create-backup', misfire_grace_time=180)
|
||||
job = self.get_job('create-backup')
|
||||
self.next_job = f"Next run: {job.next_run_time.strftime('%Y-%m-%d %H:%M')}"
|
||||
|
|
|
@ -17,7 +17,7 @@ class TrayMenu(QSystemTrayIcon):
|
|||
self.app = parent
|
||||
menu = QMenu()
|
||||
|
||||
self.status = menu.addAction(self.app.scheduler.next_job())
|
||||
self.status = menu.addAction(self.app.scheduler.next_job)
|
||||
self.status.setEnabled(False)
|
||||
|
||||
self.create_action = menu.addAction("Backup Now")
|
||||
|
@ -53,5 +53,5 @@ class TrayMenu(QSystemTrayIcon):
|
|||
self.status.setText('Backup in Progress')
|
||||
self.create_action.setText('Cancel Backup')
|
||||
else:
|
||||
self.status.setText(self.app.scheduler.next_job())
|
||||
self.status.setText(self.app.scheduler.next_job)
|
||||
self.create_action.setText('Backup Now')
|
||||
|
|
|
@ -83,27 +83,27 @@ class MainWindow(MainWindowBase, MainWindowUI):
|
|||
except:
|
||||
pass
|
||||
|
||||
def create_get_result(self, result):
|
||||
self.createStartBtn.setEnabled(True)
|
||||
self.createStartBtn.repaint()
|
||||
self.set_status(progress_max=100)
|
||||
if result['returncode'] == 0:
|
||||
new_snapshot, created = SnapshotModel.get_or_create(
|
||||
snapshot_id=result['data']['archive']['id'],
|
||||
defaults={
|
||||
'name': result['data']['archive']['name'],
|
||||
'time': parser.parse(result['data']['archive']['start']),
|
||||
'repo': self.profile.repo
|
||||
}
|
||||
)
|
||||
new_snapshot.save()
|
||||
if 'cache' in result['data'] and created:
|
||||
stats = result['data']['cache']['stats']
|
||||
repo = self.profile.repo
|
||||
repo.total_size = stats['total_size']
|
||||
repo.unique_csize = stats['unique_csize']
|
||||
repo.unique_size = stats['unique_size']
|
||||
repo.total_unique_chunks = stats['total_unique_chunks']
|
||||
repo.save()
|
||||
self.snapshotTab.populate()
|
||||
|
||||
# def create_get_result(self, result):
|
||||
# self.createStartBtn.setEnabled(True)
|
||||
# self.createStartBtn.repaint()
|
||||
# self.set_status(progress_max=100)
|
||||
# if result['returncode'] == 0:
|
||||
# new_snapshot, created = SnapshotModel.get_or_create(
|
||||
# snapshot_id=result['data']['archive']['id'],
|
||||
# defaults={
|
||||
# 'name': result['data']['archive']['name'],
|
||||
# 'time': parser.parse(result['data']['archive']['start']),
|
||||
# 'repo': self.profile.repo
|
||||
# }
|
||||
# )
|
||||
# new_snapshot.save()
|
||||
# if 'cache' in result['data'] and created:
|
||||
# stats = result['data']['cache']['stats']
|
||||
# repo = self.profile.repo
|
||||
# repo.total_size = stats['total_size']
|
||||
# repo.unique_csize = stats['unique_csize']
|
||||
# repo.unique_size = stats['unique_size']
|
||||
# repo.total_unique_chunks = stats['total_unique_chunks']
|
||||
# repo.save()
|
||||
# self.snapshotTab.populate()
|
||||
#
|
||||
|
|
|
@ -41,7 +41,7 @@ class ScheduleTab(ScheduleBase, ScheduleUI):
|
|||
|
||||
self.scheduleApplyButton.clicked.connect(self.on_scheduler_apply)
|
||||
|
||||
self.nextBackupDateTimeLabel.setText(self.app.scheduler.next_job())
|
||||
self.nextBackupDateTimeLabel.setText(self.app.scheduler.next_job)
|
||||
self.init_wifi()
|
||||
self.init_logs()
|
||||
|
||||
|
@ -103,5 +103,5 @@ class ScheduleTab(ScheduleBase, ScheduleUI):
|
|||
self.profile.schedule_fixed_hour, self.profile.schedule_fixed_minute = qtime.hour(), qtime.minute()
|
||||
self.profile.save()
|
||||
self.app.scheduler.reload()
|
||||
self.nextBackupDateTimeLabel.setText(self.app.scheduler.next_job())
|
||||
self.nextBackupDateTimeLabel.setText(self.app.scheduler.next_job)
|
||||
self.nextBackupDateTimeLabel.repaint()
|
||||
|
|
|
@ -3,14 +3,12 @@ import pytest
|
|||
import io
|
||||
import peewee
|
||||
from PyQt5 import QtCore
|
||||
from PyQt5.QtWidgets import QMenu, QApplication, QSystemTrayIcon
|
||||
|
||||
import vorta.borg_runner
|
||||
import vorta.models
|
||||
from vorta.application import VortaApp
|
||||
from vorta.views.repo_add import AddRepoWindow
|
||||
from vorta.models import EventLogModel, RepoModel
|
||||
from vorta.tray_menu import TrayMenu
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
|
@ -27,12 +25,6 @@ def main(app, qtbot):
|
|||
qtbot.addWidget(main)
|
||||
return main
|
||||
|
||||
# def test_tray(app, qtbot):
|
||||
# # app.tray.activated.emit(QSystemTrayIcon.Context)
|
||||
# menu = app.tray.contextMenu()
|
||||
# qtbot.addWidget(menu)
|
||||
# menu.popup(QtCore.QPoint())
|
||||
|
||||
|
||||
def test_repo_tab(main, qtbot):
|
||||
qtbot.mouseClick(main.createStartBtn, QtCore.Qt.LeftButton)
|
||||
|
|
Loading…
Reference in New Issue