From 2cdacd5441be14486f6d85cec77d9aacbee65428 Mon Sep 17 00:00:00 2001 From: Thesacraft Date: Wed, 5 Jun 2024 20:11:26 +0200 Subject: [PATCH 01/18] Add option to use a whitelist instead of a blacklist --- tagstudio/src/core/library.py | 36 ++++++++++++++++++++--- tagstudio/src/qt/modals/file_extension.py | 9 ++++++ tagstudio/src/qt/ts_qt.py | 4 ++- 3 files changed, 44 insertions(+), 5 deletions(-) diff --git a/tagstudio/src/core/library.py b/tagstudio/src/core/library.py index 4a4b70f4d..894a59403 100644 --- a/tagstudio/src/core/library.py +++ b/tagstudio/src/core/library.py @@ -343,6 +343,7 @@ def __init__(self) -> None: # A list of file extensions to be ignored by TagStudio. self.default_ext_blacklist: list = [".json", ".xmp", ".aae"] self.ignored_extensions: list = self.default_ext_blacklist + self.ignore_extensions = True # Tags ================================================================= # List of every Tag object (ts-v8). @@ -502,7 +503,8 @@ def open_library(self, path: str | Path) -> int: # Load Extension Blacklist --------------------------------- if "ignored_extensions" in json_dump.keys(): self.ignored_extensions = json_dump["ignored_extensions"] - + if "ignore_extensions" in json_dump.keys(): + self.ignore_extensions = json_dump["ignore_extensions"] # Parse Tags --------------------------------------------------- if "tags" in json_dump.keys(): start_time = time.time() @@ -736,6 +738,7 @@ def to_json(self): file_to_save: JsonLibary = { "ts-version": VERSION, "ignored_extensions": [], + "ignore_extensions": True, "tags": [], "collations": [], "fields": [], @@ -746,6 +749,7 @@ def to_json(self): print("[LIBRARY] Formatting Tags to JSON...") file_to_save["ignored_extensions"] = [i for i in self.ignored_extensions if i] + file_to_save["ignore_extensions"] = self.ignore_extensions for tag in self.tags: file_to_save["tags"].append(tag.compressed_dict()) @@ -864,7 +868,21 @@ def refresh_dir(self) -> Generator: and "tagstudio_thumbs" not in f.parts and not f.is_dir() ): - if f.suffix not in self.ignored_extensions: + if ( + f.suffix not in self.ignored_extensions + and self.ignore_extensions + ): + self.dir_file_count += 1 + file = f.relative_to(self.library_dir) + try: + _ = self.filename_to_entry_id_map[file] + except KeyError: + # print(file) + self.files_not_in_library.append(file) + elif ( + f.suffix in self.ignored_extensions + and not self.ignore_extensions + ): self.dir_file_count += 1 file = f.relative_to(self.library_dir) try: @@ -1352,7 +1370,12 @@ def search_library( # non_entry_count = 0 # Iterate over all Entries ============================================================= for entry in self.entries: - allowed_ext: bool = entry.filename.suffix not in self.ignored_extensions + if self.ignore_extensions: + allowed_ext: bool = ( + entry.filename.suffix not in self.ignored_extensions + ) + else: + allowed_ext: bool = entry.filename.suffix in self.ignored_extensions # try: # entry: Entry = self.entries[self.file_to_library_index_map[self._source_filenames[i]]] # print(f'{entry}') @@ -1509,7 +1532,12 @@ def add_entry(entry: Entry): else: for entry in self.entries: added = False - allowed_ext = entry.filename.suffix not in self.ignored_extensions + if self.ignore_extensions: + allowed_ext: bool = ( + entry.filename.suffix not in self.ignored_extensions + ) + else: + allowed_ext: bool = entry.filename.suffix in self.ignored_extensions if allowed_ext: for f in entry.fields: if self.get_field_attr(f, "type") == "collation": diff --git a/tagstudio/src/qt/modals/file_extension.py b/tagstudio/src/qt/modals/file_extension.py index e61368dcc..eb78c6384 100644 --- a/tagstudio/src/qt/modals/file_extension.py +++ b/tagstudio/src/qt/modals/file_extension.py @@ -11,6 +11,7 @@ QTableWidgetItem, QStyledItemDelegate, QLineEdit, + QCheckBox, ) from src.core.library import Library @@ -53,8 +54,16 @@ def __init__(self, library: "Library"): self.root_layout.addWidget( self.add_button, alignment=Qt.AlignmentFlag.AlignCenter ) + self.ignored_checkbox = QCheckBox() + self.ignored_checkbox.setText("Uncheck to toggle it to an Whitelist") + self.ignored_checkbox.setChecked(self.lib.ignore_extensions) + self.ignored_checkbox.clicked.connect(self.toggle_whitelist) + self.root_layout.addWidget(self.ignored_checkbox) self.refresh_list() + def toggle_whitelist(self) -> None: + self.lib.ignore_extensions = self.ignored_checkbox.isChecked() + def refresh_list(self): for i, ext in enumerate(self.lib.ignored_extensions): self.table.setItem(i, 0, QTableWidgetItem(ext)) diff --git a/tagstudio/src/qt/ts_qt.py b/tagstudio/src/qt/ts_qt.py index c562c4cd4..a4cddbef4 100644 --- a/tagstudio/src/qt/ts_qt.py +++ b/tagstudio/src/qt/ts_qt.py @@ -403,7 +403,9 @@ def start(self) -> None: edit_menu.addSeparator() - manage_file_extensions_action = QAction("Ignored File Extensions", menu_bar) + manage_file_extensions_action = QAction( + "Ignore/Allow File Extensions", menu_bar + ) manage_file_extensions_action.triggered.connect( lambda: self.show_file_extension_modal() ) From 0975263d3f3059c850dae79cc35a1ee8b7252d90 Mon Sep 17 00:00:00 2001 From: Thesacraft Date: Wed, 5 Jun 2024 20:37:29 +0200 Subject: [PATCH 02/18] maybe fix mypy? --- tagstudio/src/core/json_typing.py | 1 + tagstudio/src/core/library.py | 18 ++++++------------ 2 files changed, 7 insertions(+), 12 deletions(-) diff --git a/tagstudio/src/core/json_typing.py b/tagstudio/src/core/json_typing.py index ae63a8853..e62843eb3 100644 --- a/tagstudio/src/core/json_typing.py +++ b/tagstudio/src/core/json_typing.py @@ -9,6 +9,7 @@ class JsonLibary(TypedDict("", {"ts-version": str})): macros: "list[JsonMacro]" entries: "list[JsonEntry]" ignored_extensions: list[str] + ignore_extensions: bool class JsonBase(TypedDict): diff --git a/tagstudio/src/core/library.py b/tagstudio/src/core/library.py index 894a59403..7fa007632 100644 --- a/tagstudio/src/core/library.py +++ b/tagstudio/src/core/library.py @@ -1370,12 +1370,9 @@ def search_library( # non_entry_count = 0 # Iterate over all Entries ============================================================= for entry in self.entries: - if self.ignore_extensions: - allowed_ext: bool = ( - entry.filename.suffix not in self.ignored_extensions - ) - else: - allowed_ext: bool = entry.filename.suffix in self.ignored_extensions + allowed_ext: bool = entry.filename.suffix not in self.ignored_extensions + if not self.ignore_extensions: + allowed_ext = not allowed_ext # try: # entry: Entry = self.entries[self.file_to_library_index_map[self._source_filenames[i]]] # print(f'{entry}') @@ -1532,12 +1529,9 @@ def add_entry(entry: Entry): else: for entry in self.entries: added = False - if self.ignore_extensions: - allowed_ext: bool = ( - entry.filename.suffix not in self.ignored_extensions - ) - else: - allowed_ext: bool = entry.filename.suffix in self.ignored_extensions + allowed_ext: bool = entry.filename.suffix not in self.ignored_extensions + if not self.ignore_extensions: + allowed_ext = not allowed_ext if allowed_ext: for f in entry.fields: if self.get_field_attr(f, "type") == "collation": From 0c104d617e731e34630a2626f50de069a90a4cc9 Mon Sep 17 00:00:00 2001 From: Thesacraft Date: Wed, 5 Jun 2024 20:45:14 +0200 Subject: [PATCH 03/18] Fix Mypy and rename ignored_extensions --- tagstudio/src/core/library.py | 34 ++++++++++------------- tagstudio/src/qt/modals/file_extension.py | 8 +++--- 2 files changed, 18 insertions(+), 24 deletions(-) diff --git a/tagstudio/src/core/library.py b/tagstudio/src/core/library.py index 7fa007632..1a4c69328 100644 --- a/tagstudio/src/core/library.py +++ b/tagstudio/src/core/library.py @@ -342,7 +342,7 @@ def __init__(self) -> None: self.filename_to_entry_id_map: dict[Path, int] = {} # A list of file extensions to be ignored by TagStudio. self.default_ext_blacklist: list = [".json", ".xmp", ".aae"] - self.ignored_extensions: list = self.default_ext_blacklist + self.ext_list: list = self.default_ext_blacklist self.ignore_extensions = True # Tags ================================================================= @@ -502,7 +502,7 @@ def open_library(self, path: str | Path) -> int: # Load Extension Blacklist --------------------------------- if "ignored_extensions" in json_dump.keys(): - self.ignored_extensions = json_dump["ignored_extensions"] + self.ext_list = json_dump["ignored_extensions"] if "ignore_extensions" in json_dump.keys(): self.ignore_extensions = json_dump["ignore_extensions"] # Parse Tags --------------------------------------------------- @@ -748,7 +748,7 @@ def to_json(self): print("[LIBRARY] Formatting Tags to JSON...") - file_to_save["ignored_extensions"] = [i for i in self.ignored_extensions if i] + file_to_save["ignored_extensions"] = [i for i in self.ext_list if i] file_to_save["ignore_extensions"] = self.ignore_extensions for tag in self.tags: @@ -838,7 +838,7 @@ def clear_internal_vars(self): self.missing_files.clear() self.fixed_files.clear() self.filename_to_entry_id_map: dict[Path, int] = {} - self.ignored_extensions = self.default_ext_blacklist + self.ext_list = self.default_ext_blacklist self.tags.clear() self._next_tag_id = 1000 @@ -868,10 +868,7 @@ def refresh_dir(self) -> Generator: and "tagstudio_thumbs" not in f.parts and not f.is_dir() ): - if ( - f.suffix not in self.ignored_extensions - and self.ignore_extensions - ): + if f.suffix not in self.ext_list and self.ignore_extensions: self.dir_file_count += 1 file = f.relative_to(self.library_dir) try: @@ -879,10 +876,7 @@ def refresh_dir(self) -> Generator: except KeyError: # print(file) self.files_not_in_library.append(file) - elif ( - f.suffix in self.ignored_extensions - and not self.ignore_extensions - ): + elif f.suffix in self.ext_list and not self.ignore_extensions: self.dir_file_count += 1 file = f.relative_to(self.library_dir) try: @@ -1370,14 +1364,14 @@ def search_library( # non_entry_count = 0 # Iterate over all Entries ============================================================= for entry in self.entries: - allowed_ext: bool = entry.filename.suffix not in self.ignored_extensions - if not self.ignore_extensions: - allowed_ext = not allowed_ext + allowed_ext: bool = entry.filename.suffix not in self.ext_list # try: # entry: Entry = self.entries[self.file_to_library_index_map[self._source_filenames[i]]] # print(f'{entry}') - if allowed_ext: + if (allowed_ext and self.ignore_extensions) or ( + not allowed_ext and not self.ignore_extensions + ): # If the entry has tags of any kind, append them to this main tag list. entry_tags: list[int] = [] entry_authors: list[str] = [] @@ -1529,10 +1523,10 @@ def add_entry(entry: Entry): else: for entry in self.entries: added = False - allowed_ext: bool = entry.filename.suffix not in self.ignored_extensions - if not self.ignore_extensions: - allowed_ext = not allowed_ext - if allowed_ext: + allowed_ext: bool = entry.filename.suffix not in self.ext_list + if (allowed_ext and self.ignore_extensions) or ( + not allowed_ext and not self.ignore_extensions + ): for f in entry.fields: if self.get_field_attr(f, "type") == "collation": if ( diff --git a/tagstudio/src/qt/modals/file_extension.py b/tagstudio/src/qt/modals/file_extension.py index eb78c6384..43fd28661 100644 --- a/tagstudio/src/qt/modals/file_extension.py +++ b/tagstudio/src/qt/modals/file_extension.py @@ -38,7 +38,7 @@ def __init__(self, library: "Library"): self.root_layout = QVBoxLayout(self) self.root_layout.setContentsMargins(6, 6, 6, 6) - self.table = QTableWidget(len(self.lib.ignored_extensions), 1) + self.table = QTableWidget(len(self.lib.ext_list), 1) self.table.horizontalHeader().setVisible(False) self.table.verticalHeader().setVisible(False) self.table.horizontalHeader().setStretchLastSection(True) @@ -65,15 +65,15 @@ def toggle_whitelist(self) -> None: self.lib.ignore_extensions = self.ignored_checkbox.isChecked() def refresh_list(self): - for i, ext in enumerate(self.lib.ignored_extensions): + for i, ext in enumerate(self.lib.ext_list): self.table.setItem(i, 0, QTableWidgetItem(ext)) def add_item(self): self.table.insertRow(self.table.rowCount()) def save(self): - self.lib.ignored_extensions.clear() + self.lib.ext_list.clear() for i in range(self.table.rowCount()): ext = self.table.item(i, 0) if ext and ext.text(): - self.lib.ignored_extensions.append(ext.text()) + self.lib.ext_list.append(ext.text()) From 5312324ed45e579e7ea839486eb1af42672b06bd Mon Sep 17 00:00:00 2001 From: Thesacraft Date: Wed, 5 Jun 2024 21:28:58 +0200 Subject: [PATCH 04/18] This should fix mypy --- tagstudio/src/core/library.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tagstudio/src/core/library.py b/tagstudio/src/core/library.py index 1a4c69328..a068d5c73 100644 --- a/tagstudio/src/core/library.py +++ b/tagstudio/src/core/library.py @@ -1523,7 +1523,7 @@ def add_entry(entry: Entry): else: for entry in self.entries: added = False - allowed_ext: bool = entry.filename.suffix not in self.ext_list + allowed_ext = entry.filename.suffix not in self.ext_list if (allowed_ext and self.ignore_extensions) or ( not allowed_ext and not self.ignore_extensions ): From 8288f107a7880db51c436956b00b9173fe8d29ce Mon Sep 17 00:00:00 2001 From: Thesacraft Date: Wed, 5 Jun 2024 21:35:00 +0200 Subject: [PATCH 05/18] Update checkbox text --- tagstudio/src/qt/modals/file_extension.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/tagstudio/src/qt/modals/file_extension.py b/tagstudio/src/qt/modals/file_extension.py index 43fd28661..2c985b85f 100644 --- a/tagstudio/src/qt/modals/file_extension.py +++ b/tagstudio/src/qt/modals/file_extension.py @@ -55,7 +55,7 @@ def __init__(self, library: "Library"): self.add_button, alignment=Qt.AlignmentFlag.AlignCenter ) self.ignored_checkbox = QCheckBox() - self.ignored_checkbox.setText("Uncheck to toggle it to an Whitelist") + self.update_text() self.ignored_checkbox.setChecked(self.lib.ignore_extensions) self.ignored_checkbox.clicked.connect(self.toggle_whitelist) self.root_layout.addWidget(self.ignored_checkbox) @@ -63,6 +63,13 @@ def __init__(self, library: "Library"): def toggle_whitelist(self) -> None: self.lib.ignore_extensions = self.ignored_checkbox.isChecked() + self.update_text() + + def update_text(self): + if self.lib.ignore_extensions: + self.ignored_checkbox.setText("Uncheck to make it a whitelist.") + else: + self.ignored_checkbox.setText("Check to make it a blacklist.") def refresh_list(self): for i, ext in enumerate(self.lib.ext_list): From 3786096a1606b7a268f42d4291fa849d6e8afd7a Mon Sep 17 00:00:00 2001 From: Thesacraft Date: Wed, 5 Jun 2024 21:39:30 +0200 Subject: [PATCH 06/18] Update window title --- tagstudio/src/qt/ts_qt.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tagstudio/src/qt/ts_qt.py b/tagstudio/src/qt/ts_qt.py index a4cddbef4..ef16ca486 100644 --- a/tagstudio/src/qt/ts_qt.py +++ b/tagstudio/src/qt/ts_qt.py @@ -739,7 +739,10 @@ def show_file_extension_modal(self): # self.modal = FileExtensionModal(self.lib) panel = FileExtensionModal(self.lib) self.modal = PanelModal( - panel, "Ignored File Extensions", "Ignored File Extensions", has_save=True + panel, + "Ignored/Allowed File Extensions", + "Ignored/Allowed File Extensions", + has_save=True, ) self.modal.saved.connect(lambda: (panel.save(), self.filter_items(""))) self.modal.show() From 99720b1bd784947ae058bf6c6dd9708e064ea0f6 Mon Sep 17 00:00:00 2001 From: Thesacraft Date: Thu, 6 Jun 2024 11:28:37 +0200 Subject: [PATCH 07/18] shorten if statment and update text --- tagstudio/src/core/library.py | 8 ++------ tagstudio/src/qt/modals/file_extension.py | 8 ++++++-- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/tagstudio/src/core/library.py b/tagstudio/src/core/library.py index a068d5c73..93d83fd04 100644 --- a/tagstudio/src/core/library.py +++ b/tagstudio/src/core/library.py @@ -1369,9 +1369,7 @@ def search_library( # entry: Entry = self.entries[self.file_to_library_index_map[self._source_filenames[i]]] # print(f'{entry}') - if (allowed_ext and self.ignore_extensions) or ( - not allowed_ext and not self.ignore_extensions - ): + if allowed_ext == self.ignore_extensions: # If the entry has tags of any kind, append them to this main tag list. entry_tags: list[int] = [] entry_authors: list[str] = [] @@ -1524,9 +1522,7 @@ def add_entry(entry: Entry): for entry in self.entries: added = False allowed_ext = entry.filename.suffix not in self.ext_list - if (allowed_ext and self.ignore_extensions) or ( - not allowed_ext and not self.ignore_extensions - ): + if allowed_ext == self.ignore_extensions: for f in entry.fields: if self.get_field_attr(f, "type") == "collation": if ( diff --git a/tagstudio/src/qt/modals/file_extension.py b/tagstudio/src/qt/modals/file_extension.py index 2c985b85f..1f872271c 100644 --- a/tagstudio/src/qt/modals/file_extension.py +++ b/tagstudio/src/qt/modals/file_extension.py @@ -67,9 +67,13 @@ def toggle_whitelist(self) -> None: def update_text(self): if self.lib.ignore_extensions: - self.ignored_checkbox.setText("Uncheck to make it a whitelist.") + self.ignored_checkbox.setText( + "Uncheck to only show files with these file extensions." + ) else: - self.ignored_checkbox.setText("Check to make it a blacklist.") + self.ignored_checkbox.setText( + "Check to ignore files with these file extensions." + ) def refresh_list(self): for i, ext in enumerate(self.lib.ext_list): From 3989013258f140c30c29478654859af3ba7b7bf3 Mon Sep 17 00:00:00 2001 From: Thesacraft Date: Thu, 6 Jun 2024 11:56:27 +0200 Subject: [PATCH 08/18] update variable names --- tagstudio/src/core/json_typing.py | 4 +-- tagstudio/src/core/library.py | 32 +++++++++++++---------- tagstudio/src/qt/modals/file_extension.py | 18 ++++++------- 3 files changed, 29 insertions(+), 25 deletions(-) diff --git a/tagstudio/src/core/json_typing.py b/tagstudio/src/core/json_typing.py index e62843eb3..eab511485 100644 --- a/tagstudio/src/core/json_typing.py +++ b/tagstudio/src/core/json_typing.py @@ -8,8 +8,8 @@ class JsonLibary(TypedDict("", {"ts-version": str})): fields: list # TODO macros: "list[JsonMacro]" entries: "list[JsonEntry]" - ignored_extensions: list[str] - ignore_extensions: bool + ext_list: list[str] + is_exclude_list: bool class JsonBase(TypedDict): diff --git a/tagstudio/src/core/library.py b/tagstudio/src/core/library.py index 93d83fd04..205f802b1 100644 --- a/tagstudio/src/core/library.py +++ b/tagstudio/src/core/library.py @@ -5,6 +5,7 @@ """The Library object and related methods for TagStudio.""" import datetime +import json import logging import os import time @@ -341,9 +342,9 @@ def __init__(self) -> None: # That filename can then be used to provide quick lookup to image metadata entries in the Library. self.filename_to_entry_id_map: dict[Path, int] = {} # A list of file extensions to be ignored by TagStudio. - self.default_ext_blacklist: list = [".json", ".xmp", ".aae"] - self.ext_list: list = self.default_ext_blacklist - self.ignore_extensions = True + self.default_ext_exclude_list: list = [".json", ".xmp", ".aae"] + self.ext_list: list = self.default_ext_exclude_list + self.is_exclude_list = True # Tags ================================================================= # List of every Tag object (ts-v8). @@ -500,11 +501,14 @@ def open_library(self, path: str | Path) -> int: self.verify_ts_folders() major, minor, patch = json_dump["ts-version"].split(".") - # Load Extension Blacklist --------------------------------- - if "ignored_extensions" in json_dump.keys(): + # Load Extension Exclude List --------------------------------- + # The following if statment is only used when a old library is loaded + if "ignored_extensions" in json_dump: self.ext_list = json_dump["ignored_extensions"] - if "ignore_extensions" in json_dump.keys(): - self.ignore_extensions = json_dump["ignore_extensions"] + if "ext_list" in json_dump: + self.ext_list = json_dump["ext_list"] + if "is_exclude_list" in json_dump: + self.is_exclude_list = json_dump["is_exclude_list"] # Parse Tags --------------------------------------------------- if "tags" in json_dump.keys(): start_time = time.time() @@ -748,8 +752,8 @@ def to_json(self): print("[LIBRARY] Formatting Tags to JSON...") - file_to_save["ignored_extensions"] = [i for i in self.ext_list if i] - file_to_save["ignore_extensions"] = self.ignore_extensions + file_to_save["ext_list"] = [i for i in self.ext_list if i] + file_to_save["is_exclude_list"] = self.is_exclude_list for tag in self.tags: file_to_save["tags"].append(tag.compressed_dict()) @@ -838,7 +842,7 @@ def clear_internal_vars(self): self.missing_files.clear() self.fixed_files.clear() self.filename_to_entry_id_map: dict[Path, int] = {} - self.ext_list = self.default_ext_blacklist + self.ext_list = self.default_ext_exclude_list self.tags.clear() self._next_tag_id = 1000 @@ -868,7 +872,7 @@ def refresh_dir(self) -> Generator: and "tagstudio_thumbs" not in f.parts and not f.is_dir() ): - if f.suffix not in self.ext_list and self.ignore_extensions: + if f.suffix not in self.ext_list and self.is_exclude_list: self.dir_file_count += 1 file = f.relative_to(self.library_dir) try: @@ -876,7 +880,7 @@ def refresh_dir(self) -> Generator: except KeyError: # print(file) self.files_not_in_library.append(file) - elif f.suffix in self.ext_list and not self.ignore_extensions: + elif f.suffix in self.ext_list and not self.is_exclude_list: self.dir_file_count += 1 file = f.relative_to(self.library_dir) try: @@ -1369,7 +1373,7 @@ def search_library( # entry: Entry = self.entries[self.file_to_library_index_map[self._source_filenames[i]]] # print(f'{entry}') - if allowed_ext == self.ignore_extensions: + if allowed_ext == self.is_exclude_list: # If the entry has tags of any kind, append them to this main tag list. entry_tags: list[int] = [] entry_authors: list[str] = [] @@ -1522,7 +1526,7 @@ def add_entry(entry: Entry): for entry in self.entries: added = False allowed_ext = entry.filename.suffix not in self.ext_list - if allowed_ext == self.ignore_extensions: + if allowed_ext == self.is_exclude_list: for f in entry.fields: if self.get_field_attr(f, "type") == "collation": if ( diff --git a/tagstudio/src/qt/modals/file_extension.py b/tagstudio/src/qt/modals/file_extension.py index 1f872271c..26c433507 100644 --- a/tagstudio/src/qt/modals/file_extension.py +++ b/tagstudio/src/qt/modals/file_extension.py @@ -54,24 +54,24 @@ def __init__(self, library: "Library"): self.root_layout.addWidget( self.add_button, alignment=Qt.AlignmentFlag.AlignCenter ) - self.ignored_checkbox = QCheckBox() + self.exclude_list_checkbox = QCheckBox() self.update_text() - self.ignored_checkbox.setChecked(self.lib.ignore_extensions) - self.ignored_checkbox.clicked.connect(self.toggle_whitelist) - self.root_layout.addWidget(self.ignored_checkbox) + self.exclude_list_checkbox.setChecked(self.lib.is_exclude_list) + self.exclude_list_checkbox.clicked.connect(self.toggle_exclude_list) + self.root_layout.addWidget(self.exclude_list_checkbox) self.refresh_list() - def toggle_whitelist(self) -> None: - self.lib.ignore_extensions = self.ignored_checkbox.isChecked() + def toggle_exclude_list(self) -> None: + self.lib.is_exclude_list = self.exclude_list_checkbox.isChecked() self.update_text() def update_text(self): - if self.lib.ignore_extensions: - self.ignored_checkbox.setText( + if self.lib.is_exclude_list: + self.exclude_list_checkbox.setText( "Uncheck to only show files with these file extensions." ) else: - self.ignored_checkbox.setText( + self.exclude_list_checkbox.setText( "Check to ignore files with these file extensions." ) From 5d4d7f1bc61cea39d6f2b95ad99cb2b804fbd2c2 Mon Sep 17 00:00:00 2001 From: Thesacraft Date: Thu, 6 Jun 2024 12:08:19 +0200 Subject: [PATCH 09/18] Fix Mypy --- tagstudio/src/core/library.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tagstudio/src/core/library.py b/tagstudio/src/core/library.py index 205f802b1..a389bdd3f 100644 --- a/tagstudio/src/core/library.py +++ b/tagstudio/src/core/library.py @@ -741,8 +741,8 @@ def to_json(self): file_to_save: JsonLibary = { "ts-version": VERSION, - "ignored_extensions": [], - "ignore_extensions": True, + "ext_list": [], + "is_exclude_list": True, "tags": [], "collations": [], "fields": [], From 749ea49fd61de57dc0452e22c611f1513be8e054 Mon Sep 17 00:00:00 2001 From: Thesacraft Date: Thu, 6 Jun 2024 12:27:49 +0200 Subject: [PATCH 10/18] hopefully fix mypy --- tagstudio/src/core/json_typing.py | 1 + tagstudio/src/core/library.py | 9 ++++----- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/tagstudio/src/core/json_typing.py b/tagstudio/src/core/json_typing.py index eab511485..f7f3b4dec 100644 --- a/tagstudio/src/core/json_typing.py +++ b/tagstudio/src/core/json_typing.py @@ -10,6 +10,7 @@ class JsonLibary(TypedDict("", {"ts-version": str})): entries: "list[JsonEntry]" ext_list: list[str] is_exclude_list: bool + ignored_extensions: list[str] class JsonBase(TypedDict): diff --git a/tagstudio/src/core/library.py b/tagstudio/src/core/library.py index a389bdd3f..b4d534739 100644 --- a/tagstudio/src/core/library.py +++ b/tagstudio/src/core/library.py @@ -502,11 +502,10 @@ def open_library(self, path: str | Path) -> int: major, minor, patch = json_dump["ts-version"].split(".") # Load Extension Exclude List --------------------------------- - # The following if statment is only used when a old library is loaded - if "ignored_extensions" in json_dump: - self.ext_list = json_dump["ignored_extensions"] - if "ext_list" in json_dump: - self.ext_list = json_dump["ext_list"] + self.ext_list = json_dump.get("ext_list") or json_dump.get( + "ignored_extensions" + ) + if "is_exclude_list" in json_dump: self.is_exclude_list = json_dump["is_exclude_list"] # Parse Tags --------------------------------------------------- From 1bd008df6f72a3f81ecebe98094091b350ccca17 Mon Sep 17 00:00:00 2001 From: Thesacraft Date: Thu, 6 Jun 2024 12:36:50 +0200 Subject: [PATCH 11/18] Fix mypy --- tagstudio/src/core/library.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tagstudio/src/core/library.py b/tagstudio/src/core/library.py index b4d534739..182063231 100644 --- a/tagstudio/src/core/library.py +++ b/tagstudio/src/core/library.py @@ -747,6 +747,7 @@ def to_json(self): "fields": [], "macros": [], "entries": [], + "ignored_extensions": [], } print("[LIBRARY] Formatting Tags to JSON...") From c8937cb37471b2cfcfdc62c0dc960a0037eae16c Mon Sep 17 00:00:00 2001 From: Theasacraft <91694323+Thesacraft@users.noreply.github.com> Date: Thu, 6 Jun 2024 14:10:21 +0200 Subject: [PATCH 12/18] deprecate ignored_extensions Co-authored-by: Jiri --- tagstudio/src/core/json_typing.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tagstudio/src/core/json_typing.py b/tagstudio/src/core/json_typing.py index f7f3b4dec..452bbc751 100644 --- a/tagstudio/src/core/json_typing.py +++ b/tagstudio/src/core/json_typing.py @@ -10,7 +10,7 @@ class JsonLibary(TypedDict("", {"ts-version": str})): entries: "list[JsonEntry]" ext_list: list[str] is_exclude_list: bool - ignored_extensions: list[str] + ignored_extensions: NotRequired[list[str]] # deprecated class JsonBase(TypedDict): From b33af8ad01172b1b17ae8fe15c4a495711b94c61 Mon Sep 17 00:00:00 2001 From: Thesacraft Date: Thu, 6 Jun 2024 14:14:10 +0200 Subject: [PATCH 13/18] polishing --- tagstudio/src/core/library.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/tagstudio/src/core/library.py b/tagstudio/src/core/library.py index 182063231..4501e0e9f 100644 --- a/tagstudio/src/core/library.py +++ b/tagstudio/src/core/library.py @@ -740,21 +740,17 @@ def to_json(self): file_to_save: JsonLibary = { "ts-version": VERSION, - "ext_list": [], - "is_exclude_list": True, + "ext_list": [i for i in self.ext_list if i], + "is_exclude_list": self.is_exclude_list, "tags": [], "collations": [], "fields": [], "macros": [], "entries": [], - "ignored_extensions": [], } print("[LIBRARY] Formatting Tags to JSON...") - file_to_save["ext_list"] = [i for i in self.ext_list if i] - file_to_save["is_exclude_list"] = self.is_exclude_list - for tag in self.tags: file_to_save["tags"].append(tag.compressed_dict()) From 4f4900d19c12774b401962e4ada7d471c541fc5a Mon Sep 17 00:00:00 2001 From: Thesacraft Date: Thu, 6 Jun 2024 14:15:53 +0200 Subject: [PATCH 14/18] polishing --- tagstudio/src/core/json_typing.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tagstudio/src/core/json_typing.py b/tagstudio/src/core/json_typing.py index 452bbc751..af93a3b5e 100644 --- a/tagstudio/src/core/json_typing.py +++ b/tagstudio/src/core/json_typing.py @@ -1,4 +1,4 @@ -from typing import TypedDict +from typing import TypedDict, NotRequired class JsonLibary(TypedDict("", {"ts-version": str})): @@ -10,7 +10,7 @@ class JsonLibary(TypedDict("", {"ts-version": str})): entries: "list[JsonEntry]" ext_list: list[str] is_exclude_list: bool - ignored_extensions: NotRequired[list[str]] # deprecated + ignored_extensions: NotRequired[list[str]] # deprecated class JsonBase(TypedDict): From b29fac24daa90756882de95491351d1109ce56c0 Mon Sep 17 00:00:00 2001 From: Thesacraft Date: Thu, 6 Jun 2024 14:25:24 +0200 Subject: [PATCH 15/18] Fix mypy --- tagstudio/src/core/json_typing.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tagstudio/src/core/json_typing.py b/tagstudio/src/core/json_typing.py index af93a3b5e..29ffdc35b 100644 --- a/tagstudio/src/core/json_typing.py +++ b/tagstudio/src/core/json_typing.py @@ -1,4 +1,5 @@ -from typing import TypedDict, NotRequired +from typing import TypedDict +from typing_extensions import NotRequired class JsonLibary(TypedDict("", {"ts-version": str})): From ecc3a08e0db0b44b79245f721b5f77c16c83e95d Mon Sep 17 00:00:00 2001 From: Theasacraft <91694323+Thesacraft@users.noreply.github.com> Date: Thu, 6 Jun 2024 17:55:35 +0200 Subject: [PATCH 16/18] finishing touches Co-authored-by: Jiri --- tagstudio/src/core/library.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/tagstudio/src/core/library.py b/tagstudio/src/core/library.py index 4501e0e9f..aa6fc2464 100644 --- a/tagstudio/src/core/library.py +++ b/tagstudio/src/core/library.py @@ -871,10 +871,7 @@ def refresh_dir(self) -> Generator: if f.suffix not in self.ext_list and self.is_exclude_list: self.dir_file_count += 1 file = f.relative_to(self.library_dir) - try: - _ = self.filename_to_entry_id_map[file] - except KeyError: - # print(file) + if file not in self.filename_to_entry_id_map: self.files_not_in_library.append(file) elif f.suffix in self.ext_list and not self.is_exclude_list: self.dir_file_count += 1 From 0da6eed24da3234c8c227e8e715ae49a34ada6b6 Mon Sep 17 00:00:00 2001 From: Thesacraft Date: Fri, 7 Jun 2024 10:43:45 +0200 Subject: [PATCH 17/18] Fix boolean loading --- tagstudio/src/core/library.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/tagstudio/src/core/library.py b/tagstudio/src/core/library.py index aa6fc2464..5ccd00bf8 100644 --- a/tagstudio/src/core/library.py +++ b/tagstudio/src/core/library.py @@ -502,9 +502,10 @@ def open_library(self, path: str | Path) -> int: major, minor, patch = json_dump["ts-version"].split(".") # Load Extension Exclude List --------------------------------- - self.ext_list = json_dump.get("ext_list") or json_dump.get( - "ignored_extensions" - ) + if "ext_list" in json_dump or "ignored_extensions" in json_dump: + self.ext_list = json_dump.get("ext_list") or json_dump.get( + "ignored_extensions" + ) if "is_exclude_list" in json_dump: self.is_exclude_list = json_dump["is_exclude_list"] From 858b466112ea66e1bdc98c9a733341c6bab4dc8e Mon Sep 17 00:00:00 2001 From: Travis Abendshien Date: Fri, 7 Jun 2024 12:46:45 -0700 Subject: [PATCH 18/18] UI/UX + ext list loading tweaks - Change extension list mode setting from Checkbox to ComboBox to help better convey its purpose - Change and simplify wording - Add type hints to extension variables and change loading to use `get()` with default values - Sanitize older extension lists that don't use extensions with a leading "." - Misc. code organization and docstrings --- tagstudio/src/core/library.py | 46 ++++++++++++----- tagstudio/src/qt/modals/file_extension.py | 63 ++++++++++++++++------- tagstudio/src/qt/ts_qt.py | 9 ++-- 3 files changed, 79 insertions(+), 39 deletions(-) diff --git a/tagstudio/src/core/library.py b/tagstudio/src/core/library.py index 5ccd00bf8..59f494010 100644 --- a/tagstudio/src/core/library.py +++ b/tagstudio/src/core/library.py @@ -342,9 +342,9 @@ def __init__(self) -> None: # That filename can then be used to provide quick lookup to image metadata entries in the Library. self.filename_to_entry_id_map: dict[Path, int] = {} # A list of file extensions to be ignored by TagStudio. - self.default_ext_exclude_list: list = [".json", ".xmp", ".aae"] - self.ext_list: list = self.default_ext_exclude_list - self.is_exclude_list = True + self.default_ext_exclude_list: list[str] = [".json", ".xmp", ".aae"] + self.ext_list: list[str] = [] + self.is_exclude_list: bool = True # Tags ================================================================= # List of every Tag object (ts-v8). @@ -501,15 +501,35 @@ def open_library(self, path: str | Path) -> int: self.verify_ts_folders() major, minor, patch = json_dump["ts-version"].split(".") - # Load Extension Exclude List --------------------------------- - if "ext_list" in json_dump or "ignored_extensions" in json_dump: - self.ext_list = json_dump.get("ext_list") or json_dump.get( - "ignored_extensions" + # Load Extension List -------------------------------------- + start_time = time.time() + if "ignored_extensions" in json_dump: + self.ext_list = json_dump.get( + "ignored_extensions", self.default_ext_exclude_list ) + else: + self.ext_list = json_dump.get( + "ext_list", self.default_ext_exclude_list + ) + + # Sanitizes older lists (v9.2.1) that don't use leading periods. + # Without this, existing lists (including default lists) + # have to otherwise be updated by hand in order to restore + # previous functionality. + sanitized_list: list[str] = [] + for ext in self.ext_list: + if not ext.startswith("."): + ext = "." + ext + sanitized_list.append(ext) + self.ext_list = sanitized_list + + self.is_exclude_list = json_dump.get("is_exclude_list", True) + end_time = time.time() + logging.info( + f"[LIBRARY] Extension list loaded in {(end_time - start_time):.3f} seconds" + ) - if "is_exclude_list" in json_dump: - self.is_exclude_list = json_dump["is_exclude_list"] - # Parse Tags --------------------------------------------------- + # Parse Tags ----------------------------------------------- if "tags" in json_dump.keys(): start_time = time.time() @@ -563,7 +583,7 @@ def open_library(self, path: str | Path) -> int: f"[LIBRARY] Tags loaded in {(end_time - start_time):.3f} seconds" ) - # Parse Entries ------------------------------------------------ + # Parse Entries -------------------------------------------- if entries := json_dump.get("entries"): start_time = time.time() for entry in entries: @@ -587,7 +607,7 @@ def open_library(self, path: str | Path) -> int: del f[list(f.keys())[0]] fields = entry["fields"] - # Look through fields for legacy Collation data -------- + # Look through fields for legacy Collation data ---- if int(major) >= 9 and int(minor) < 1: for f in fields: if self.get_field_attr(f, "type") == "collation": @@ -664,7 +684,7 @@ def open_library(self, path: str | Path) -> int: f"[LIBRARY] Entries loaded in {(end_time - start_time):.3f} seconds" ) - # Parse Collations --------------------------------------------------- + # Parse Collations ----------------------------------------- if "collations" in json_dump.keys(): start_time = time.time() for collation in json_dump["collations"]: diff --git a/tagstudio/src/qt/modals/file_extension.py b/tagstudio/src/qt/modals/file_extension.py index 26c433507..00fc2607f 100644 --- a/tagstudio/src/qt/modals/file_extension.py +++ b/tagstudio/src/qt/modals/file_extension.py @@ -6,12 +6,15 @@ from PySide6.QtCore import Signal, Qt from PySide6.QtWidgets import ( QVBoxLayout, + QHBoxLayout, + QWidget, QPushButton, QTableWidget, QTableWidgetItem, QStyledItemDelegate, QLineEdit, - QCheckBox, + QComboBox, + QLabel, ) from src.core.library import Library @@ -31,49 +34,69 @@ class FileExtensionModal(PanelWidget): def __init__(self, library: "Library"): super().__init__() + # Initialize Modal ===================================================== self.lib = library self.setWindowTitle("File Extensions") self.setWindowModality(Qt.WindowModality.ApplicationModal) - self.setMinimumSize(200, 400) + self.setMinimumSize(240, 400) self.root_layout = QVBoxLayout(self) self.root_layout.setContentsMargins(6, 6, 6, 6) + # Create Table Widget -------------------------------------------------- self.table = QTableWidget(len(self.lib.ext_list), 1) self.table.horizontalHeader().setVisible(False) self.table.verticalHeader().setVisible(False) self.table.horizontalHeader().setStretchLastSection(True) self.table.setItemDelegate(FileExtensionItemDelegate()) + # Create "Add Button" Widget ------------------------------------------- self.add_button = QPushButton() self.add_button.setText("&Add Extension") self.add_button.clicked.connect(self.add_item) self.add_button.setDefault(True) self.add_button.setMinimumWidth(100) + # Create Mode Widgets -------------------------------------------------- + self.mode_widget = QWidget() + self.mode_layout = QHBoxLayout(self.mode_widget) + self.mode_layout.setContentsMargins(0, 0, 0, 0) + self.mode_layout.setSpacing(12) + self.mode_label = QLabel() + self.mode_label.setText("List Mode:") + self.mode_combobox = QComboBox() + self.mode_combobox.setEditable(False) + self.mode_combobox.addItem("Exclude") + self.mode_combobox.addItem("Include") + self.mode_combobox.setCurrentIndex(0 if self.lib.is_exclude_list else 1) + self.mode_combobox.currentIndexChanged.connect( + lambda i: self.update_list_mode(i) + ) + self.mode_layout.addWidget(self.mode_label) + self.mode_layout.addWidget(self.mode_combobox) + self.mode_layout.setStretch(1, 1) + + # Add Widgets To Layout ------------------------------------------------ + self.root_layout.addWidget(self.mode_widget) self.root_layout.addWidget(self.table) self.root_layout.addWidget( self.add_button, alignment=Qt.AlignmentFlag.AlignCenter ) - self.exclude_list_checkbox = QCheckBox() - self.update_text() - self.exclude_list_checkbox.setChecked(self.lib.is_exclude_list) - self.exclude_list_checkbox.clicked.connect(self.toggle_exclude_list) - self.root_layout.addWidget(self.exclude_list_checkbox) + + # Finalize Modal ------------------------------------------------------- self.refresh_list() - def toggle_exclude_list(self) -> None: - self.lib.is_exclude_list = self.exclude_list_checkbox.isChecked() - self.update_text() - - def update_text(self): - if self.lib.is_exclude_list: - self.exclude_list_checkbox.setText( - "Uncheck to only show files with these file extensions." - ) - else: - self.exclude_list_checkbox.setText( - "Check to ignore files with these file extensions." - ) + def update_list_mode(self, mode: int): + """ + Update the mode of the extension list: "Exclude" or "Include". + + Args: + mode (int): The list mode, given by the index of the mode inside + the mode combobox. 0 for "Exclude", 1 for "Include". + """ + if mode == 0: + self.lib.is_exclude_list = True + elif mode == 1: + self.lib.is_exclude_list = False def refresh_list(self): for i, ext in enumerate(self.lib.ext_list): diff --git a/tagstudio/src/qt/ts_qt.py b/tagstudio/src/qt/ts_qt.py index ef16ca486..89eabfc54 100644 --- a/tagstudio/src/qt/ts_qt.py +++ b/tagstudio/src/qt/ts_qt.py @@ -403,9 +403,7 @@ def start(self) -> None: edit_menu.addSeparator() - manage_file_extensions_action = QAction( - "Ignore/Allow File Extensions", menu_bar - ) + manage_file_extensions_action = QAction("Manage File Extensions", menu_bar) manage_file_extensions_action.triggered.connect( lambda: self.show_file_extension_modal() ) @@ -736,12 +734,11 @@ def show_tag_database(self): self.modal.show() def show_file_extension_modal(self): - # self.modal = FileExtensionModal(self.lib) panel = FileExtensionModal(self.lib) self.modal = PanelModal( panel, - "Ignored/Allowed File Extensions", - "Ignored/Allowed File Extensions", + "File Extensions", + "File Extensions", has_save=True, ) self.modal.saved.connect(lambda: (panel.save(), self.filter_items("")))