Skip to content

Commit faec18a

Browse files
authored
v1.9.20 (#536)
* update deps * fix opds v1 pagination, bump version and news * add opds v2 alternate link into opds v1 * update deps, ignore ty thing * accept readium.progression type for progression parser * fix bookmark update crash * fix serializing opds v2 progression postion field * bump version and news * update deps * reduce opds v1 links complexity * lint README. talk about OIDC * trim spaces from opds v1 index template * fix schema generation crash * fix full text search barfing on sigle quoted term * User django lookups where possible for field search. Keep complex like search * advance djangotypes version' * debug log admin task submissions * make shutdown message for scribed a proper tuple * allow order by in GroupConcat aggregate * refactor use of GroupConcat in search sync * Bearer token only * update deps. vue router 5 * update bump version * bump news * bump news * use raw string for regex * dynamically generate TextChoices from enums, maps and strings * fix drf-spectacular warnings * opds order by filename * do basic and token auth before session auth for opds to present 401 and auth json * update deps, format * update deps * fix universe fts population * update deps * Centralize user_agent_name property in OPDSFeed base view. Type a lot. * fix href validation is num pages is 0 * allow subtitles in links * bump version and news * only include mtime in opds v2 feed if it exists * fix opds v2 start page feed modified date * update deps * remove cruft * update deps * refactor opds utility functions into different files * add NoContent API Exception. add all opds query params to exceptions * use regular exception handler for opds v2 progress endpoint * consolidate param copying in custom redirect view * bump version and news * bump comicbox * Adjust OPDS v2 progression position field to be 1 based, while Codex page is 0 based. * update deps * fix opds v2 progression crash * fix progress annotation math * fix metadata progress header math * fix broken reader close button * update deps * format * finally standardize view layout for opensearch and opds authentication * opds exception handling optimiziations * cast TextChoices metaclass into TextChoices * update deps * lint, mostly inline if elses into ors * update deps * update deps * no v-if needed in auth-token dialog, handled by parent * fix extra slot tag in clipboard. fix required subtitle in clipboard component * change some whites to textPrimary that aren't hovers * format makefile * format Dockerfiles * use numeric inputs for Poll Every * remove seconds from pollevery * update deps * lint dockerfiles with dockerfmt * more explict djangoDuration to integer fields back and forth * update deps * disable unsafe regex warning * remove errant console.log * Squashed commit of the following: commit 813a0229fc3f0564fe3c7791e306be2189fe2632 Author: AJ Slater <aj@slater.net> Date: Tue Feb 10 13:40:08 2026 -0800 add eslint plugin html commit 3a7171584da74e95c4ec3e28aa08d83cb2ca97d0 Author: AJ Slater <aj@slater.net> Date: Tue Feb 10 02:41:58 2026 -0800 eslint Math plugin commit 9814461b1a7d0f09b8eed4d73aa3a3a064fe4313 Author: AJ Slater <aj@slater.net> Date: Tue Feb 10 01:24:11 2026 -0800 alphabetize plugins commit 359983ed0fbcafa78f03a2f1355b9b2ce364b309 Author: AJ Slater <aj@slater.net> Date: Tue Feb 10 01:16:37 2026 -0800 clean up unecessacy eslint plugin declarations commit a4ca60fb8cd253b66fb0b9bb10e2660031fdebf8 Author: AJ Slater <aj@slater.net> Date: Tue Feb 10 01:10:15 2026 -0800 add de-morgan eslint plugin * add mbake * update deps * update deps * update deps * advance vuetify eslint plugin * Squashed commit of the following: commit d5f5f22c1339014b2c715e8671320eb6f9dc8883 Author: AJ Slater <aj@slater.net> Date: Fri Feb 13 21:16:12 2026 -0800 type infer types. Tasks all now inherit from LibraryTask commit fa09de3a15c72f516c47fe70d2e2cc64daef1ebc Author: AJ Slater <aj@slater.net> Date: Fri Feb 13 18:11:05 2026 -0800 update deps * update deps * update deps * Squashed commit of the following: commit 3ee9d5a7e4634be1bbbfaef658b54754b2f2673a Author: AJ Slater <aj@slater.net> Date: Sun Feb 15 14:26:41 2026 -0800 update devenv commit 09a87be542c7b949316d12e9f0ee27a9696af1ad Author: AJ Slater <aj@slater.net> Date: Sun Feb 15 12:54:44 2026 -0800 fix test function names to not start with test commit e9706dc71d64b7aa28b385a613d0c7dfbe7cfd80 Author: AJ Slater <aj@slater.net> Date: Sun Feb 15 12:54:21 2026 -0800 update devenv commit d89dc988706ec994a828b94ed7a2b2627fc89311 Author: AJ Slater <aj@slater.net> Date: Sun Feb 15 12:46:26 2026 -0800 switch to devenv * update devenv * update devenv * fix import to not bridge files * update deps * update deps * update comicbox. work with to_pixmap change to pdf_format * update deps and devenv * minimum vuetify 3.12 * use img-class prop on covers * Fix search indexing the universes tag. * add an item to news' * fix version-codex-dist-builder script for new fs layout * fix build for devenv * use build group for linting * add pathspec to lint group for roman script * run roman.py with uv * update devenv. use uv to run mbake * fix django makefile for codex build * fix ci build. make collectstatic and django-check in backend-test * bump news and version * remove non news item * update deven prettierignore * update devenv and deps * format * Vuetify 4 (#533) * vuetify 4. few bugs * update deps * update #item templates for vuetify 4 * use plus icon for admin create buttons * format * capitalize close book button * no if show needed in auth-token dialog, handled by parent. * fix clipboard bugs * fix h3 & h4 spacing issues with vuetify 4 * fix admin h3 h4 spacing for vuetify 4 * fix sidebar footer not falling to bottom * fix link colors for vuetify 4 * change some whites to textPrimary that aren't hovers * format makefile * format Dockerfiles * replace highlight-table with vuetify v-table striped * use numeric inputs for Poll Every * remove seconds from pollevery * move settingsSubHeader css into settings-drawer.vue * format * advance vuetify beta * fix vuetify dep * tweak font title sizes * modernize book cover v-img * remove cruft * update deps * update devenv * update news and version and deps * update deps * update devenv & deps * fix comic name when volume_to is null * Fix OPDS v2 folder as collection naming. * Provide volume name in comic title for OPDS v2 as there's no volume view available. * update comicbox version. bump news * credits now link to filtered views * update deps * credits and subjects opdsv2 filtered links * add other filters to subtitle kind of * show selected filters alway as active, on submenu with check icon. and move none item computation into api vuetify-items * update deps * finally resolve filter choices arguments vuetify null code issues. * reduce opds v2 complexity * reduce complecity of librarian and janitor task delagation * return (None) to None * fix isinstance bug with libarry task delegation refactor * fix filtering on credits when clicking on metadata chips
1 parent 84d47fa commit faec18a

File tree

18 files changed

+675
-418
lines changed

18 files changed

+675
-418
lines changed

NEWS.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,13 @@ width: 128px;
66
border-radius: 128px;
77
" />
88

9+
## v1.9.20
10+
11+
- Features
12+
- Browser filters now show selected filters more clearly in the menus.
13+
- Fix filtering on credits when clicking on metadata chips.
14+
- OPDS v2 credits and subjects now link to views filtered on the tag.
15+
916
## v1.9.19
1017

1118
- Fixes

codex/librarian/librariand.py

Lines changed: 24 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
from codex.librarian.scribe.scribed import ScribeThread
2727
from codex.librarian.scribe.search.tasks import SearchIndexSyncTask
2828
from codex.librarian.scribe.tasks import ScribeTask
29-
from codex.librarian.tasks import LibrarianShutdownTask, WakeCronTask
29+
from codex.librarian.tasks import LibrarianShutdownTask, LibrarianTask, WakeCronTask
3030
from codex.librarian.watchdog.event_batcherd import WatchdogEventBatcherThread
3131
from codex.librarian.watchdog.observers import (
3232
LibraryEventObserver,
@@ -52,6 +52,12 @@
5252
{snakecase(thread_class.__name__): thread_class for thread_class in _THREAD_CLASSES}
5353
)
5454
LibrarianThreads = NamedTuple("LibrarianThreads", tuple(_THREAD_CLASS_MAP.items())) # ty: ignore[invalid-named-tuple]
55+
_THREAD_QUEUE_TASK_MAP: dict[type, str] = {
56+
CoverTask: "cover_thread",
57+
BookmarkTask: "bookmark_thread",
58+
NotifierTask: "notifier_thread",
59+
WatchdogEventTask: "watchdog_event_batcher_thread",
60+
}
5561

5662

5763
class LibrarianDaemon(Process):
@@ -75,31 +81,35 @@ def __init__(self, logger_, queue: Queue, broadcast_queue: AioQueue) -> None:
7581
self.run_loop = True
7682
self._reversed_threads = ()
7783

78-
def _process_task(self, task) -> None: # noqa: C901
84+
def _sync_watchdog_observers(self) -> None:
85+
for observer in self._observers:
86+
observer.sync_library_watches()
87+
88+
def _restart_codex(self, task: LibrarianTask) -> None:
89+
restarter = CodexRestarter(self.log, self.queue, self.db_write_lock)
90+
restarter.handle_task(task)
91+
92+
def _process_task(self, task) -> None:
7993
"""Process an individual task popped off the queue."""
94+
# Simply requeue tasks to the handler thread.
95+
for task_type, thread_attr in _THREAD_QUEUE_TASK_MAP.items():
96+
if isinstance(task, task_type):
97+
getattr(self._threads, thread_attr).queue.put(task)
98+
return
8099
match task:
81-
case CoverTask():
82-
self._threads.cover_thread.queue.put(task)
83-
case BookmarkTask():
84-
self._threads.bookmark_thread.queue.put(task)
85-
case NotifierTask():
86-
self._threads.notifier_thread.queue.put(task)
87-
case WatchdogEventTask():
88-
self._threads.watchdog_event_batcher_thread.queue.put(task)
89100
case ScribeTask():
101+
# Special put method does queue put preprocessing.
90102
self._threads.scribe_thread.put(task)
91103
case WatchdogSyncTask():
92-
for observer in self._observers:
93-
observer.sync_library_watches()
104+
self._sync_watchdog_observers()
94105
case WatchdogPollLibrariesTask():
95106
self._threads.library_polling_observer.poll(
96107
task.library_ids, force=task.force
97108
)
98109
case WakeCronTask():
99110
self._threads.cron_thread.end_timeout()
100111
case CodexRestarterTask():
101-
restarter = CodexRestarter(self.log, self.queue, self.db_write_lock)
102-
restarter.handle_task(task)
112+
self._restart_codex(task)
103113
case LibrarianShutdownTask():
104114
self.log.info(f"Shutting down {self.name}...")
105115
self.run_loop = False

codex/librarian/scribe/janitor/janitor.py

Lines changed: 39 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
SearchIndexOptimizeTask,
4545
SearchIndexSyncTask,
4646
)
47+
from codex.librarian.tasks import LibrarianTask
4748
from codex.models import Timestamp
4849

