Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat: script editor in pre and post backup scripts #1950

Open
wants to merge 20 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions src/vorta/assets/UI/scheduletab.ui
Original file line number Diff line number Diff line change
Expand Up @@ -725,6 +725,20 @@
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="preBackupScriptEditorButton">
<property name="toolTip">
<string> Open Pre-backup script editor</string>
</property>
<property name="text">
<string>...</string>
</property>
<property name="icon">
<iconset>
<normaloff>:/icons/edit.svg</normaloff>:/icons/edit.svg</iconset>
</property>
</widget>
</item>
</layout>
</item>
<item>
Expand Down Expand Up @@ -764,6 +778,20 @@
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="postBackupScriptEditorButton">
<property name="toolTip">
<string> Open Post-backup script editor</string>
</property>
<property name="text">
<string>...</string>
</property>
<property name="icon">
<iconset>
<normaloff>:/icons/edit.svg</normaloff>:/icons/edit.svg</iconset>
</property>
</widget>
</item>
</layout>
</item>
</layout>
Expand Down
92 changes: 92 additions & 0 deletions src/vorta/assets/UI/scriptedit.ui
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>ScriptEdit</class>
<widget class="QDialog" name="ScriptEdit">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>440</width>
<height>260</height>
</rect>
</property>
<property name="windowTitle">
<string>Edit Script</string>
</property>
<property name="modal">
<bool>true</bool>
</property>
<layout class="QGridLayout" name="gridLayout">
<property name="verticalSpacing">
<number>0</number>
</property>
<item row="1" column="0">
<spacer name="verticalSpacer_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>10</height>
</size>
</property>
</spacer>
</item>
<item row="0" column="0">
<layout class="QGridLayout" name="gridLayout_2">
<item row="0" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>Pre/Post-Backup Script:</string>
</property>
</widget>
</item>
<item row="1" column="0">
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>5</height>
</size>
</property>
</spacer>
</item>
<item row="2" column="0">
<widget class="QTextEdit" name="scriptEdit">
<property name="font">
<font>
<family>Monospace</family>
</font>
</property>
<property name="cursor" stdset="0">
<cursorShape>IBeamCursor</cursorShape>
</property>
<property name="lineWrapMode">
<enum>QTextEdit::NoWrap</enum>
</property>
</widget>
</item>
</layout>
</item>
<item row="2" column="0">
<widget class="QDialogButtonBox" name="buttonBox">
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Save</set>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>
15 changes: 13 additions & 2 deletions src/vorta/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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


Expand Down
10 changes: 10 additions & 0 deletions src/vorta/views/schedule_tab.py
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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."""
Expand Down Expand Up @@ -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.
56 changes: 56 additions & 0 deletions src/vorta/views/script_edit_dialog.py
Original file line number Diff line number Diff line change
@@ -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()
Loading