Skip to content

feat: add filetype and mediatype searches #575

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 11 commits into from
Nov 14, 2024
8 changes: 8 additions & 0 deletions tagstudio/src/core/library/alchemy/enums.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,9 @@ class FilterState:
path: Path | str | None = None
# file name
name: str | None = None
# file type
filetype: str | None = None
mediatype: str | None = None

# a generic query to be parsed
query: str | None = None
Expand All @@ -87,6 +90,7 @@ def __post_init__(self):
# parse the value
if ":" in query:
kind, _, value = query.partition(":")
value = value.replace('"', "")
else:
# default to tag search
kind, value = "tag", query
Expand All @@ -101,6 +105,10 @@ def __post_init__(self):
self.name = value
elif kind == "id":
self.id = int(self.id) if str(self.id).isnumeric() else self.id
elif kind == "filetype":
self.filetype = value
elif kind == "mediatype":
self.mediatype = value

else:
self.tag = self.tag and self.tag.strip()
Expand Down
13 changes: 13 additions & 0 deletions tagstudio/src/core/library/alchemy/library.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
TS_FOLDER_NAME,
)
from ...enums import LibraryPrefs
from ...media_types import MediaCategories
from .db import make_tables
from .enums import FieldTypeEnum, FilterState, TagColor
from .fields import (
Expand Down Expand Up @@ -438,6 +439,18 @@ def search_library(
)
elif search.path:
statement = statement.where(Entry.path.ilike(f"%{search.path}%"))
elif search.filetype:
statement = statement.where(Entry.suffix.ilike(f"{search.filetype}"))
elif search.mediatype:
extensions: set[str] = set[str]()
for media_cat in MediaCategories.ALL_CATEGORIES:
if search.mediatype == media_cat.name:
extensions = extensions | media_cat.extensions
break
# just need to map it to search db - suffixes do not have '.'
statement = statement.where(
Entry.suffix.in_(map(lambda x: x.replace(".", ""), extensions))
)

extensions = self.prefs(LibraryPrefs.EXTENSION_LIST)
is_exclude_list = self.prefs(LibraryPrefs.IS_EXCLUDE_LIST)
Expand Down
31 changes: 31 additions & 0 deletions tagstudio/src/core/media_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ class MediaCategory:

media_type: MediaType
extensions: set[str]
name: str
is_iana: bool = False


Expand Down Expand Up @@ -338,151 +339,181 @@ class MediaCategories:
media_type=MediaType.ADOBE_PHOTOSHOP,
extensions=_ADOBE_PHOTOSHOP_SET,
is_iana=False,
name="photoshop",
)
AFFINITY_PHOTO_TYPES: MediaCategory = MediaCategory(
media_type=MediaType.AFFINITY_PHOTO,
extensions=_AFFINITY_PHOTO_SET,
is_iana=False,
name="affinity photo",
)
ARCHIVE_TYPES: MediaCategory = MediaCategory(
media_type=MediaType.ARCHIVE,
extensions=_ARCHIVE_SET,
is_iana=False,
name="archive",
)
AUDIO_MIDI_TYPES: MediaCategory = MediaCategory(
media_type=MediaType.AUDIO_MIDI,
extensions=_AUDIO_MIDI_SET,
is_iana=False,
name="audio midi",
)
AUDIO_TYPES: MediaCategory = MediaCategory(
media_type=MediaType.AUDIO,
extensions=_AUDIO_SET | _AUDIO_MIDI_SET,
is_iana=True,
name="audio",
)
BLENDER_TYPES: MediaCategory = MediaCategory(
media_type=MediaType.BLENDER,
extensions=_BLENDER_SET,
is_iana=False,
name="blender",
)
DATABASE_TYPES: MediaCategory = MediaCategory(
media_type=MediaType.DATABASE,
extensions=_DATABASE_SET,
is_iana=False,
name="database",
)
DISK_IMAGE_TYPES: MediaCategory = MediaCategory(
media_type=MediaType.DISK_IMAGE,
extensions=_DISK_IMAGE_SET,
is_iana=False,
name="disk image",
)
DOCUMENT_TYPES: MediaCategory = MediaCategory(
media_type=MediaType.DOCUMENT,
extensions=_DOCUMENT_SET,
is_iana=False,
name="document",
)
EBOOK_TYPES: MediaCategory = MediaCategory(
media_type=MediaType.EBOOK,
extensions=_EBOOK_SET,
is_iana=False,
name="ebook",
)
FONT_TYPES: MediaCategory = MediaCategory(
media_type=MediaType.FONT,
extensions=_FONT_SET,
is_iana=True,
name="font",
)
IMAGE_ANIMATED_TYPES: MediaCategory = MediaCategory(
media_type=MediaType.IMAGE_ANIMATED,
extensions=_IMAGE_ANIMATED_SET,
is_iana=False,
name="animated image",
)
IMAGE_RAW_TYPES: MediaCategory = MediaCategory(
media_type=MediaType.IMAGE_RAW,
extensions=_IMAGE_RAW_SET,
is_iana=False,
name="raw image",
)
IMAGE_VECTOR_TYPES: MediaCategory = MediaCategory(
media_type=MediaType.IMAGE_VECTOR,
extensions=_IMAGE_VECTOR_SET,
is_iana=False,
name="vector image",
)
IMAGE_RASTER_TYPES: MediaCategory = MediaCategory(
media_type=MediaType.IMAGE,
extensions=_IMAGE_RASTER_SET,
is_iana=False,
name="raster image",
)
IMAGE_TYPES: MediaCategory = MediaCategory(
media_type=MediaType.IMAGE,
extensions=_IMAGE_RASTER_SET | _IMAGE_RAW_SET | _IMAGE_VECTOR_SET,
is_iana=True,
name="image",
)
INSTALLER_TYPES: MediaCategory = MediaCategory(
media_type=MediaType.INSTALLER,
extensions=_INSTALLER_SET,
is_iana=False,
name="installer",
)
MATERIAL_TYPES: MediaCategory = MediaCategory(
media_type=MediaType.MATERIAL,
extensions=_MATERIAL_SET,
is_iana=False,
name="material",
)
MODEL_TYPES: MediaCategory = MediaCategory(
media_type=MediaType.MODEL,
extensions=_MODEL_SET,
is_iana=True,
name="model",
)
OPEN_DOCUMENT_TYPES: MediaCategory = MediaCategory(
media_type=MediaType.OPEN_DOCUMENT,
extensions=_OPEN_DOCUMENT_SET,
is_iana=False,
name="open document",
)
PACKAGE_TYPES: MediaCategory = MediaCategory(
media_type=MediaType.PACKAGE,
extensions=_PACKAGE_SET,
is_iana=False,
name="package",
)
PDF_TYPES: MediaCategory = MediaCategory(
media_type=MediaType.PDF,
extensions=_PDF_SET,
is_iana=False,
name="pdf",
)
PLAINTEXT_TYPES: MediaCategory = MediaCategory(
media_type=MediaType.PLAINTEXT,
extensions=_PLAINTEXT_SET,
is_iana=False,
name="plaintext",
)
PRESENTATION_TYPES: MediaCategory = MediaCategory(
media_type=MediaType.PRESENTATION,
extensions=_PRESENTATION_SET,
is_iana=False,
name="presentation",
)
PROGRAM_TYPES: MediaCategory = MediaCategory(
media_type=MediaType.PROGRAM,
extensions=_PROGRAM_SET,
is_iana=False,
name="program",
)
SHORTCUT_TYPES: MediaCategory = MediaCategory(
media_type=MediaType.SHORTCUT,
extensions=_SHORTCUT_SET,
is_iana=False,
name="shortcut",
)
SOURCE_ENGINE_TYPES: MediaCategory = MediaCategory(
media_type=MediaType.SOURCE_ENGINE,
extensions=_SOURCE_ENGINE_SET,
is_iana=False,
name="source engine",
)
SPREADSHEET_TYPES: MediaCategory = MediaCategory(
media_type=MediaType.SPREADSHEET,
extensions=_SPREADSHEET_SET,
is_iana=False,
name="spreadsheet",
)
TEXT_TYPES: MediaCategory = MediaCategory(
media_type=MediaType.TEXT,
extensions=_DOCUMENT_SET | _PLAINTEXT_SET,
is_iana=True,
name="text",
)
VIDEO_TYPES: MediaCategory = MediaCategory(
media_type=MediaType.VIDEO,
extensions=_VIDEO_SET,
is_iana=True,
name="video",
)