4950
_JANITOR_STATII = (
@@ -67,6 +68,35 @@
6768
RemoveCoversStatus,
6869
)
6970

71+
_NIGHTLY_TASK_CLASSES: tuple[type[LibrarianTask], ...] = (
72+
CodexLatestVersionTask,
73+
JanitorAdoptOrphanFoldersTask,
74+
JanitorForeignKeyCheckTask,
75+
JanitorIntegrityCheckTask,
76+
JanitorFTSIntegrityCheckTask,
77+
JanitorCleanFKsTask,
78+
JanitorCleanCoversTask,
79+
JanitorCleanupSessionsTask,
80+
JanitorCleanupBookmarksTask,
81+
SearchIndexSyncTask,
82+
SearchIndexOptimizeTask,
83+
JanitorVacuumTask,
84+
JanitorBackupTask,
85+
CoverRemoveOrphansTask,
86+
)
87+
_JANITOR_METHOD_MAP: dict[type, str] = {
88+
JanitorVacuumTask: "vacuum_db",
89+
JanitorCleanFKsTask: "cleanup_fks",
90+
JanitorCleanCoversTask: "cleanup_custom_covers",
91+
JanitorCleanupSessionsTask: "cleanup_sessions",
92+
JanitorCleanupBookmarksTask: "cleanup_orphan_bookmarks",
93+
JanitorImportForceAllFailedTask: "force_update_all_failed_imports",
94+
JanitorForeignKeyCheckTask: "foreign_key_check",
95+
JanitorFTSIntegrityCheckTask: "fts_integrity_check",
96+
JanitorFTSRebuildTask: "fts_rebuild",
97+
JanitorNightlyTask: "queue_nightly_tasks",
98+
}
99+
70100

