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()