diff --git a/src/vorta/assets/UI/exclude_dialog.ui b/src/vorta/assets/UI/exclude_dialog.ui index 793ad3fdc..370f2d973 100644 --- a/src/vorta/assets/UI/exclude_dialog.ui +++ b/src/vorta/assets/UI/exclude_dialog.ui @@ -52,6 +52,13 @@ + + + + Exclude Files/Folders + + + @@ -97,46 +104,46 @@ - - Exclude If Present - - - - - - true - - - - - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - - - - - - + + Exclude If Present + + + + + + true + + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + + + + Preview diff --git a/src/vorta/borg/create.py b/src/vorta/borg/create.py index 6f1f8c5b9..1fcf0671e 100644 --- a/src/vorta/borg/create.py +++ b/src/vorta/borg/create.py @@ -4,18 +4,13 @@ import tempfile from datetime import datetime as dt +from PyQt6.QtCore import QCoreApplication + from vorta import config from vorta.i18n import trans_late, translate -from vorta.store.models import ( - ArchiveModel, - RepoModel, - SourceFileModel, - WifiSettingModel, -) +from vorta.store.models import ArchiveModel, RepoModel, SourceFileModel, WifiSettingModel from vorta.utils import borg_compat, format_archive_name, get_network_status_monitor -from PyQt6.QtCore import QCoreApplication - from .borg_job import BorgJob diff --git a/src/vorta/views/exclude_dialog.py b/src/vorta/views/exclude_dialog.py index 314a985fc..6cd7d1233 100644 --- a/src/vorta/views/exclude_dialog.py +++ b/src/vorta/views/exclude_dialog.py @@ -1,14 +1,12 @@ +from pathlib import Path +from typing import List + from PyQt6 import uic from PyQt6.QtCore import QModelIndex, QObject, Qt from PyQt6.QtGui import QCursor, QStandardItem, QStandardItemModel -from PyQt6.QtWidgets import ( - QAbstractItemView, - QApplication, - QMenu, - QMessageBox, - QStyledItemDelegate, -) +from PyQt6.QtWidgets import QAbstractItemView, QApplication, QMenu, QMessageBox, QStyledItemDelegate +from vorta.filedialog import VortaFileSelector from vorta.i18n import translate from vorta.store.models import ExclusionModel from vorta.utils import get_asset @@ -87,6 +85,7 @@ def __init__(self, profile, parent=None): self.bPreviewCopy.setIcon(get_colored_icon('copy')) self.bAddPattern.clicked.connect(self.add_pattern_custom_exclusion) self.bAddPattern.setIcon(get_colored_icon('plus')) + self.bOpenExcludeFD.clicked.connect(self.open_exclude_filedialog) self.bAddPatternExcludeIfPresent.setIcon(get_colored_icon('plus')) self.bAddPatternExcludeIfPresent.clicked.connect(self.add_pattern_exclude_if_present) self.bRemovePatternExcludeIfPresent.setIcon(get_colored_icon('minus')) @@ -302,7 +301,7 @@ def text_editing_finished(self, model): ''' for row in range(model.rowCount()): item = model.item(row) - if item.text() == '': + if item is not None and item.text().strip() == '': model.removeRow(row) def custom_pattern_editing_finished(self, editor): @@ -373,7 +372,7 @@ def add_pattern_exclude_if_present(self): self.save_exclude_if_present_patterns() def exclude_if_present_pattern_editing_finished(self, editor): - self.text_editing_finished(self.customExclusionsModel) + self.text_editing_finished(self.excludeIfPresentModel) self.save_exclude_if_present_patterns() def save_exclude_if_present_patterns(self): @@ -448,3 +447,36 @@ def eventFilter(self, source, event): self.remove_pattern() return True return QObject.eventFilter(self, source, event) + + def make_exclude_pattern_from_path(self, paths: List[str]) -> List[str]: + ''' + Create exclusion patterns from a list of given paths. + ''' + patterns = [] + # Both files and folders use the same pattern format (fm:*/name), + # confirmed through testing to correctly exclude them during backup creation. + for path in paths: + patterns.append(f"pf:{path}") + return patterns + + def open_exclude_filedialog(self): + file_dialog = VortaFileSelector( + self, window_title='Exclude Files and Folders', title='Select files and folders to exclude:' + ) + paths = file_dialog.get_paths() # Selected paths from file dialog + if paths: + patterns = self.make_exclude_pattern_from_path(paths) + for pattern in patterns: + # Check if the path already exists in the model + if not ExclusionModel.get_or_none(name=pattern, profile=self.profile): + # Create a new item in the list + item = QStandardItem(pattern) + item.setCheckable(True) + item.setCheckState(Qt.CheckState.Checked) + self.customExclusionsModel.appendRow(item) + + # Add the item to database + ExclusionModel.create( + name=pattern, source=ExclusionModel.SourceFieldOptions.CUSTOM.value, profile=self.profile + ) + self.populate_preview_tab()