71101
class Janitor(JanitorCodexUpdate):
72102
"""Janitor inline task runner."""
@@ -75,56 +105,26 @@ def queue_nightly_tasks(self) -> None:
75105
"""Queue all the janitor tasks."""
76106
try:
77107
self.status_controller.start_many(_JANITOR_STATII)
78-
tasks = (
79-
CodexLatestVersionTask(),
80-
JanitorAdoptOrphanFoldersTask(),
81-
JanitorForeignKeyCheckTask(),
82-
JanitorIntegrityCheckTask(),
83-
JanitorFTSIntegrityCheckTask(),
84-
JanitorCleanFKsTask(),
85-
JanitorCleanCoversTask(),
86-
JanitorCleanupSessionsTask(),
87-
JanitorCleanupBookmarksTask(),
88-
SearchIndexSyncTask(),
89-
SearchIndexOptimizeTask(),
90-
JanitorVacuumTask(),
91-
JanitorBackupTask(),
92-
CoverRemoveOrphansTask(),
93-
)
94-
for task in tasks:
95-
self.librarian_queue.put(task)
108+
for task_class in _NIGHTLY_TASK_CLASSES:
109+
self.librarian_queue.put(task_class())
96110
Timestamp.touch(Timestamp.Choices.JANITOR)
97111
except Exception:
98112
self.log.exception(f"In {self.__class__.__name__}")
99113

