Skip to content

Commit 97e0e80

Browse files
authored
feat: add filetype and mediatype searches (#575)
* feat: add filetype and mediatype searches * style: fix some style issues * fix: parametrize mediatype and filetype tests * style: fix remaining unordered import * style: fix pytest parametrize calls * feat: add human-readable names to mediacategories * feat: use human-readable names in mediacategory: search * feat: add human-readable name to open document * fix: fix returning multiple filetypes issue and add regression test
1 parent fb7ad92 commit 97e0e80

File tree

5 files changed

+101
-0
lines changed

5 files changed

+101
-0
lines changed

tagstudio/src/core/library/alchemy/enums.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,9 @@ class FilterState:
7777
path: Path | str | None = None
7878
# file name
7979
name: str | None = None
80+
# file type
81+
filetype: str | None = None
82+
mediatype: str | None = None
8083

8184
# a generic query to be parsed
8285
query: str | None = None
@@ -87,6 +90,7 @@ def __post_init__(self):
8790
# parse the value
8891
if ":" in query:
8992
kind, _, value = query.partition(":")
93+
value = value.replace('"', "")
9094
else:
9195
# default to tag search
9296
kind, value = "tag", query
@@ -101,6 +105,10 @@ def __post_init__(self):
101105
self.name = value
102106
elif kind == "id":
103107
self.id = int(self.id) if str(self.id).isnumeric() else self.id
108+
elif kind == "filetype":
109+
self.filetype = value
110+
elif kind == "mediatype":
111+
self.mediatype = value
104112

105113
else:
106114
self.tag = self.tag and self.tag.strip()

tagstudio/src/core/library/alchemy/library.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
TS_FOLDER_NAME,
3737
)
3838
from ...enums import LibraryPrefs
39+
from ...media_types import MediaCategories
3940
from .db import make_tables
4041
from .enums import FieldTypeEnum, FilterState, TagColor
4142
from .fields import (
@@ -438,6 +439,18 @@ def search_library(
438439
)
439440
elif search.path:
440441
statement = statement.where(Entry.path.ilike(f"%{search.path}%"))
442+
elif search.filetype:
443+
statement = statement.where(Entry.suffix.ilike(f"{search.filetype}"))
444+
elif search.mediatype:
445+
extensions: set[str] = set[str]()
446+
for media_cat in MediaCategories.ALL_CATEGORIES:
447+
if search.mediatype == media_cat.name:
448+
extensions = extensions | media_cat.extensions
449+
break
450+
# just need to map it to search db - suffixes do not have '.'
451+
statement = statement.where(
452+
Entry.suffix.in_(map(lambda x: x.replace(".", ""), extensions))
453+
)
441454

442455
extensions = self.prefs(LibraryPrefs.EXTENSION_LIST)
443456
is_exclude_list = self.prefs(LibraryPrefs.IS_EXCLUDE_LIST)

tagstudio/src/core/media_types.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ class MediaCategory:
6262

6363
media_type: MediaType
6464
extensions: set[str]
65+
name: str
6566
is_iana: bool = False
6667

6768

@@ -338,151 +339,181 @@ class MediaCategories:
338339
media_type=MediaType.ADOBE_PHOTOSHOP,
339340
extensions=_ADOBE_PHOTOSHOP_SET,
340341
is_iana=False,
342+
name="photoshop",
341343
)
342344
AFFINITY_PHOTO_TYPES: MediaCategory = MediaCategory(
343345
media_type=MediaType.AFFINITY_PHOTO,
344346
extensions=_AFFINITY_PHOTO_SET,
345347
is_iana=False,
348+
name="affinity photo",
346349
)
347350
ARCHIVE_TYPES: MediaCategory = MediaCategory(
348351
media_type=MediaType.ARCHIVE,
349352
extensions=_ARCHIVE_SET,
350353
is_iana=False,
354+
name="archive",
351355
)
352356
AUDIO_MIDI_TYPES: MediaCategory = MediaCategory(
353357
media_type=MediaType.AUDIO_MIDI,
354358
extensions=_AUDIO_MIDI_SET,
355359
is_iana=False,
360+
name="audio midi",
356361
)
357362
AUDIO_TYPES: MediaCategory = MediaCategory(
358363
media_type=MediaType.AUDIO,
359364
extensions=_AUDIO_SET | _AUDIO_MIDI_SET,
360365
is_iana=True,
366+
name="audio",
361367
)
362368
BLENDER_TYPES: MediaCategory = MediaCategory(
363369
media_type=MediaType.BLENDER,
364370
extensions=_BLENDER_SET,
365371
is_iana=False,
372+
name="blender",
366373
)
367374
DATABASE_TYPES: MediaCategory = MediaCategory(
368375
media_type=MediaType.DATABASE,
369376
extensions=_DATABASE_SET,
370377
is_iana=False,
378+
name="database",
371379
)
372380
DISK_IMAGE_TYPES: MediaCategory = MediaCategory(
373381
media_type=MediaType.DISK_IMAGE,
374382
extensions=_DISK_IMAGE_SET,
375383
is_iana=False,
384+
name="disk image",
376385
)
377386
DOCUMENT_TYPES: MediaCategory = MediaCategory(
378387
media_type=MediaType.DOCUMENT,
379388
extensions=_DOCUMENT_SET,
380389
is_iana=False,
390+
name="document",
381391
)
382392
EBOOK_TYPES: MediaCategory = MediaCategory(
383393
media_type=MediaType.EBOOK,
384394
extensions=_EBOOK_SET,
385395
is_iana=False,
396+
name="ebook",
386397
)
387398
FONT_TYPES: MediaCategory = MediaCategory(
388399
media_type=MediaType.FONT,
389400
extensions=_FONT_SET,
390401
is_iana=True,
402+
name="font",
391403
)
392404
IMAGE_ANIMATED_TYPES: MediaCategory = MediaCategory(
393405
media_type=MediaType.IMAGE_ANIMATED,
394406
extensions=_IMAGE_ANIMATED_SET,
395407
is_iana=False,
408+
name="animated image",
396409
)
397410
IMAGE_RAW_TYPES: MediaCategory = MediaCategory(
398411
media_type=MediaType.IMAGE_RAW,
399412
extensions=_IMAGE_RAW_SET,
400413
is_iana=False,
414+
name="raw image",
401415
)
402416
IMAGE_VECTOR_TYPES: MediaCategory = MediaCategory(
403417
media_type=MediaType.IMAGE_VECTOR,
404418
extensions=_IMAGE_VECTOR_SET,
405419
is_iana=False,
420+
name="vector image",
406421
)
407422
IMAGE_RASTER_TYPES: MediaCategory = MediaCategory(
408423
media_type=MediaType.IMAGE,
409424
extensions=_IMAGE_RASTER_SET,
410425
is_iana=False,
426+
name="raster image",
411427
)
412428
IMAGE_TYPES: MediaCategory = MediaCategory(
413429
media_type=MediaType.IMAGE,
414430
extensions=_IMAGE_RASTER_SET | _IMAGE_RAW_SET | _IMAGE_VECTOR_SET,
415431
is_iana=True,
432+
name="image",
416433
)
417434
INSTALLER_TYPES: MediaCategory = MediaCategory(
418435
media_type=MediaType.INSTALLER,
419436
extensions=_INSTALLER_SET,
420437
is_iana=False,
438+
name="installer",
421439
)
422440
MATERIAL_TYPES: MediaCategory = MediaCategory(
423441
media_type=MediaType.MATERIAL,
424442
extensions=_MATERIAL_SET,
425443
is_iana=False,
444+
name="material",
426445
)
427446
MODEL_TYPES: MediaCategory = MediaCategory(
428447
media_type=MediaType.MODEL,
429448
extensions=_MODEL_SET,
430449
is_iana=True,
450+
name="model",
431451
)
432452
OPEN_DOCUMENT_TYPES: MediaCategory = MediaCategory(
433453
media_type=MediaType.OPEN_DOCUMENT,
434454
extensions=_OPEN_DOCUMENT_SET,
435455
is_iana=False,
456+
name="open document",
436457
)
437458
PACKAGE_TYPES: MediaCategory = MediaCategory(
438459
media_type=MediaType.PACKAGE,
439460
extensions=_PACKAGE_SET,
440461
is_iana=False,
462+
name="package",
441463
)
442464
PDF_TYPES: MediaCategory = MediaCategory(
443465
media_type=MediaType.PDF,
444466
extensions=_PDF_SET,
445467
is_iana=False,
468+
name="pdf",
446469
)
447470
PLAINTEXT_TYPES: MediaCategory = MediaCategory(
448471
media_type=MediaType.PLAINTEXT,
449472
extensions=_PLAINTEXT_SET,
450473
is_iana=False,
474+
name="plaintext",
451475
)
452476
PRESENTATION_TYPES: MediaCategory = MediaCategory(
453477
media_type=MediaType.PRESENTATION,
454478
extensions=_PRESENTATION_SET,
455479
is_iana=False,
480+
name="presentation",
456481
)
457482
PROGRAM_TYPES: MediaCategory = MediaCategory(
458483
media_type=MediaType.PROGRAM,
459484
extensions=_PROGRAM_SET,
460485
is_iana=False,
486+
name="program",
461487
)
462488
SHORTCUT_TYPES: MediaCategory = MediaCategory(
463489
media_type=MediaType.SHORTCUT,
464490
extensions=_SHORTCUT_SET,
465491
is_iana=False,
492+
name="shortcut",
466493
)
467494
SOURCE_ENGINE_TYPES: MediaCategory = MediaCategory(
468495
media_type=MediaType.SOURCE_ENGINE,
469496
extensions=_SOURCE_ENGINE_SET,
470497
is_iana=False,
498+
name="source engine",
471499
)
472500
SPREADSHEET_TYPES: MediaCategory = MediaCategory(
473501
media_type=MediaType.SPREADSHEET,
474502
extensions=_SPREADSHEET_SET,
475503
is_iana=False,
504+
name="spreadsheet",
476505
)
477506
TEXT_TYPES: MediaCategory = MediaCategory(
478507
media_type=MediaType.TEXT,
479508
extensions=_DOCUMENT_SET | _PLAINTEXT_SET,
480509
is_iana=True,
510+
name="text",
481511
)
482512
VIDEO_TYPES: MediaCategory = MediaCategory(
483513
media_type=MediaType.VIDEO,
484514
extensions=_VIDEO_SET,
485515
is_iana=True,
516+
name="video",
486517
)
487518