ALL_CATEGORIES: list[MediaCategory] = [
Expand Down
31 changes: 31 additions & 0 deletions tagstudio/tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,37 @@ def cwd():
return CWD


@pytest.fixture
def file_mediatypes_library():
lib = Library()

status = lib.open_library(pathlib.Path(""), ":memory:")
assert status.success

entry1 = Entry(
folder=lib.folder,
path=pathlib.Path("foo.png"),
fields=lib.default_fields,
)

entry2 = Entry(
folder=lib.folder,
path=pathlib.Path("bar.png"),
fields=lib.default_fields,
)

entry3 = Entry(
folder=lib.folder,
path=pathlib.Path("baz.apng"),
fields=lib.default_fields,
)

assert lib.add_entries([entry1, entry2, entry3])
assert len(lib.tags) == 2

return lib


@pytest.fixture
def library(request):
# when no param is passed, use the default
Expand Down
18 changes: 18 additions & 0 deletions tagstudio/tests/test_library.py
Original file line number Diff line number Diff line change
Expand Up @@ -401,3 +401,21 @@ class TestPrefs(DefaultEnum):
# accessing .value should raise exception
with pytest.raises(AttributeError):
assert TestPrefs.BAR.value


@pytest.mark.parametrize(["filetype", "num_of_filetype"], [("md", 1), ("txt", 1), ("png", 0)])
def test_filetype_search(library, filetype, num_of_filetype):
results = library.search_library(FilterState(filetype=filetype))
assert len(results.items) == num_of_filetype


@pytest.mark.parametrize(["filetype", "num_of_filetype"], [("png", 2), ("apng", 1), ("ng", 0)])
def test_filetype_return_one_filetype(file_mediatypes_library, filetype, num_of_filetype):
results = file_mediatypes_library.search_library(FilterState(filetype=filetype))
assert len(results.items) == num_of_filetype


@pytest.mark.parametrize(["mediatype", "num_of_mediatype"], [("plaintext", 2), ("image", 0)])
def test_mediatype_search(library, mediatype, num_of_mediatype):
results = library.search_library(FilterState(mediatype=mediatype))
assert len(results.items) == num_of_mediatype