100-
def handle_task(self, task) -> None: # noqa: PLR0912,C901
114+
def handle_task(self, task) -> None:
101115
"""Run Janitor tasks as the librarian process directly."""
102116
try:
117+
# Simple task dispatch
118+
if method_name := _JANITOR_METHOD_MAP.get(type(task)):
119+
getattr(self, method_name)()
120+
return
121+
122+
# Tasks with special parameters
103123
match task:
104-
case JanitorVacuumTask():
105-
self.vacuum_db()
106124
case JanitorBackupTask():
107125
self.backup_db(show_status=True)
108-
case JanitorCleanFKsTask():
109-
self.cleanup_fks()
110-
case JanitorCleanCoversTask():
111-
self.cleanup_custom_covers()
112-
case JanitorCleanupSessionsTask():
113-
self.cleanup_sessions()
114-
case JanitorCleanupBookmarksTask():
115-
self.cleanup_orphan_bookmarks()
116-
case JanitorImportForceAllFailedTask():
117-
self.force_update_all_failed_imports()
118-
case JanitorForeignKeyCheckTask():
119-
self.foreign_key_check()
120126
case JanitorIntegrityCheckTask():
121127
self.integrity_check(long=task.long)
122-
case JanitorFTSIntegrityCheckTask():
123-
self.fts_integrity_check()
124-
case JanitorFTSRebuildTask():
125-
self.fts_rebuild()
126-
case JanitorNightlyTask():
127-
self.queue_nightly_tasks()
128128
case JanitorCodexUpdateTask():
129129
self.update_codex(force=task.force)
130130
case _:

codex/serializers/browser/choices.py

