From 6abd0f5862140273c6c1a23c33c288f13529f924 Mon Sep 17 00:00:00 2001 From: Sam Date: Fri, 1 Sep 2023 23:13:17 +0530 Subject: [PATCH 1/7] added peewee to requirements.d/dev.txt --- requirements.d/dev.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements.d/dev.txt b/requirements.d/dev.txt index 5391c54a0..9cc3cc9c8 100644 --- a/requirements.d/dev.txt +++ b/requirements.d/dev.txt @@ -3,6 +3,7 @@ coverage flake8 macholib nox +peewee pkgconfig pre-commit pyinstaller From 7b800661c2e6822c4c259afefe3fd27fb6bf26ed Mon Sep 17 00:00:00 2001 From: Sam Date: Wed, 6 Sep 2023 11:57:06 +0530 Subject: [PATCH 2/7] removed peewee from requirements: not necessary --- requirements.d/dev.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/requirements.d/dev.txt b/requirements.d/dev.txt index 9cc3cc9c8..5391c54a0 100644 --- a/requirements.d/dev.txt +++ b/requirements.d/dev.txt @@ -3,7 +3,6 @@ coverage flake8 macholib nox -peewee pkgconfig pre-commit pyinstaller From 384c57af57f9bd46d3fae6b567cfbc9057fed399 Mon Sep 17 00:00:00 2001 From: Sam Date: Wed, 28 Feb 2024 20:38:31 +0530 Subject: [PATCH 3/7] feat: file selection dialog for selecting pre and post backup script in schedule tab --- src/vorta/assets/UI/scheduletab.ui | 28 ++++++++++++++++++++++++++++ src/vorta/utils.py | 15 +++++++++++++-- src/vorta/views/schedule_tab.py | 26 +++++++++++++++++++++++++- 3 files changed, 66 insertions(+), 3 deletions(-) diff --git a/src/vorta/assets/UI/scheduletab.ui b/src/vorta/assets/UI/scheduletab.ui index f7cb7b34e..b58142871 100644 --- a/src/vorta/assets/UI/scheduletab.ui +++ b/src/vorta/assets/UI/scheduletab.ui @@ -725,6 +725,20 @@ + + + + Choose a local Pre-backup script + + + ... + + + + :/icons/file.svg:/icons/file.svg + + + @@ -764,6 +778,20 @@ + + + + Choose a local Post-backup script + + + ... + + + + :/icons/file.svg:/icons/file.svg + + + 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..11cd2d4db 100644 --- a/src/vorta/views/schedule_tab.py +++ b/src/vorta/views/schedule_tab.py @@ -12,7 +12,7 @@ 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.utils import choose_file_dialog, get_asset, get_sorted_wifis from vorta.views.utils import get_colored_icon uifile = get_asset('UI/scheduletab.ui') @@ -81,6 +81,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.chooseLocalPreBackupScriptButton.clicked.connect(lambda: self.choose_local_script(context="pre")) + self.chooseLocalPostBackupScriptButton.clicked.connect(lambda: self.choose_local_script(context="post")) # Network and shell commands events self.meteredNetworksCheckBox.stateChanged.connect( @@ -135,6 +137,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.chooseLocalPreBackupScriptButton.setIcon(get_colored_icon('file')) + self.chooseLocalPostBackupScriptButton.setIcon(get_colored_icon('file')) def populate_from_profile(self): """Populate current view with data from selected profile.""" @@ -237,3 +241,23 @@ def populate_logs(self): QTableWidgetItem(str(log_line.returncode)), ) self.logTableWidget.setSortingEnabled(sorting) # restore sorting now that modifications are done + + def choose_local_script(self, context: str) -> None: + def save_to_profile(target, script): + with open(script, "r") as s: + content = s.read() + target.setText(content) + attr = 'pre_backup_cmd' if context == "pre" else 'post_backup_cmd' + self.save_profile_attr(attr, content) + + def receive(): + script = dialog.selectedFiles()[0] + if context == "pre": + save_to_profile(self.preBackupCmdLineEdit, script) + elif context == "post": + save_to_profile(self.postBackupCmdLineEdit, script) + + dialog = choose_file_dialog( + self, self.tr('Choose Script'), want_folder=False, file_filter='*.sh', single_selection=True + ) + dialog.open(receive) From 2b55cd9866c17923590dc674818b13869b7ad067 Mon Sep 17 00:00:00 2001 From: Sam Date: Sun, 3 Mar 2024 01:25:54 +0530 Subject: [PATCH 4/7] feat: editor for pre and post backup cmd --- src/vorta/assets/UI/scheduletab.ui | 28 +++++++++++ src/vorta/assets/UI/scriptedit.ui | 72 +++++++++++++++++++++++++++ src/vorta/views/schedule_tab.py | 10 ++++ src/vorta/views/script_edit_dialog.py | 49 ++++++++++++++++++ 4 files changed, 159 insertions(+) create mode 100644 src/vorta/assets/UI/scriptedit.ui create mode 100644 src/vorta/views/script_edit_dialog.py diff --git a/src/vorta/assets/UI/scheduletab.ui b/src/vorta/assets/UI/scheduletab.ui index b58142871..cfc521255 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 + + + @@ -778,6 +792,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..3b5ce9923 --- /dev/null +++ b/src/vorta/assets/UI/scriptedit.ui @@ -0,0 +1,72 @@ + + + ScriptEdit + + + Edit Script + + + true + + + + 0 + + + + + + + Save + + + + + + + + Monospace + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Custom Pre/Post backup script: + + + + + + + + + + diff --git a/src/vorta/views/schedule_tab.py b/src/vorta/views/schedule_tab.py index 11cd2d4db..131914239 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 choose_file_dialog, 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') @@ -83,6 +84,8 @@ def __init__(self, parent=None): self.scheduleFixedTime.timeChanged.connect(self.on_scheduler_change) self.chooseLocalPreBackupScriptButton.clicked.connect(lambda: self.choose_local_script(context="pre")) self.chooseLocalPostBackupScriptButton.clicked.connect(lambda: self.choose_local_script(context="post")) + 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( @@ -139,6 +142,8 @@ def set_icons(self): self.toolBox.setItemIcon(3, get_colored_icon('terminal')) self.chooseLocalPreBackupScriptButton.setIcon(get_colored_icon('file')) self.chooseLocalPostBackupScriptButton.setIcon(get_colored_icon('file')) + 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.""" @@ -261,3 +266,8 @@ def receive(): self, self.tr('Choose Script'), want_folder=False, file_filter='*.sh', single_selection=True ) dialog.open(receive) + + 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..bf437f7da --- /dev/null +++ b/src/vorta/views/script_edit_dialog.py @@ -0,0 +1,49 @@ +from PyQt6 import QtCore, uic + +from vorta.store.models import BackupProfileMixin +from vorta.utils import get_asset + +uifile = get_asset("UI/scriptedit.ui") +ScriptEditUI, ScriptEditBase = uic.loadUiType(uifile) + + +class ScriptEditWindow(ScriptEditUI, ScriptEditBase, BackupProfileMixin): + 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")) + elif context == "post": + self.setWindowTitle(self.tr("Edit Post-Backup Script")) + + self.profile = profile + self.saveScriptButton.clicked.connect(self.save_script) + + # 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() From c5fc7c9c3f2de2b4d0104f683b686a6f12725d1f Mon Sep 17 00:00:00 2001 From: Sam Date: Mon, 4 Mar 2024 14:04:12 +0530 Subject: [PATCH 5/7] Not needed: Removed the add from file button/dialog. --- src/vorta/assets/UI/scheduletab.ui | 28 --------------------------- src/vorta/assets/UI/scriptedit.ui | 8 ++++++++ src/vorta/views/schedule_tab.py | 26 +------------------------ src/vorta/views/script_edit_dialog.py | 3 +-- 4 files changed, 10 insertions(+), 55 deletions(-) diff --git a/src/vorta/assets/UI/scheduletab.ui b/src/vorta/assets/UI/scheduletab.ui index cfc521255..695391b7a 100644 --- a/src/vorta/assets/UI/scheduletab.ui +++ b/src/vorta/assets/UI/scheduletab.ui @@ -739,20 +739,6 @@ - - - - Choose a local Pre-backup script - - - ... - - - - :/icons/file.svg:/icons/file.svg - - - @@ -806,20 +792,6 @@ - - - - Choose a local Post-backup script - - - ... - - - - :/icons/file.svg:/icons/file.svg - - - diff --git a/src/vorta/assets/UI/scriptedit.ui b/src/vorta/assets/UI/scriptedit.ui index 3b5ce9923..28a209c28 100644 --- a/src/vorta/assets/UI/scriptedit.ui +++ b/src/vorta/assets/UI/scriptedit.ui @@ -2,6 +2,14 @@ ScriptEdit + + + 0 + 0 + 440 + 260 + + Edit Script diff --git a/src/vorta/views/schedule_tab.py b/src/vorta/views/schedule_tab.py index 131914239..b2c720dec 100644 --- a/src/vorta/views/schedule_tab.py +++ b/src/vorta/views/schedule_tab.py @@ -12,7 +12,7 @@ from vorta.i18n import get_locale from vorta.scheduler import ScheduleStatusType from vorta.store.models import BackupProfileMixin, EventLogModel, WifiSettingModel -from vorta.utils import choose_file_dialog, get_asset, get_sorted_wifis +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 @@ -82,8 +82,6 @@ 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.chooseLocalPreBackupScriptButton.clicked.connect(lambda: self.choose_local_script(context="pre")) - self.chooseLocalPostBackupScriptButton.clicked.connect(lambda: self.choose_local_script(context="post")) self.preBackupScriptEditorButton.clicked.connect(lambda: self.launch_script_editor(context="pre")) self.postBackupScriptEditorButton.clicked.connect(lambda: self.launch_script_editor(context="post")) @@ -140,8 +138,6 @@ 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.chooseLocalPreBackupScriptButton.setIcon(get_colored_icon('file')) - self.chooseLocalPostBackupScriptButton.setIcon(get_colored_icon('file')) self.preBackupScriptEditorButton.setIcon(get_colored_icon('edit')) self.postBackupScriptEditorButton.setIcon(get_colored_icon('edit')) @@ -247,26 +243,6 @@ def populate_logs(self): ) self.logTableWidget.setSortingEnabled(sorting) # restore sorting now that modifications are done - def choose_local_script(self, context: str) -> None: - def save_to_profile(target, script): - with open(script, "r") as s: - content = s.read() - target.setText(content) - attr = 'pre_backup_cmd' if context == "pre" else 'post_backup_cmd' - self.save_profile_attr(attr, content) - - def receive(): - script = dialog.selectedFiles()[0] - if context == "pre": - save_to_profile(self.preBackupCmdLineEdit, script) - elif context == "post": - save_to_profile(self.postBackupCmdLineEdit, script) - - dialog = choose_file_dialog( - self, self.tr('Choose Script'), want_folder=False, file_filter='*.sh', single_selection=True - ) - dialog.open(receive) - def launch_script_editor(self, context: str) -> None: edit_window = ScriptEditWindow(context, profile=self.profile()) edit_window.exec() diff --git a/src/vorta/views/script_edit_dialog.py b/src/vorta/views/script_edit_dialog.py index bf437f7da..adb38babe 100644 --- a/src/vorta/views/script_edit_dialog.py +++ b/src/vorta/views/script_edit_dialog.py @@ -1,13 +1,12 @@ from PyQt6 import QtCore, uic -from vorta.store.models import BackupProfileMixin from vorta.utils import get_asset uifile = get_asset("UI/scriptedit.ui") ScriptEditUI, ScriptEditBase = uic.loadUiType(uifile) -class ScriptEditWindow(ScriptEditUI, ScriptEditBase, BackupProfileMixin): +class ScriptEditWindow(ScriptEditUI, ScriptEditBase): def __init__(self, context: str, profile, parent=None) -> None: super().__init__(parent) self.setupUi(self) From dae0ce153f540f62458288771eefe76c5c99500f Mon Sep 17 00:00:00 2001 From: Sam Date: Mon, 4 Mar 2024 15:25:12 +0530 Subject: [PATCH 6/7] minor changes: spacing between textedit and save button --- src/vorta/assets/UI/scriptedit.ui | 64 +++++++++++++++++++------------ 1 file changed, 40 insertions(+), 24 deletions(-) diff --git a/src/vorta/assets/UI/scriptedit.ui b/src/vorta/assets/UI/scriptedit.ui index 28a209c28..8da8a9908 100644 --- a/src/vorta/assets/UI/scriptedit.ui +++ b/src/vorta/assets/UI/scriptedit.ui @@ -22,7 +22,40 @@ - + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Custom Pre/Post backup script: + + + + Save @@ -38,39 +71,22 @@ - - + + Qt::Vertical - - - 20 - 40 - - - - - - - - Qt::Horizontal + + QSizePolicy::Fixed - 40 - 20 + 20 + 5 - - - - Custom Pre/Post backup script: - - - From e070fda084342494b73c2e0b0dfe9661e68c4aff Mon Sep 17 00:00:00 2001 From: Sam Date: Tue, 5 Mar 2024 13:43:24 +0530 Subject: [PATCH 7/7] minor changes: script editor dialog --- src/vorta/assets/UI/scriptedit.ui | 82 +++++++++++++-------------- src/vorta/views/script_edit_dialog.py | 10 +++- 2 files changed, 48 insertions(+), 44 deletions(-) diff --git a/src/vorta/assets/UI/scriptedit.ui b/src/vorta/assets/UI/scriptedit.ui index 8da8a9908..bfc8edcbd 100644 --- a/src/vorta/assets/UI/scriptedit.ui +++ b/src/vorta/assets/UI/scriptedit.ui @@ -20,75 +20,71 @@ 0 + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 10 + + + + - - - - Qt::Horizontal - - - - 40 - 20 - + + + + Pre/Post-Backup Script: - + - + Qt::Vertical + + QSizePolicy::Fixed + 20 - 40 + 5 - - - - Custom Pre/Post backup script: - - - - - - - Save - - - - + Monospace - - - - - - Qt::Vertical - - - QSizePolicy::Fixed + + IBeamCursor - - - 20 - 5 - + + QTextEdit::NoWrap - + + + + + QDialogButtonBox::Cancel|QDialogButtonBox::Save + + + diff --git a/src/vorta/views/script_edit_dialog.py b/src/vorta/views/script_edit_dialog.py index adb38babe..3bbed7baf 100644 --- a/src/vorta/views/script_edit_dialog.py +++ b/src/vorta/views/script_edit_dialog.py @@ -1,4 +1,5 @@ from PyQt6 import QtCore, uic +from PyQt6.QtWidgets import QDialogButtonBox from vorta.utils import get_asset @@ -15,11 +16,18 @@ def __init__(self, context: str, profile, parent=None) -> None: 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.saveScriptButton.clicked.connect(self.save_script) + + 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()