488519
ALL_CATEGORIES: list[MediaCategory] = [

tagstudio/tests/conftest.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,37 @@ def cwd():
2121
return CWD
2222

2323

24+
@pytest.fixture
25+
def file_mediatypes_library():
26+
lib = Library()
27+
28+
status = lib.open_library(pathlib.Path(""), ":memory:")
29+
assert status.success
30+
31+
entry1 = Entry(
32+
folder=lib.folder,
33+
path=pathlib.Path("foo.png"),
34+
fields=lib.default_fields,
35+
)
36+
37+
entry2 = Entry(
38+
folder=lib.folder,
39+
path=pathlib.Path("bar.png"),
40+
fields=lib.default_fields,
41+
)
42+
43+
entry3 = Entry(
44+
folder=lib.folder,
45+
path=pathlib.Path("baz.apng"),
46+
fields=lib.default_fields,
47+
)
48+
49+
assert lib.add_entries([entry1, entry2, entry3])
50+
assert len(lib.tags) == 2
51+
52+
return lib
53+
54+
2455
@pytest.fixture
2556
def library(request):
2657
# when no param is passed, use the default

tagstudio/tests/test_library.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -401,3 +401,21 @@ class TestPrefs(DefaultEnum):
401401
# accessing .value should raise exception
402402
with pytest.raises(AttributeError):
403403
assert TestPrefs.BAR.value
404+
405+
406+
@pytest.mark.parametrize(["filetype", "num_of_filetype"], [("md", 1), ("txt", 1), ("png", 0)])
407+
def test_filetype_search(library, filetype, num_of_filetype):
408+
results = library.search_library(FilterState(filetype=filetype))
409+
assert len(results.items) == num_of_filetype
410+
411+
412+
@pytest.mark.parametrize(["filetype", "num_of_filetype"], [("png", 2), ("apng", 1), ("ng", 0)])
413+
def test_filetype_return_one_filetype(file_mediatypes_library, filetype, num_of_filetype):
414+
results = file_mediatypes_library.search_library(FilterState(filetype=filetype))
415+
assert len(results.items) == num_of_filetype
416+
417+
418+
@pytest.mark.parametrize(["mediatype", "num_of_mediatype"], [("plaintext", 2), ("image", 0)])
419+
def test_mediatype_search(library, mediatype, num_of_mediatype):
420+
results = library.search_library(FilterState(mediatype=mediatype))
421+
assert len(results.items) == num_of_mediatype

0 commit comments

Comments
 (0)