diff --git a/src/vorta/assets/exclusion_presets/browsers.json b/src/vorta/assets/exclusion_presets/browsers.json index 95f89c993..a94882181 100644 --- a/src/vorta/assets/exclusion_presets/browsers.json +++ b/src/vorta/assets/exclusion_presets/browsers.json @@ -3,14 +3,14 @@ "name": "Chromium cache and config files", "patterns": [ - "fm:/.config/chromium/*/Local Storage", - "fm:/.config/chromium/*/Session Storage", - "fm:/.config/chromium/*/Service Worker/CacheStorage", - "fm:/.config/chromium/*/Application Cache", - "fm:/.config/chromium/*/History Index *", - "fm:/snap/chromium/common/.cache", - "fm:/snap/chromium/*/.config/chromium/*/Service Worker/CacheStorage", - "fm:/snap/chromium/*/.local/share/" + "fm:.config/chromium/*/Local Storage", + "fm:.config/chromium/*/Session Storage", + "fm:.config/chromium/*/Service Worker/CacheStorage", + "fm:.config/chromium/*/Application Cache", + "fm:.config/chromium/*/History Index *", + "fm:snap/chromium/common/.cache", + "fm:snap/chromium/*/.config/chromium/*/Service Worker/CacheStorage", + "fm:snap/chromium/*/.local/share/" ], "tags":["application:chromium", "type:browser", "os:linux"], "author": "Divi" @@ -51,15 +51,15 @@ "fm:.mozilla/firefox/*/urlclassifier3.sqlite", "fm:.mozilla/firefox/*/blocklist.xml", "fm:.mozilla/firefox/*/extensions.sqlite", - "fm:./mozilla/firefox/*/extensions.sqlite-journal", - "fm:./mozilla/firefox/*/extensions.rdf", - "fm:./mozilla/firefox/*/extensions.ini", - "fm:./mozilla/firefox/*/extensions.cache", - "fm:./mozilla/firefox/*/XUL.mfasl", - "fm:./mozilla/firefox/*/XPC.mfasl", - "fm:./mozilla/firefox/*/xpti.dat", - "fm:./mozilla/firefox/*/compreg.dat", - "fm:./mozilla/firefox/*/pluginreg.dat" + "fm:.mozilla/firefox/*/extensions.sqlite-journal", + "fm:.mozilla/firefox/*/extensions.rdf", + "fm:.mozilla/firefox/*/extensions.ini", + "fm:.mozilla/firefox/*/extensions.cache", + "fm:.mozilla/firefox/*/XUL.mfasl", + "fm:.mozilla/firefox/*/XPC.mfasl", + "fm:.mozilla/firefox/*/xpti.dat", + "fm:.mozilla/firefox/*/compreg.dat", + "fm:.mozilla/firefox/*/pluginreg.dat" ], "tags": ["application:firefox", "type:browser", "os:linux"], "author": "Divi" diff --git a/src/vorta/assets/exclusion_presets/dev.json b/src/vorta/assets/exclusion_presets/dev.json index dc565c4ec..4269604cb 100644 --- a/src/vorta/assets/exclusion_presets/dev.json +++ b/src/vorta/assets/exclusion_presets/dev.json @@ -3,7 +3,7 @@ "name": "Node Modules and package manager cache", "patterns": [ - "fm:/node_modules", + "fm:node_modules", "fm:.npm" ], "tags": ["type:dev", "lang:javascript", "os:linux", "os:macos"], @@ -17,7 +17,7 @@ "fm:.pyc", "fm:.pyo", "fm:.env", - "fm:/.virtualenvs" + "fm:.virtualenvs" ], "tags": ["type:dev", "lang:python", "os:linux", "os:macos"], "author": "Divi" diff --git a/src/vorta/store/connection.py b/src/vorta/store/connection.py index 48105b893..e7756f157 100644 --- a/src/vorta/store/connection.py +++ b/src/vorta/store/connection.py @@ -13,7 +13,6 @@ BackupProfileModel, EventLogModel, ExclusionModel, - RawExclusionModel, RepoModel, RepoPassword, SchemaVersion, @@ -55,7 +54,6 @@ def init_db(con=None): EventLogModel, SchemaVersion, ExclusionModel, - RawExclusionModel, ] ) diff --git a/src/vorta/store/models.py b/src/vorta/store/models.py index 33faa6bbd..d8c884111 100644 --- a/src/vorta/store/models.py +++ b/src/vorta/store/models.py @@ -6,6 +6,7 @@ import json from datetime import datetime +from enum import Enum import peewee as pw from playhouse import signals @@ -73,8 +74,8 @@ class BackupProfileModel(BaseModel): repo = pw.ForeignKeyField(RepoModel, default=None, null=True) ssh_key = pw.CharField(default=None, null=True) compression = pw.CharField(default='lz4') + raw_exclusions = pw.TextField(default='') exclude_patterns = pw.TextField(null=True) - exclude_if_present = pw.TextField(null=True) schedule_mode = pw.CharField(default='off') schedule_interval_count = pw.IntegerField(default=3) schedule_interval_unit = pw.CharField(default='hours') @@ -114,25 +115,14 @@ class ExclusionModel(BaseModel): presets, the name will be the same as the preset name. Duplicate patterns are already handled by Borg. """ + class SourceFieldOptions(Enum): + CUSTOM = 'custom' + PRESET = 'preset' + profile = pw.ForeignKeyField(BackupProfileModel, backref='exclusions') name = pw.CharField(unique=True) enabled = pw.BooleanField(default=True) - source = pw.CharField(default='custom') # custom or preset - date_added = pw.DateTimeField(default=datetime.now) - - class Meta: - database = DB - - -class RawExclusionModel(BaseModel): - """ - The raw exclusion patterns that a user adds to a profile will be added here as plaintext. - Each profile will have a single associated RawExclusionModel. - """ - - profile = pw.ForeignKeyField(BackupProfileModel, backref='raw_exclusions') - patterns = pw.TextField(default='') - date_added = pw.DateTimeField(default=datetime.now) + source = pw.CharField(default=SourceFieldOptions.CUSTOM.value) class Meta: database = DB diff --git a/src/vorta/views/exclude_dialog.py b/src/vorta/views/exclude_dialog.py index e87a02879..996ec81b1 100644 --- a/src/vorta/views/exclude_dialog.py +++ b/src/vorta/views/exclude_dialog.py @@ -10,7 +10,7 @@ QMessageBox, ) -from vorta.store.models import ExclusionModel, RawExclusionModel +from vorta.store.models import ExclusionModel from vorta.utils import get_asset from vorta.views.utils import get_colored_icon @@ -18,12 +18,16 @@ ExcludeDialogUi, ExcludeDialogBase = uic.loadUiType(uifile) -class QCustomItemModel(QStandardItemModel): - # When a user-added item in edit mode has no text, remove it from the list. +class MandatoryInputItemModel(QStandardItemModel): + ''' + A model that prevents the user from adding an empty item to the list. + ''' + def __init__(self, parent=None): super().__init__(parent) def setData(self, index: QModelIndex, value, role: int = ...) -> bool: + # When a user-added item in edit mode has no text, remove it from the list. if role == Qt.ItemDataRole.EditRole and value == '': self.removeRow(index.row()) return True @@ -46,9 +50,9 @@ def __init__(self, profile, parent=None): self.profile = profile self.allPresets = {} - self.customExcludesModel = QCustomItemModel() - self.customExclusionsList.setModel(self.customExcludesModel) - self.customExcludesModel.itemChanged.connect(self.custom_item_changed) + self.customExclusionsModel = MandatoryInputItemModel() + self.customExclusionsList.setModel(self.customExclusionsModel) + self.customExclusionsModel.itemChanged.connect(self.custom_item_changed) self.customExclusionsList.setEditTriggers(QAbstractItemView.EditTrigger.NoEditTriggers) self.customExclusionsList.setSelectionMode(QAbstractItemView.SelectionMode.MultiSelection) self.customExclusionsList.setFocusPolicy(Qt.FocusPolicy.NoFocus) @@ -105,15 +109,15 @@ def populate_custom_exclusions_list(self): user_excluded_patterns = { e.name: e.enabled for e in self.profile.exclusions.select() - .where(ExclusionModel.source == 'custom') - .order_by(ExclusionModel.date_added.desc()) + .where(ExclusionModel.source == ExclusionModel.SourceFieldOptions.CUSTOM.value) + .order_by(ExclusionModel.name) } for (exclude, enabled) in user_excluded_patterns.items(): item = QStandardItem(exclude) item.setCheckable(True) item.setCheckState(Qt.CheckState.Checked if enabled else Qt.CheckState.Unchecked) - self.customExcludesModel.appendRow(item) + self.customExclusionsModel.appendRow(item) def populate_presets_list(self): if getattr(sys, 'frozen', False): @@ -131,46 +135,59 @@ def populate_presets_list(self): item.setCheckable(True) preset_model = ExclusionModel.get_or_none( name=preset['name'], - source='preset', + source=ExclusionModel.SourceFieldOptions.PRESET.value, profile=self.profile, ) + if preset_model: item.setCheckState(Qt.CheckState.Checked if preset_model.enabled else Qt.CheckState.Unchecked) else: item.setCheckState(Qt.CheckState.Unchecked) - # add a link icon to the end of the item + self.exclusionPresetsModel.appendRow(item) self.allPresets[preset['name']] = { 'patterns': preset['patterns'], 'tags': preset['tags'], - 'filename': preset_file, } def populate_raw_exclusions_text(self): - raw_excludes = RawExclusionModel.get_or_none(profile=self.profile) + raw_excludes = self.profile.raw_exclusions if raw_excludes: - self.rawExclusionsText.setPlainText(raw_excludes.patterns) + self.rawExclusionsText.setPlainText(raw_excludes) def populate_preview_tab(self): - excludes = "# custom added rules\n" + excludes = "" + + if ( + ExclusionModel.select() + .where( + ExclusionModel.profile == self.profile, + ExclusionModel.enabled, + ExclusionModel.source == ExclusionModel.SourceFieldOptions.CUSTOM.value, + ) + .count() + > 0 + ): + excludes = "# custom added rules\n" + for exclude in ExclusionModel.select().where( ExclusionModel.profile == self.profile, ExclusionModel.enabled, - ExclusionModel.source == 'custom', + ExclusionModel.source == ExclusionModel.SourceFieldOptions.CUSTOM.value, ): excludes += f"{exclude.name}\n" - raw_excludes = RawExclusionModel.get_or_none(profile=self.profile) + raw_excludes = self.profile.raw_exclusions if raw_excludes: excludes += "\n# raw exclusions\n" - excludes += raw_excludes.patterns + excludes += raw_excludes excludes += "\n" # go through all source=='preset' exclusions, find the name in the allPresets dict, and add the patterns for exclude in ExclusionModel.select().where( ExclusionModel.profile == self.profile, ExclusionModel.enabled, - ExclusionModel.source == 'preset', + ExclusionModel.source == ExclusionModel.SourceFieldOptions.PRESET.value, ): excludes += f"\n#{exclude.name}\n" for pattern in self.allPresets[exclude.name]['patterns']: @@ -183,10 +200,10 @@ def remove_pattern(self): for index in reversed(indexes): ExclusionModel.delete().where( ExclusionModel.name == index.data(), - ExclusionModel.source == 'custom', + ExclusionModel.source == ExclusionModel.SourceFieldOptions.CUSTOM.value, ExclusionModel.profile == self.profile, ).execute() - self.customExcludesModel.removeRow(index.row()) + self.customExclusionsModel.removeRow(index.row()) self.populate_preview_tab() @@ -206,12 +223,16 @@ def custom_item_changed(self, item): When the user checks or unchecks an item, update the database. When the user adds a new item, add it to the database. ''' - if not ExclusionModel.get_or_none(name=item.text(), source='custom', profile=self.profile): - ExclusionModel.create(name=item.text(), source='custom', profile=self.profile) + if not ExclusionModel.get_or_none( + name=item.text(), source=ExclusionModel.SourceFieldOptions.CUSTOM.value, profile=self.profile + ): + ExclusionModel.create( + name=item.text(), source=ExclusionModel.SourceFieldOptions.CUSTOM.value, profile=self.profile + ) ExclusionModel.update(enabled=item.checkState() == Qt.CheckState.Checked).where( ExclusionModel.name == item.text(), - ExclusionModel.source == 'custom', + ExclusionModel.source == ExclusionModel.SourceFieldOptions.CUSTOM.value, ExclusionModel.profile == self.profile, ).execute() @@ -225,7 +246,7 @@ def preset_item_changed(self, item): ''' preset = ExclusionModel.get_or_none( name=item.text(), - source='preset', + source=ExclusionModel.SourceFieldOptions.PRESET.value, profile=self.profile, ) if preset: @@ -234,7 +255,7 @@ def preset_item_changed(self, item): else: ExclusionModel.create( name=item.text(), - source='preset', + source=ExclusionModel.SourceFieldOptions.PRESET.value, profile=self.profile, enabled=item.checkState() == Qt.CheckState.Checked, ) @@ -246,11 +267,7 @@ def raw_exclusions_saved(self): When the user saves changes in the raw exclusions text box, add it to the database. ''' raw_excludes = self.rawExclusionsText.toPlainText() - raw_excludes_model = RawExclusionModel.get_or_none(profile=self.profile) - if raw_excludes_model: - raw_excludes_model.patterns = raw_excludes - raw_excludes_model.save() - else: - RawExclusionModel.create(profile=self.profile, patterns=raw_excludes) + self.profile.raw_exclusions = raw_excludes + self.profile.save() self.populate_preview_tab()