diff --git a/src/vorta/assets/UI/scheduletab.ui b/src/vorta/assets/UI/scheduletab.ui
index f7cb7b34e..695391b7a 100644
--- a/src/vorta/assets/UI/scheduletab.ui
+++ b/src/vorta/assets/UI/scheduletab.ui
@@ -725,6 +725,20 @@
+ -
+
+
+ Open Pre-backup script editor
+
+
+ ...
+
+
+
+ :/icons/edit.svg:/icons/edit.svg
+
+
+
-
@@ -764,6 +778,20 @@
+ -
+
+
+ Open Post-backup script editor
+
+
+ ...
+
+
+
+ :/icons/edit.svg:/icons/edit.svg
+
+
+
diff --git a/src/vorta/assets/UI/scriptedit.ui b/src/vorta/assets/UI/scriptedit.ui
new file mode 100644
index 000000000..bfc8edcbd
--- /dev/null
+++ b/src/vorta/assets/UI/scriptedit.ui
@@ -0,0 +1,92 @@
+
+
+ ScriptEdit
+
+
+
+ 0
+ 0
+ 440
+ 260
+
+
+
+ Edit Script
+
+
+ true
+
+
+
+ 0
+
+ -
+
+
+ Qt::Vertical
+
+
+ QSizePolicy::Fixed
+
+
+
+ 20
+ 10
+
+
+
+
+ -
+
+
-
+
+
+ Pre/Post-Backup Script:
+
+
+
+ -
+
+
+ Qt::Vertical
+
+
+ QSizePolicy::Fixed
+
+
+
+ 20
+ 5
+
+
+
+
+ -
+
+
+
+ Monospace
+
+
+
+ IBeamCursor
+
+
+ QTextEdit::NoWrap
+
+
+
+
+
+ -
+
+
+ QDialogButtonBox::Cancel|QDialogButtonBox::Save
+
+
+
+
+
+
+
+
diff --git a/src/vorta/utils.py b/src/vorta/utils.py
index 033b486a5..7ba31f919 100644
--- a/src/vorta/utils.py
+++ b/src/vorta/utils.py
@@ -15,7 +15,13 @@
import psutil
from PyQt6 import QtCore
from PyQt6.QtCore import QFileInfo, QThread, pyqtSignal
-from PyQt6.QtWidgets import QApplication, QFileDialog, QSystemTrayIcon
+from PyQt6.QtWidgets import (
+ QAbstractItemView,
+ QApplication,
+ QFileDialog,
+ QSystemTrayIcon,
+ QTreeView,
+)
from vorta.borg._compatibility import BorgCompatibility
from vorta.log import logger
@@ -166,12 +172,17 @@ def get_dict_from_list(dataDict, mapList):
return reduce(lambda d, k: d.setdefault(k, {}), mapList, dataDict)
-def choose_file_dialog(parent, title, want_folder=True):
+def choose_file_dialog(parent, title, want_folder=True, file_filter=None, single_selection=False):
dialog = QFileDialog(parent, title, os.path.expanduser('~'))
dialog.setFileMode(QFileDialog.FileMode.Directory if want_folder else QFileDialog.FileMode.ExistingFiles)
dialog.setParent(parent, QtCore.Qt.WindowType.Sheet)
if want_folder:
dialog.setOption(QFileDialog.Option.ShowDirsOnly)
+ elif file_filter:
+ dialog.setNameFilter(file_filter)
+ if single_selection:
+ tree_view = dialog.findChild(QTreeView)
+ tree_view.setSelectionMode(QAbstractItemView.SelectionMode.SingleSelection)
return dialog
diff --git a/src/vorta/views/schedule_tab.py b/src/vorta/views/schedule_tab.py
index dc97cfa47..b2c720dec 100644
--- a/src/vorta/views/schedule_tab.py
+++ b/src/vorta/views/schedule_tab.py
@@ -13,6 +13,7 @@
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.script_edit_dialog import ScriptEditWindow
from vorta.views.utils import get_colored_icon
uifile = get_asset('UI/scheduletab.ui')
@@ -81,6 +82,8 @@ def __init__(self, parent=None):
self.scheduleIntervalCount.valueChanged.connect(self.on_scheduler_change)
self.scheduleIntervalUnit.currentIndexChanged.connect(self.on_scheduler_change)
self.scheduleFixedTime.timeChanged.connect(self.on_scheduler_change)
+ self.preBackupScriptEditorButton.clicked.connect(lambda: self.launch_script_editor(context="pre"))
+ self.postBackupScriptEditorButton.clicked.connect(lambda: self.launch_script_editor(context="post"))
# Network and shell commands events
self.meteredNetworksCheckBox.stateChanged.connect(
@@ -135,6 +138,8 @@ def set_icons(self):
self.toolBox.setItemIcon(1, get_colored_icon('wifi'))
self.toolBox.setItemIcon(2, get_colored_icon('tasks'))
self.toolBox.setItemIcon(3, get_colored_icon('terminal'))
+ self.preBackupScriptEditorButton.setIcon(get_colored_icon('edit'))
+ self.postBackupScriptEditorButton.setIcon(get_colored_icon('edit'))
def populate_from_profile(self):
"""Populate current view with data from selected profile."""
@@ -237,3 +242,8 @@ def populate_logs(self):
QTableWidgetItem(str(log_line.returncode)),
)
self.logTableWidget.setSortingEnabled(sorting) # restore sorting now that modifications are done
+
+ def launch_script_editor(self, context: str) -> None:
+ edit_window = ScriptEditWindow(context, profile=self.profile())
+ edit_window.exec()
+ self.populate_from_profile() # Refresh the view after the script has been edited.
diff --git a/src/vorta/views/script_edit_dialog.py b/src/vorta/views/script_edit_dialog.py
new file mode 100644
index 000000000..3bbed7baf
--- /dev/null
+++ b/src/vorta/views/script_edit_dialog.py
@@ -0,0 +1,56 @@
+from PyQt6 import QtCore, uic
+from PyQt6.QtWidgets import QDialogButtonBox
+
+from vorta.utils import get_asset
+
+uifile = get_asset("UI/scriptedit.ui")
+ScriptEditUI, ScriptEditBase = uic.loadUiType(uifile)
+
+
+class ScriptEditWindow(ScriptEditUI, ScriptEditBase):
+ def __init__(self, context: str, profile, parent=None) -> None:
+ super().__init__(parent)
+ self.setupUi(self)
+ self.setAttribute(QtCore.Qt.WidgetAttribute.WA_DeleteOnClose)
+
+ self.context = context
+ if context == "pre":
+ self.setWindowTitle(self.tr("Edit Pre-Backup Script"))
+ self.label.setText(self.tr("Pre-Backup Script:"))
+ elif context == "post":
+ self.setWindowTitle(self.tr("Edit Post-Backup Script"))
+ self.label.setText(self.tr("Post-Backup Script:"))
+
+ self.buttonBox.button(QDialogButtonBox.StandardButton.Save).setText(self.tr("Save"))
+ self.buttonBox.button(QDialogButtonBox.StandardButton.Cancel).setText(self.tr("Cancel"))
+
+ self.profile = profile
+
+ self.buttonBox.button(QDialogButtonBox.StandardButton.Save).clicked.connect(self.save_script)
+ self.buttonBox.button(QDialogButtonBox.StandardButton.Cancel).clicked.connect(self.close)
+
+ # Populate data from profile
+ self.populate_from_profile()
+
+ def populate_from_profile(self):
+ """Populate the script editor with the current profile's script."""
+ profile = self.profile
+ if self.context == "pre":
+ self.scriptEdit.setPlainText(profile.pre_backup_cmd)
+ elif self.context == "post":
+ self.scriptEdit.setPlainText(profile.post_backup_cmd)
+
+ def save_profile_attr(self, attr, new_value):
+ profile = self.profile
+ setattr(profile, attr, new_value)
+ profile.save()
+
+ def save_script(self):
+ script = self.scriptEdit.toPlainText()
+ profile = self.profile
+ if self.context == "pre":
+ self.save_profile_attr("pre_backup_cmd", script)
+ elif self.context == "post":
+ self.save_profile_attr("post_backup_cmd", script)
+ profile.save()
+ self.close()