Lines changed: 25 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
from rest_framework.fields import (
66
BooleanField,
77
CharField,
8-
ListField,
98
SerializerMethodField,
109
)
1110
from rest_framework.serializers import Serializer
@@ -21,6 +20,7 @@
2120
from codex.serializers.fields.vuetify import (
2221
VuetifyFileTypeChoiceField,
2322
VuetifyReadingDirectionChoiceField,
23+
VuetifyReadOnlyListField,
2424
)
2525
from codex.serializers.models.pycountry import CountrySerializer, LanguageSerializer
2626

@@ -57,42 +57,32 @@ class BrowserSettingsFilterSerializer(Serializer):
5757

5858
bookmark = BookmarkFilterField(required=False, read_only=True)
5959
# Dynamic filters
60-
age_rating = ListField(child=VuetifyCharField(), required=False, read_only=True)
61-
characters = ListField(child=VuetifyIntegerField(), required=False, read_only=True)
62-
country = ListField(child=VuetifyIntegerField(), required=False, read_only=True)
63-
credits = ListField(child=VuetifyIntegerField(), required=False, read_only=True)
64-
critical_rating = ListField(
65-
child=VuetifyDecimalField(max_digits=5, decimal_places=2),
66-
required=False,
67-
read_only=True,
60+
age_rating = VuetifyReadOnlyListField()
61+
characters = VuetifyReadOnlyListField()
62+
country = VuetifyReadOnlyListField()
63+
credits = VuetifyReadOnlyListField()
64+
critical_rating = VuetifyReadOnlyListField(
65+
child=VuetifyDecimalField(max_digits=5, decimal_places=2)
6866
)
69-
decade = ListField(child=VuetifyDecadeField(), required=False, read_only=True)
70-
file_type = ListField(
71-
child=VuetifyFileTypeChoiceField(), required=False, read_only=True
67+
decade = VuetifyReadOnlyListField(child=VuetifyDecadeField)
68+
file_type = VuetifyReadOnlyListField(child=VuetifyFileTypeChoiceField)
69+
genres = VuetifyReadOnlyListField()
70+
identifier_source = VuetifyReadOnlyListField()
71+
language = VuetifyReadOnlyListField()
72+
locations = VuetifyReadOnlyListField()
73+
monochrome = VuetifyReadOnlyListField(child=VuetifyBooleanField)
74+
original_format = VuetifyReadOnlyListField()
75+
reading_direction = VuetifyReadOnlyListField(
76+
child=VuetifyReadingDirectionChoiceField
7277
)
73-
genres = ListField(child=VuetifyIntegerField(), required=False, read_only=True)
74-
identifier_source = ListField(
75-
child=VuetifyCharField(), required=False, read_only=True
76-
)
77-
language = ListField(child=VuetifyIntegerField(), required=False, read_only=True)
78-
locations = ListField(child=VuetifyIntegerField(), required=False, read_only=True)
79-
monochrome = ListField(child=VuetifyBooleanField(), required=False, read_only=True)
80-
original_format = ListField(
81-
child=VuetifyCharField(), required=False, read_only=True
82-
)
83-
reading_direction = ListField(
84-
child=VuetifyReadingDirectionChoiceField(), required=False, read_only=True
85-
)
86-
series_groups = ListField(
87-
child=VuetifyIntegerField(), required=False, read_only=True
88-
)
89-
stories = ListField(child=VuetifyIntegerField(), required=False, read_only=True)
90-
story_arcs = ListField(child=VuetifyIntegerField(), required=False, read_only=True)
91-
tagger = ListField(child=VuetifyCharField(), required=False, read_only=True)
92-
tags = ListField(child=VuetifyIntegerField(), required=False, read_only=True)
93-
teams = ListField(child=VuetifyIntegerField(), required=False, read_only=True)
94-
universes = ListField(child=VuetifyIntegerField(), required=False, read_only=True)
95-
year = ListField(child=VuetifyIntegerField(), required=False, read_only=True)
78+
series_groups = VuetifyReadOnlyListField()
79+
stories = VuetifyReadOnlyListField()
80+
story_arcs = VuetifyReadOnlyListField()
81+
tagger = VuetifyReadOnlyListField()
82+
tags = VuetifyReadOnlyListField()
83+
teams = VuetifyReadOnlyListField()
84+
universes = VuetifyReadOnlyListField()
85+
year = VuetifyReadOnlyListField()
9686

9787

9888
class BrowserChoicesIntegerPkSerializer(Serializer):
Lines changed: 29 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,46 @@
11
"""Browser Settings Filter Serializers."""
22

3-
from rest_framework.serializers import ListField, Serializer
3+
from rest_framework.serializers import Serializer
44

55
from codex.serializers.fields import (
66
VuetifyBooleanField,
77
VuetifyDecadeField,
88
VuetifyDecimalField,
9-
VuetifyIntegerField,
109
)
1110
from codex.serializers.fields.browser import BookmarkFilterField
11+
from codex.serializers.fields.vuetify import (
12+
VuetifyFileTypeChoiceField,
13+
VuetifyListField,
14+
VuetifyReadingDirectionChoiceField,
15+
)
1216

1317

1418
class BrowserSettingsFilterInputSerializer(Serializer):
1519
"""Filter values for settings."""
1620

1721
bookmark = BookmarkFilterField(required=False)
1822
# Dynamic filters
19-
age_rating = ListField(child=VuetifyIntegerField(), required=False)
20-
characters = ListField(child=VuetifyIntegerField(), required=False)
21-
country = ListField(child=VuetifyIntegerField(), required=False)
22-
credits = ListField(child=VuetifyIntegerField(), required=False)
23-
critical_rating = ListField(
24-
child=VuetifyDecimalField(max_digits=5, decimal_places=2), required=False
23+
age_rating = VuetifyListField()
24+
characters = VuetifyListField()
25+
country = VuetifyListField()
26+
credits = VuetifyListField()
27+
critical_rating = VuetifyListField(
28+
child=VuetifyDecimalField(max_digits=5, decimal_places=2)
2529
)
26-
decade = ListField(child=VuetifyDecadeField(), required=False)
27-
file_type = ListField(child=VuetifyIntegerField(), required=False)
28-
genres = ListField(child=VuetifyIntegerField(), required=False)
29-
identifier_source = ListField(child=VuetifyIntegerField(), required=False)
30-
language = ListField(child=VuetifyIntegerField(), required=False)
31-
locations = ListField(child=VuetifyIntegerField(), required=False)
32-
monochrome = ListField(child=VuetifyBooleanField(), required=False)
33-
original_format = ListField(child=VuetifyIntegerField(), required=False)
34-
reading_direction = ListField(child=VuetifyIntegerField(), required=False)
35-
series_groups = ListField(child=VuetifyIntegerField(), required=False)
36-
stories = ListField(child=VuetifyIntegerField(), required=False)
37-
story_arcs = ListField(child=VuetifyIntegerField(), required=False)
38-
tagger = ListField(child=VuetifyIntegerField(), required=False)
39-
tags = ListField(child=VuetifyIntegerField(), required=False)
40-
teams = ListField(child=VuetifyIntegerField(), required=False)
41-
universes = ListField(child=VuetifyIntegerField(), required=False)
42-
year = ListField(child=VuetifyIntegerField(), required=False)
30+
decade = VuetifyListField(child=VuetifyDecadeField)
31+
file_type = VuetifyListField(child=VuetifyFileTypeChoiceField)
32+
genres = VuetifyListField()
33+
identifier_source = VuetifyListField()
34+
language = VuetifyListField()
35+
locations = VuetifyListField()
36+
monochrome = VuetifyListField(child=VuetifyBooleanField)
37+
original_format = VuetifyListField()
38+
reading_direction = VuetifyListField(child=VuetifyReadingDirectionChoiceField)
39+
series_groups = VuetifyListField()
40+
stories = VuetifyListField()
41+
story_arcs = VuetifyListField()
42+
tagger = VuetifyListField()
43+
tags = VuetifyListField()
44+
teams = VuetifyListField()
45+
universes = VuetifyListField()
46+
year = VuetifyListField()

0 commit comments

Comments
 (0)