Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
315 commits
Select commit Hold shift + click to select a range
5360e82
fix docker-tag-latest cscript
ajslater Apr 7, 2026
ec0eee2
ignore gh token file
ajslater Apr 7, 2026
837ed69
try to log more request errors
ajslater Apr 7, 2026
c7b3edf
reconfigure logging to hopefully be more verbose about request errors…
ajslater Apr 7, 2026
bb9aa03
update deps and bump to alpha version
ajslater Apr 7, 2026
63eb55f
bump news (#555)
ajslater Apr 7, 2026
68b5c44
v1.10.7
ajslater Apr 7, 2026
4e845b2
bump news
ajslater Apr 7, 2026
125b061
update devenv
ajslater Apr 7, 2026
1811aa3
update devevn and deps
ajslater Apr 7, 2026
d0203e6
fix pm script
ajslater Apr 7, 2026
6761799
move django-check to test category
ajslater Apr 7, 2026
ccc3b15
workflow build frontend and collect static for prodcution build. fix …
ajslater Apr 7, 2026
575f3ca
bump version and news 1.10.8
ajslater Apr 7, 2026
2a4be92
fix dev-module script
ajslater Apr 7, 2026
a9b9c54
fix news
ajslater Apr 7, 2026
c4f42eb
explain news
ajslater Apr 7, 2026
03edb5b
Merge branch 'main' into develop
ajslater Apr 7, 2026
4d07d2a
Merge branch 'main' into develop
ajslater Apr 8, 2026
499e853
fix opds clear search setting
ajslater Apr 8, 2026
1968845
fix clear search button
ajslater Apr 8, 2026
188c890
Merge branch 'main' into develop
ajslater Apr 8, 2026
382ea9b
use a registry cache instead of gha cache for the dist-builder
ajslater Apr 8, 2026
68926b9
gha use more env vars for image names. retain python dist for 2 days.
ajslater Apr 8, 2026
cab90ff
new quick deploy gha script. update deps & devenv.
ajslater Apr 9, 2026
9179cd1
silence watchfiles 5 second timeout debug message
ajslater Apr 9, 2026
299d24d
consolidate null values const
ajslater Apr 9, 2026
384d6f0
make scope private
ajslater Apr 9, 2026
929ac98
update devenv
ajslater Apr 10, 2026
3f23d63
update deps. migrate to unhead v3
ajslater Apr 10, 2026
ca2b99a
update deps
ajslater Apr 12, 2026
ac23fb6
fix creating reader global settings
ajslater Apr 12, 2026
b118359
fix caching
ajslater Apr 12, 2026
a9bab2a
rename codex build-dist to codex-ci
ajslater Apr 12, 2026
498a2d0
fix image name. make gha steps depend on each other more.
ajslater Apr 12, 2026
00307e2
fix gha syntax errors
ajslater Apr 12, 2026
0e62ca4
Merge branch 'main' into develop
ajslater Apr 12, 2026
1a0865a
names for gha steps
ajslater Apr 13, 2026
8d73870
use ghcr.io for python-debian base
ajslater Apr 13, 2026
ce3c806
update deps
ajslater Apr 13, 2026
aea24a4
format dockerfile
ajslater Apr 13, 2026
928084a
picopt treestamps
ajslater Apr 13, 2026
34325e8
fix custom covers not importing. v1.10.11
ajslater Apr 13, 2026
8364163
fix custom covers count in admin view
ajslater Apr 13, 2026
84a958e
bump news for custom cover count fix
ajslater Apr 13, 2026
d28662c
update deps
ajslater Apr 13, 2026
97daa11
Merge branch 'main' into develop
ajslater Apr 13, 2026
9abd4fd
codex identification in server tag and opds generator tag
ajslater Apr 14, 2026
67a823f
update deps
ajslater Apr 14, 2026
2791819
force no entries on opds start page
ajslater Apr 14, 2026
fe6ef60
common opds start page mixin. emtpy group objects on start page
ajslater Apr 14, 2026
68f1c65
update deps. typechecking.
ajslater Apr 15, 2026
c2a37bf
api change q to search
ajslater Apr 15, 2026
62baa9e
standardize search param as 'search' instead of 'q' or other variations
ajslater Apr 15, 2026
045293d
remove errant icecream
ajslater Apr 15, 2026
44a9d32
clear settings on backend
ajslater Apr 15, 2026
ceba72d
Squashed commit of the following:
ajslater Apr 15, 2026
ee330e1
simplify settings class hierarchy
ajslater Apr 15, 2026
8e57ae4
rename select-many store to browser-select-many
ajslater Apr 15, 2026
afd71d2
switch to bun. updated devenv
ajslater Apr 16, 2026
0eb4799
add a claude md
ajslater Apr 16, 2026
4bff1ee
use frozenattrdict to speed up configuration
ajslater Apr 16, 2026
49c508f
fix rename of browserSelectMany store
ajslater Apr 23, 2026
b297064
auth token help
ajslater Apr 23, 2026
505baf3
fix sort-ignores to make deterministic across shells with different l…
ajslater Apr 23, 2026
fabd716
fix crash on settings not being raw
ajslater Apr 20, 2026
74a506c
another gaurd for getMetadta()
ajslater Apr 22, 2026
c6ae79b
remove keys from unhead meta headers
ajslater Apr 20, 2026
2cf3e49
fix unhead description for admin tabs"
ajslater Apr 22, 2026
889b99d
fix overzealous lazy importer
ajslater Apr 21, 2026
b192b59
fix lazyImportEnabled variable in metadata-activator
ajslater Apr 21, 2026
31baf54
fix metadata activator from bad cherry pick
ajslater Apr 23, 2026
2a46944
fix errant quote
ajslater Apr 23, 2026
a7d0c86
fix typechecking
ajslater Apr 23, 2026
2ae90fc
update devenv & deps
ajslater Apr 23, 2026
49284b4
bump news
ajslater Apr 23, 2026
8a396f0
fix import bug linking folders
ajslater Apr 20, 2026
d6dcbe4
fix possible batching crashes. adjust import variables for throughput
ajslater Apr 20, 2026
b3ec926
batch comic updates
ajslater Apr 20, 2026
65f2b05
move INTERNAL_IPS setting to general django area
ajslater Apr 23, 2026
e0719d1
fix typechecking issue
ajslater Apr 23, 2026
8901560
update deps
ajslater Apr 23, 2026
0885048
bump version to v1.10.12
ajslater Apr 23, 2026
40ce697
fix redirect on OPDS alternate view with metadata
ajslater Apr 23, 2026
6d20994
minor change to browser empty page for better first time experience
ajslater Apr 23, 2026
77174e4
allow browsing to comics with any top group in opds
ajslater Apr 23, 2026
7dfd33b
bring project up to speed with bun and no package-lock.json
ajslater Apr 23, 2026
c0edcc7
Merge branch 'main' into develop
ajslater Apr 23, 2026
23c3ac2
fix browser paginator
ajslater Apr 24, 2026
d59ae4f
bump news for browser paginaor fix
ajslater Apr 24, 2026
8c23450
fix search combobox clearing
ajslater Apr 24, 2026
6bab5e8
uppercase book close button
ajslater Apr 27, 2026
17df41a
version v1.10.13. update deps
ajslater Apr 27, 2026
59f52ae
fix pdfs not displaying
ajslater Apr 27, 2026
731b59a
fix csp for pdfs
ajslater Apr 27, 2026
e511828
fix OPDS FK constraint failure when session row is missing (#607)
ajslater Apr 27, 2026
7264de8
extend session-key validation to bookmark + reader-settings paths (#608)
ajslater Apr 27, 2026
251b09e
format
ajslater Apr 27, 2026
0b51523
bump news
ajslater Apr 27, 2026
8063871
Merge branch 'main' into develop
ajslater Apr 27, 2026
ae40799
Stats: fix user_registered_count / auth_group_count always-zero bug (…
ajslater Apr 27, 2026
8861b9f
defaults for dockerfile ARGs
ajslater Apr 28, 2026
62dc95b
format'
ajslater Apr 28, 2026
982a507
Frontend correctness: 10 bug fixes from sub-plan 01 (#647)
ajslater Apr 28, 2026
517a41c
news for cherry pick
ajslater Apr 28, 2026
a214681
OPDS auth: send WWW-Authenticate + opds-authentication content-type o…
ajslater Apr 28, 2026
e0fb6a1
fix typing inheritence with drf perms
ajslater Apr 28, 2026
d796b40
bump version to v1.10.14
ajslater Apr 28, 2026
14c3d32
update picopt treestamps
ajslater Apr 28, 2026
6625743
fix typing import error
ajslater Apr 28, 2026
4ade941
fix vulture ignorelist
ajslater Apr 28, 2026
8e36cef
readd the mistakenly removed vulture_ignorelist
ajslater Apr 28, 2026
4dcffb0
Merge branch 'main' into develop
ajslater Apr 28, 2026
7924207
v1.10.15 fix show order bug. update deps
ajslater May 1, 2026
90e0cb3
fix nightly job manual expansion
ajslater May 1, 2026
ce5d1b6
fix news version number
ajslater May 1, 2026
ea2d589
Merge branch 'main' into develop
ajslater May 1, 2026
8975999
V1.11 performance (#714)
ajslater May 4, 2026
158c29e
use comicbox 3
ajslater May 4, 2026
d6a2c6c
Merge branch 'develop' of github.com:ajslater/codex into develop
ajslater May 4, 2026
de2c237
update version to v1.11.0
ajslater May 4, 2026
f6b99b7
Merge branch 'main' into develop
ajslater May 4, 2026
c429b9a
v1.11.1 fix anonymous user crash on midddleware setting timezone
ajslater May 4, 2026
50a4102
middleware: use ``session.aget`` in the async path of CodexMiddleware…
ajslater May 4, 2026
22c9ffd
update devenv
ajslater May 4, 2026
6d1558b
Merge branch 'main' into develop
ajslater May 4, 2026
8e13ca1
frontend: use static import for useCommonStore in api/v3/base.js (#719)
ajslater May 4, 2026
95ac80f
format news and picopt treestamps
ajslater May 4, 2026
a24d6f9
reader: restore scroll-to-top on horizontal page change with cacheBoo…
ajslater May 4, 2026
9d8b70d
Fix reader not scrolling to top of page
ajslater May 4, 2026
95716be
add folder view default feature to news
ajslater May 4, 2026
b5878fd
reader: default cache_book to False; reset global override on upgrade…
ajslater May 4, 2026
8e84ba9
browser: collapse browser pane to a single scroller (#723)
ajslater May 4, 2026
bf20a20
bump news. update deps
ajslater May 4, 2026
6a1741b
update deps
ajslater May 4, 2026
ce4782b
Merge branch 'main' into develop
ajslater May 4, 2026
0d65678
metadata: include ids in current-group list field (#725)
ajslater May 5, 2026
ab3679d
update version and bump news v1.11.3
ajslater May 5, 2026
6c211d0
metadata: extract group_list helper for GroupSerializer queryset shap…
ajslater May 5, 2026
afe14e1
frontend: simplify vuetify-items, remove ES2024 dependency (#727)
ajslater May 5, 2026
768d6aa
frontend(api/v3): cleanup pass — dead code, mutations, conventions (#…
ajslater May 5, 2026
8853970
frontend: move vuetify-items.js out of api/v3/ (#729)
ajslater May 5, 2026
58d6294
frontend(api/v3): convert default exports to named exports (#730)
ajslater May 5, 2026
4f5b3c6
Merge branch 'main' into develop
ajslater May 5, 2026
7014ab6
v1.11.4 fix firefox scrollbar
ajslater May 5, 2026
dca5365
update deps
ajslater May 5, 2026
0dbd42e
build(icons): replace cairosvg+inkscape with resvg-py (#733)
ajslater May 5, 2026
8bdbd52
update deps
ajslater May 5, 2026
9f532b4
format svg
ajslater May 5, 2026
8b76180
regenerate and optimize svg
ajslater May 5, 2026
f49e04f
format svg
ajslater May 5, 2026
f8feb7b
move build icons to a seperate step outide of build where it's done v…
ajslater May 5, 2026
b798f6c
Merge branch 'main' into develop
ajslater May 5, 2026
5263f71
format makefiles
ajslater May 6, 2026
ff5cf0c
fix links in readmes and organize OPDS clients
ajslater May 6, 2026
5129326
fix doubled "waiting for manual poll" log on every poll cycle (#734)
ajslater May 7, 2026
1254af4
update version to 1.11.5 & deps
ajslater May 7, 2026
2eb7e3f
fix snapshot inode-collision corruption + cleanup migration (#735)
ajslater May 7, 2026
82be7ce
refresh stale Comic.stat across inode rotations without re-import (#736)
ajslater May 7, 2026
8ed35f7
ty ignore
ajslater May 7, 2026
b69cd0b
update deps
ajslater May 7, 2026
b7a706f
bump news for fs fix
ajslater May 7, 2026
debbbfc
fix Ctrl+C hang from cover ProcessPoolExecutor (#714 regression) (#737)
ajslater May 8, 2026
5fa5f55
update deps
ajslater May 8, 2026
119f729
fix dev Vite HMR blocked by CSP / allowedHosts mismatch (#738)
ajslater May 8, 2026
85943ee
mangle DHCP-assigned FQDNs down to mDNS .local form (#739)
ajslater May 8, 2026
8572935
reader: add ?hide_text=1 to suppress visible OCR text on PDF pages (#…
ajslater May 9, 2026
25e0d00
Revert "reader: add ?hide_text=1 to suppress visible OCR text on PDF …
ajslater May 9, 2026
b3decfc
update deps
ajslater May 9, 2026
de1d732
fix dev Vite HMR still blocked by stale service worker CSP (#742)
ajslater May 9, 2026
1630a5c
fix local hmr server
ajslater May 9, 2026
814a5b4
add fixes to news
ajslater May 9, 2026
ff84a29
bump comicbox version
ajslater May 9, 2026
49862d6
Merge branch 'main' into develop
ajslater May 9, 2026
12d1239
reader: serve image-dominant PDF pages as <img>, drop full-PDF mode (…
ajslater May 9, 2026
ecd3e3d
Add browser table view (#745)
ajslater May 9, 2026
3449bd4
remove old tasks
ajslater May 9, 2026
4c8db45
bump news and version to v1.12.0
ajslater May 9, 2026
93015d3
update deps
ajslater May 9, 2026
b090a8d
format
ajslater May 9, 2026
e1a69f0
fix complexipy warnings in browser table-view dispatch helpers (#746)
ajslater May 9, 2026
639f936
split consolidated 0041 into separate phantom-cleanup + table-view mi…
ajslater May 9, 2026
26235aa
add table view to readme features
ajslater May 9, 2026
31a9c96
fix radon complexity warnings across browser table-view code + tests …
ajslater May 9, 2026
d4b9fb8
fix `make ty` warnings in browser table-view code (#749)
ajslater May 9, 2026
2349194
add per-user favorites (#750)
ajslater May 9, 2026
c3659fe
update deps
ajslater May 9, 2026
3f44a08
format
ajslater May 9, 2026
b4bf05b
fix type checking
ajslater May 9, 2026
5af21e9
speling
ajslater May 9, 2026
cfbac48
add smart canonical-order column insertion in browser table column pi…
ajslater May 9, 2026
bbb9f3d
filter orphan keys from generated browser choices json (#752)
ajslater May 9, 2026
2957e79
fix codespell config silently skipping all files (#753)
ajslater May 9, 2026
15efb19
remove old tasks
ajslater May 9, 2026
99e04c5
fix spelnig
ajslater May 9, 2026
c3ad4a7
remove ignore words frome codespell
ajslater May 9, 2026
8964deb
fix codespell skips
ajslater May 9, 2026
57ac98a
audit and fix tool ignore/skip configs (#754)
ajslater May 10, 2026
bb56f23
v1.12.0a0 (#755)
ajslater May 10, 2026
6e30091
restore regular v1.12.0 version
ajslater May 10, 2026
d4b6e71
remove old dev uv sources
ajslater May 10, 2026
248fcae
update devenv
ajslater May 10, 2026
48ff676
update deps
ajslater May 10, 2026
d2f0a4d
Merge branch 'main' into develop
ajslater May 10, 2026
f0cc758
Add optional failed-login log for fail2ban et al. (#757)
ajslater May 10, 2026
23e8441
bump version and news for fail2ban support. v1.12.1
ajslater May 10, 2026
490e005
bump news for new comicbox & comicfn2dict
ajslater May 10, 2026
10d5662
update features section
ajslater May 10, 2026
c68ee16
adjust news
ajslater May 10, 2026
6008813
add toml reference to readme
ajslater May 10, 2026
f16dab1
Downgrade routine anon-403s on /api/v3/auth/profile/ to DEBUG (#759)
ajslater May 10, 2026
9256b08
Merge branch 'develop' of github.com:ajslater/codex into develop
ajslater May 10, 2026
4548ffd
bump news
ajslater May 10, 2026
37b51ca
Keep failed-login IPs out of the main log (#760)
ajslater May 10, 2026
7681470
Merge branch 'develop' of github.com:ajslater/codex into develop
ajslater May 10, 2026
ec0ba2e
Merge branch 'main' into develop
ajslater May 11, 2026
12dbb4f
fix(browser): restore card-mode order_value caption for mixed-childre…
ajslater May 11, 2026
0ba4022
designate fenced log message in README
ajslater May 11, 2026
a29ceb5
bump news and version 1.12.2
ajslater May 11, 2026
0cde9c9
Merge branch 'main' into develop
ajslater May 11, 2026
61f14ed
fix(dev): make Vite dev server reachable from LAN browsers (#764)
ajslater May 12, 2026
9f9024e
fix(metadata): drop unneeded scrollbar on MetadataText values (#765)
ajslater May 12, 2026
b6acbf5
bump version to v1.12.3 & news
ajslater May 12, 2026
7536979
Merge branch 'develop' of github.com:ajslater/codex into develop
ajslater May 12, 2026
5a33971
feat(reader): swipe to turn pages on touch devices (#766)
ajslater May 12, 2026
4849da9
bump news
ajslater May 12, 2026
8d864ea
update deps
ajslater May 12, 2026
0ffbe5d
Merge branch 'main' into develop
ajslater May 12, 2026
e3c1c8a
update deps
ajslater May 14, 2026
c3a525b
force update tags by individual group. bump news & version to 1.12.4
ajslater May 15, 2026
cc26dd1
update deps
ajslater May 15, 2026
b96a386
fix(librarian): register JanitorCleanupFavoritesTask in scribe priori…
ajslater May 15, 2026
6304399
bump news
ajslater May 15, 2026
010e914
speling
ajslater May 15, 2026
b98cc45
Merge branch 'main' into develop
ajslater May 15, 2026
91c95fa
fix remark call with bun, update deps
ajslater May 20, 2026
ca84be1
fix eslint config my moving packagejson plugin out of js blokc
ajslater May 20, 2026
2779518
update devenv
ajslater May 20, 2026
9404e45
update deps
ajslater May 20, 2026
125b650
update deps
ajslater May 20, 2026
a5e582a
update deps
ajslater May 20, 2026
3b78013
format. multi line js comments
ajslater May 20, 2026
9ab0d4a
fix(browser): bookmark_updated_at column crashes group-row table view
ajslater May 20, 2026
a709a74
feat(librarian): ignore dotfiles in poller and watcher
ajslater May 20, 2026
a6fb395
bump version and news
ajslater May 20, 2026
2b3bfdc
feat(browser): shift-click range select in cover and table views
ajslater May 20, 2026
3884e68
update deps
ajslater May 21, 2026
cd0b63a
bump news
ajslater May 21, 2026
0654aa6
remove useless yields
ajslater May 21, 2026
d8c9831
fix tests
ajslater May 21, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,14 @@ width: 128px;
border-radius: 128px;
" />

## v1.12.5

- Fixes
- Fix "Last Read" column in table view crashing.
- Ignore dotfiles.
- Features
- Select by range in multi-select with shift+click.

## v1.12.4

- Fixes
Expand Down
59 changes: 51 additions & 8 deletions bun.lock

Large diffs are not rendered by default.

5 changes: 4 additions & 1 deletion cfg/eslint.config.base.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import * as eslintPluginMdx from "eslint-plugin-mdx";
import eslintPluginNoSecrets from "eslint-plugin-no-secrets";
import eslintPluginNoUnsanitized from "eslint-plugin-no-unsanitized";
import eslintPluginNoUseExtendNative from "eslint-plugin-no-use-extend-native";
import eslintPluginPackageJson from "eslint-plugin-package-json";
import eslintPluginPerfectionist from "eslint-plugin-perfectionist";
import eslintPluginPrettierRecommended from "eslint-plugin-prettier/recommended";
import eslintPluginPromise from "eslint-plugin-promise";
Expand Down Expand Up @@ -53,7 +54,6 @@ export const CONFIGS = {
},
rules: {
"@stylistic/multiline-comment-style": "off", // Multiple bugs with this rule
// "import-x/order": "off",
"max-params": ["warn", 4],
"no-console": "warn",
"no-debugger": "warn",
Expand Down Expand Up @@ -124,6 +124,9 @@ export default defineConfig([
...eslintJson.configs.recommended,
language: "json/json",
},
eslintPluginPackageJson.configs.recommended,
eslintPluginPackageJson.configs.stylistic,
eslintPluginPackageJson.configs["recommended-publishable"],
{
files: ["package.json"],
languageOptions: {
Expand Down
48 changes: 48 additions & 0 deletions codex/librarian/fs/filters.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""Filter files with regexes."""

import re
from contextlib import suppress
from pathlib import Path

from comicbox.box import Comicbox
Expand All @@ -9,6 +10,53 @@
from codex.models.paths import CustomCover
from codex.settings import CUSTOM_COVERS_DIR, CUSTOM_COVERS_GROUP_DIRS

# Component-level ignore registry consulted by the poller's walker and
# the watchfiles filter. Either constant can be extended to add new
# patterns without touching the walker/filter call sites:
#
# _IGNORED_BASENAMES — exact-match basenames (case-sensitive). Use
# this for OS / archive metadata trees that don't follow a prefix
# convention. Examples for future extension: ``"@eaDir"``
# (Synology), ``"__MACOSX"`` (Mac archive metadata), ``"Thumbs.db"``
# / ``"desktop.ini"`` (Windows), ``"#recycle"`` (recycle bins).
#
# _IGNORED_BASENAME_PREFIXES — prefix matches. Currently catches all
# hidden files / directories ("." prefix) so VCS metadata (.git),
# macOS spotlight (.Spotlight-V100), trash (.Trashes), and stray
# dotfiles (.DS_Store, .nomedia) are skipped wholesale.
#
# A path is ignored when *any* component (relative to its library
# root) matches either rule. The check is applied to every entry the
# walker sees and every event the watcher receives.
_IGNORED_BASENAMES: frozenset[str] = frozenset()
_IGNORED_BASENAME_PREFIXES: tuple[str, ...] = (".",)


def is_ignored_basename(name: str) -> bool:
"""Return True when ``name`` matches any registered ignore rule."""
return name in _IGNORED_BASENAMES or any(
name.startswith(prefix) for prefix in _IGNORED_BASENAME_PREFIXES
)


def is_ignored_path(path: Path | str, root: Path | str | None = None) -> bool:
"""
Return True when any component (relative to ``root``) is ignored.

The check is component-by-component so a hidden ancestor
(``.git/HEAD``) is caught even when the basename itself is
innocuous. When ``root`` is provided, components of the root are
skipped — a library whose path lives under a hidden parent (e.g.
``/Users/aj/.archive/comics``) still polls its contents.
"""
ppath = path if isinstance(path, Path) else Path(path)
rel = ppath
if root is not None:
with suppress(ValueError):
rel = ppath.relative_to(root)
return any(is_ignored_basename(part) for part in rel.parts)


_IMAGE_REGEX = r"\.(jpe?g|webp|png|gif|bmp)"
_IMAGE_MATCHER: re.Pattern = re.compile(_IMAGE_REGEX, re.IGNORECASE)

Expand Down
9 changes: 9 additions & 0 deletions codex/librarian/fs/poller/snapshot.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from django.db.models import Model

from codex.librarian.fs.filters import (
is_ignored_basename,
match_comic,
match_folder_cover,
match_group_cover_image,
Expand Down Expand Up @@ -121,6 +122,14 @@ def _init_walk(self):
def _walk(self, root: str) -> None:
"""Walk the directory tree and populate lookups."""
for entry in os.scandir(root):
# Walking only the surviving children means the recursion
# never enters an ignored dir (``.git`` / ``.Trashes`` /
# ``@eaDir`` if registered / …), so a per-component check
# at each scandir level suffices — no need to re-check
# ancestors. See ``filters._IGNORED_BASENAMES`` /
# ``_IGNORED_BASENAME_PREFIXES`` for the registered rules.
if is_ignored_basename(entry.name):
continue
path = Path(entry.path)
is_dir = entry.is_dir(follow_symlinks=self._follow_symlinks)
if is_dir:
Expand Down
10 changes: 9 additions & 1 deletion codex/librarian/fs/watcher/dirs.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

from codex.librarian.fs.events import FSChange, FSEvent
from codex.librarian.fs.filters import (
is_ignored_basename,
match_comic,
match_folder_cover,
match_group_cover_image,
Expand Down Expand Up @@ -42,8 +43,15 @@ def expand_dir_added(
if not root.is_dir():
return
count = 0
for dirpath, _dirnames, filenames in os.walk(root):
for dirpath, dirnames, filenames in os.walk(root):
# Prune ignored directories in place so ``os.walk`` never
# descends into them under a freshly-added tree. Rules come
# from the central registry in ``filters`` — extend that
# module to add more patterns.
dirnames[:] = [d for d in dirnames if not is_ignored_basename(d)]
for filename in filenames:
if is_ignored_basename(filename):
continue
file_path = Path(dirpath) / filename
if event := _classify_added_file(file_path, covers_only=covers_only):
batch.added.append((library_pk, event))
Expand Down
42 changes: 30 additions & 12 deletions codex/librarian/fs/watcher/watcher.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from watchfiles import Change, watch

from codex.librarian.fs.filters import (
is_ignored_path,
match_comic,
match_folder_cover,
match_group_cover_image,
Expand All @@ -29,27 +30,37 @@
class CodexWatchFilter:
"""Watchfiles watcher class for both types of library."""

def __init__(self, covers_only_paths: set[str]):
"""Set covers_only_paths."""
def __init__(self, library_paths: set[str], covers_only_paths: set[str]):
"""Set library and covers_only paths."""
self._library_paths = library_paths
self._covers_only_paths = covers_only_paths

def _library_root_for(self, ppath: Path) -> str | None:
"""Return the watched library root containing ``ppath``, if any."""
for lib_path in self._library_paths:
if ppath.is_relative_to(lib_path):
return lib_path
return None

def __call__(self, change: Change, path: str) -> bool:
"""
Filter method.

Deleted paths can't be inspected on disk, so let them all through;
event processing filters by DB lookup and suffix matching instead.
Deleted paths can't be inspected on disk, but the ignore-path
check runs purely on the path string so events under registered
ignore patterns are suppressed without a stat. Non-deleted
events also fall through to the suffix / cover predicates that
the poller mirrors.
"""
ppath = Path(path)
lib_root = self._library_root_for(ppath)
if lib_root is None or is_ignored_path(ppath, root=lib_root):
return False

if change == Change.deleted:
return True

ppath = Path(path)
covers_only = False
for covers_only_path in self._covers_only_paths:
if ppath.is_relative_to(covers_only_path):
covers_only = True
break

covers_only = lib_root in self._covers_only_paths
if covers_only:
return match_group_cover_image(ppath)
return ppath.is_dir() or match_comic(ppath) or match_folder_cover(ppath)
Expand Down Expand Up @@ -161,7 +172,6 @@ def _get_extant_paths(self, paths: list[str]) -> list[str]:

def _watch_loop(self) -> None:
"""Run the watchfiles loop, restarting when paths change."""
watch_filter = CodexWatchFilter(self._covers_only_paths)
while not self._shutdown_event.is_set():
self._restart_event.clear()
paths = list(self._library_paths.keys())
Expand All @@ -170,6 +180,14 @@ def _watch_loop(self) -> None:
self._restart_event.wait(timeout=5.0)
continue
extant_paths = self._get_extant_paths(paths)
# Built per iteration so a ``restart()`` from
# ``_update_paths_from_db`` (which rebinds the path sets)
# immediately propagates new libraries / covers-only flags
# into the filter — the previous outer-scope binding froze
# the filter to the path sets present at process startup.
watch_filter = CodexWatchFilter(
set(self._library_paths.keys()), self._covers_only_paths
)
try:
for changes in watch(
*extant_paths,
Expand Down
22 changes: 13 additions & 9 deletions codex/templates/pwa/serviceworker.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,12 @@ self.addEventListener("install", (event) => {
self.addEventListener("activate", (event) => {
event.waitUntil(
Promise.all([
// Pair with skipWaiting() so the new SW takes control of
// already-open pages immediately. Without this, replacing a
// stale SW (e.g. one with a stale CSP) would need a second
// reload to take effect.
/*
* Pair with skipWaiting() so the new SW takes control of
* already-open pages immediately. Without this, replacing a
* stale SW (e.g. one with a stale CSP) would need a second
* reload to take effect.
*/
self.clients.claim(),
caches.keys().then((cacheNames) => {
return Promise.all(
Expand All @@ -39,11 +41,13 @@ self.addEventListener("activate", (event) => {
});
// Serve from Cache
self.addEventListener("fetch", (event) => {
// Pass through non-GET and cross-origin requests so the browser
// handles them under the page's CSP rather than the SW's snapshot
// taken at install time. Vite HMR talks to a separate origin
// (5173) and would otherwise be blocked by a stale SW CSP that
// pre-dates the dev-only overlay.
/*
* Pass through non-GET and cross-origin requests so the browser
* handles them under the page's CSP rather than the SW's snapshot
* taken at install time. Vite HMR talks to a separate origin
* (5173) and would otherwise be blocked by a stale SW CSP that
* pre-dates the dev-only overlay.
*/
const url = new URL(event.request.url);
if (event.request.method !== "GET" || url.origin !== self.location.origin) {
return;
Expand Down
38 changes: 31 additions & 7 deletions codex/views/browser/annotate/order.py
Original file line number Diff line number Diff line change
Expand Up @@ -227,15 +227,39 @@ def _annotate_page_count(self, qs):
return qs

def _annotate_bookmark_updated_at(self, qs) -> QuerySet:
if self.is_opds_acquisition or self.order_key == "bookmark_updated_at":
bmua_agg = self.get_max_bookmark_updated_at_aggregate(
qs.model, agg_func=self.order_agg_func
)
# Aggregate triggers:
# - OPDS acquisition needs the per-entry "last read" timestamp.
# - Sorting by ``bookmark_updated_at`` needs it as the order key.
# - Group rows in table view display it as a column; the cell
# display path (``_emit_column`` → ``getattr``) reads the
# annotation directly, so without this branch the column would
# crash ``compute_group_intersections`` (the field can't be
# aggregated as a Comic-relative scalar without the user
# filter). Comic rows keep going through
# ``annotate_comic_extra_specials`` to avoid double-annotation.
primary = self.is_opds_acquisition or self.order_key == "bookmark_updated_at"
table_column = (
qs.model is not Comic
and self.params.get("view_mode") == "table"
and self.order_key != "bookmark_updated_at"
)
if not primary and not table_column:
return qs
agg_func = self.order_agg_func if primary else Max
bmua_agg = self.get_max_bookmark_updated_at_aggregate(
qs.model, agg_func=agg_func
)
if primary:
# `self.bmua_is_max` is read by `annotate.bookmark` to skip a
# second aggregate, and by the serializer to compute mtime.
self.bmua_is_max = self.order_agg_func is Max
qs = qs.annotate(bookmark_updated_at=bmua_agg)
return qs
# Only set in the primary branch — that path annotates both
# group and Comic querysets, so the scalar exists on every
# serialized row. The table-column branch annotates only
# group rows; flipping the flag here would make
# ``get_mtime`` reach for a missing ``bookmark_updated_at``
# on Comic books in the same response.
self.bmua_is_max = agg_func is Max
return qs.annotate(bookmark_updated_at=bmua_agg)

def _annotate_search_scores(self, qs, *, for_cover: bool = False):
"""Annotate Search Scores."""
Expand Down
6 changes: 5 additions & 1 deletion codex/views/browser/intersections.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,11 @@ def _format_identifier(source_name: str | None, id_type: str, key: str) -> str:
"metadata_mtime": "metadata_mtime",
"created_at": "created_at",
"updated_at": "updated_at",
"bookmark_updated_at": "bookmark_updated_at",
# ``bookmark_updated_at`` is intentionally absent: it's not a
# Comic field but a per-user-filtered ``Max(bookmark__updated_at)``
# aggregate. The annotate pipeline attaches it directly to group
# rows in table view (see ``_annotate_bookmark_updated_at``); the
# cell display then falls through to ``getattr`` in ``_emit_column``.
# FK-to-name columns (Comic FK → related model.name).
"country": "country__name",
"language": "language__name",
Expand Down
Loading