vorta/src/vorta/views/schedule_tab.py

229 lines
10 KiB
Python

from PyQt6 import QtCore, uic
from PyQt6.QtCore import QDateTime, QLocale
from PyQt6.QtWidgets import QAbstractItemView, QApplication, QHeaderView, QListWidgetItem, QTableWidgetItem
from vorta import application
from vorta.i18n import get_locale
from vorta.scheduler import ScheduleStatusType
from vorta.store.models import BackupProfileMixin, EventLogModel, WifiSettingModel
from vorta.utils import get_asset, get_sorted_wifis
from vorta.views.utils import get_colored_icon
uifile = get_asset('UI/scheduletab.ui')
ScheduleUI, ScheduleBase = uic.loadUiType(uifile)
class LogTableColumn:
Time = 0
Category = 1
Subcommand = 2
Repository = 3
ReturnCode = 4
class ScheduleTab(ScheduleBase, ScheduleUI, BackupProfileMixin):
def __init__(self, parent=None):
super().__init__(parent)
self.setupUi(parent)
self.app: application.VortaApp = QApplication.instance()
self.toolBox.setCurrentIndex(0)
self.schedulerRadioMapping = {
'off': self.scheduleOffRadio,
'interval': self.scheduleIntervalRadio,
'fixed': self.scheduleFixedRadio,
}
# Set up log table
self.logTableWidget.setAlternatingRowColors(True)
header = self.logTableWidget.horizontalHeader()
header.setVisible(True)
[header.setSectionResizeMode(i, QHeaderView.ResizeMode.ResizeToContents) for i in range(5)]
header.setSectionResizeMode(3, QHeaderView.ResizeMode.Stretch)
self.logTableWidget.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectRows)
self.logTableWidget.setEditTriggers(QAbstractItemView.EditTrigger.NoEditTriggers)
# Scheduler intervals we know
self.scheduleIntervalUnit.addItem(self.tr('Minutes'), 'minutes')
self.scheduleIntervalUnit.addItem(self.tr('Hours'), 'hours')
self.scheduleIntervalUnit.addItem(self.tr('Days'), 'days')
self.scheduleIntervalUnit.addItem(self.tr('Weeks'), 'weeks')
# Enable/Disable entries on button state changed
self.framePeriodic.setEnabled(False)
self.frameDaily.setEnabled(False)
self.frameValidation.setEnabled(False)
self.scheduleIntervalRadio.toggled.connect(self.framePeriodic.setEnabled)
self.scheduleFixedRadio.toggled.connect(self.frameDaily.setEnabled)
self.validationCheckBox.toggled.connect(self.frameValidation.setEnabled)
# POPULATE with data
self.populate_from_profile()
self.set_icons()
# Connect events
self.app.backup_finished_event.connect(self.populate_logs)
# Scheduler events
for label, obj in self.schedulerRadioMapping.items():
obj.clicked.connect(self.on_scheduler_change)
self.scheduleIntervalCount.valueChanged.connect(self.on_scheduler_change)
self.scheduleIntervalUnit.currentIndexChanged.connect(self.on_scheduler_change)
self.scheduleFixedTime.timeChanged.connect(self.on_scheduler_change)
# Network and shell commands events
self.meteredNetworksCheckBox.stateChanged.connect(
lambda new_val, attr='dont_run_on_metered_networks': self.save_profile_attr(attr, not new_val)
)
self.postBackupCmdLineEdit.textEdited.connect(
lambda new_val, attr='post_backup_cmd': self.save_profile_attr(attr, new_val)
)
self.preBackupCmdLineEdit.textEdited.connect(
lambda new_val, attr='pre_backup_cmd': self.save_profile_attr(attr, new_val)
)
self.createCmdLineEdit.textEdited.connect(
lambda new_val, attr='create_backup_cmd': self.save_repo_attr(attr, new_val)
)
self.missedBackupsCheckBox.stateChanged.connect(
lambda new_val, attr='schedule_make_up_missed': self.save_profile_attr(attr, new_val)
)
self.pruneCheckBox.stateChanged.connect(lambda new_val, attr='prune_on': self.save_profile_attr(attr, new_val))
self.validationCheckBox.stateChanged.connect(
lambda new_val, attr='validation_on': self.save_profile_attr(attr, new_val)
)
self.validationWeeksCount.valueChanged.connect(
lambda new_val, attr='validation_weeks': self.save_profile_attr(attr, new_val)
)
# Connect to schedule update
self.app.scheduler.schedule_changed.connect(lambda pid: self.draw_next_scheduled_backup())
# Connect to palette change
self.app.paletteChanged.connect(lambda p: self.set_icons())
def on_scheduler_change(self, _):
profile = self.profile()
# Save scheduler settings, apply new scheduler and display next task for profile.
for label, obj in self.schedulerRadioMapping.items():
if obj.isChecked():
profile.schedule_mode = label
profile.schedule_interval_unit = self.scheduleIntervalUnit.currentData()
profile.schedule_interval_count = self.scheduleIntervalCount.value()
qtime = self.scheduleFixedTime.time()
profile.schedule_fixed_hour, profile.schedule_fixed_minute = (
qtime.hour(),
qtime.minute(),
)
profile.save()
self.app.scheduler.set_timer_for_profile(profile.id)
self.draw_next_scheduled_backup()
def set_icons(self):
self.toolBox.setItemIcon(0, get_colored_icon('clock-o'))
self.toolBox.setItemIcon(1, get_colored_icon('wifi'))
self.toolBox.setItemIcon(2, get_colored_icon('tasks'))
self.toolBox.setItemIcon(3, get_colored_icon('terminal'))
def populate_from_profile(self):
"""Populate current view with data from selected profile."""
profile = self.profile()
self.schedulerRadioMapping[profile.schedule_mode].setChecked(True)
# Set interval scheduler options
self.scheduleIntervalUnit.setCurrentIndex(self.scheduleIntervalUnit.findData(profile.schedule_interval_unit))
self.scheduleIntervalCount.setValue(profile.schedule_interval_count)
# Set fixed daily time scheduler options
self.scheduleFixedTime.setTime(QtCore.QTime(profile.schedule_fixed_hour, profile.schedule_fixed_minute))
# Set borg-check options
self.validationCheckBox.setCheckState(
QtCore.Qt.CheckState.Checked if profile.validation_on else QtCore.Qt.CheckState.Unchecked
)
self.validationWeeksCount.setValue(profile.validation_weeks)
# Other checkbox options
self.pruneCheckBox.setCheckState(
QtCore.Qt.CheckState.Checked if profile.prune_on else QtCore.Qt.CheckState.Unchecked
)
self.missedBackupsCheckBox.setCheckState(
QtCore.Qt.CheckState.Checked if profile.schedule_make_up_missed else QtCore.Qt.CheckState.Unchecked
)
self.meteredNetworksCheckBox.setChecked(False if profile.dont_run_on_metered_networks else True)
self.preBackupCmdLineEdit.setText(profile.pre_backup_cmd)
self.postBackupCmdLineEdit.setText(profile.post_backup_cmd)
if profile.repo:
self.createCmdLineEdit.setText(profile.repo.create_backup_cmd)
self.createCmdLineEdit.setEnabled(True)
else:
self.createCmdLineEdit.setEnabled(False)
self.populate_wifi()
self.populate_logs()
self.draw_next_scheduled_backup()
def draw_next_scheduled_backup(self):
status = self.app.scheduler.next_job_for_profile(self.profile().id)
if status.type in (
ScheduleStatusType.SCHEDULED,
ScheduleStatusType.TOO_FAR_AHEAD,
):
time = QDateTime.fromMSecsSinceEpoch(int(status.time.timestamp() * 1000))
text = get_locale().toString(time, QLocale.FormatType.LongFormat)
elif status.type == ScheduleStatusType.NO_PREVIOUS_BACKUP:
text = self.tr('Run a manual backup first')
else:
text = self.tr('None scheduled')
self.nextBackupDateTimeLabel.setText(text)
self.nextBackupDateTimeLabel.repaint()
def populate_wifi(self):
self.wifiListWidget.clear()
for wifi in get_sorted_wifis(self.profile()):
item = QListWidgetItem()
item.setText(wifi.ssid)
item.setFlags(item.flags() | QtCore.Qt.ItemFlag.ItemIsUserCheckable)
if wifi.allowed:
item.setCheckState(QtCore.Qt.CheckState.Checked)
else:
item.setCheckState(QtCore.Qt.CheckState.Unchecked)
self.wifiListWidget.addItem(item)
self.wifiListWidget.itemChanged.connect(self.save_wifi_item)
def save_wifi_item(self, item):
db_item = WifiSettingModel.get(ssid=item.text(), profile=self.profile().id)
db_item.allowed = item.checkState() == 2
db_item.save()
def save_profile_attr(self, attr, new_value):
profile = self.profile()
setattr(profile, attr, new_value)
profile.save()
def save_repo_attr(self, attr, new_value):
repo = self.profile().repo
setattr(repo, attr, new_value)
repo.save()
def populate_logs(self):
event_logs = [s for s in EventLogModel.select().order_by(EventLogModel.start_time.desc())]
sorting = self.logTableWidget.isSortingEnabled()
self.logTableWidget.setSortingEnabled(False) # disable sorting while modifying the table.
self.logTableWidget.setRowCount(len(event_logs)) # go ahead and set table length and then update the rows
for row, log_line in enumerate(event_logs):
formatted_time = log_line.start_time.strftime('%Y-%m-%d %H:%M')
self.logTableWidget.setItem(row, LogTableColumn.Time, QTableWidgetItem(formatted_time))
self.logTableWidget.setItem(row, LogTableColumn.Category, QTableWidgetItem(log_line.category))
self.logTableWidget.setItem(row, LogTableColumn.Subcommand, QTableWidgetItem(log_line.subcommand))
self.logTableWidget.setItem(row, LogTableColumn.Repository, QTableWidgetItem(log_line.repo_url))
self.logTableWidget.setItem(
row,
LogTableColumn.ReturnCode,
QTableWidgetItem(str(log_line.returncode)),
)
self.logTableWidget.setSortingEnabled(sorting) # restore sorting now that modifications are done