14 KiB
title, created, updated, type, namespace, status, tags, related
| title | created | updated | type | namespace | status | tags | related | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Watchlist — фикс переполнения диска и фильтра ужасов | 2026-09-21 | 2026-09-21 | plan | family | in_progress |
|
|
Watchlist — фикс переполнения диска и фильтра ужасов
Контекст: 2026-09-21 диск
/dev/sda1на Kraken был заполнен на 100% (0 байт свободно). Radarr отдавалdisk I/O error(SQLite не мог писать). Причина —watchlist-syncдобавлял 5 фильмов ежедневно (sync-down --batch 5) без проверки свободного места и без отсечения ужасов. Диск вырос 18 → 28 фильмов за сутки, remux'ы по 57–93 ГБ.
Корневая причина — три дефекта
| # | Дефект | Где | Следствие |
|---|---|---|---|
| 1 | Нет проверки свободного места перед добавлением в Radarr | sync_down.py |
качал до полного заполнения |
| 2 | mark_queued молча проваливается (не учитывает эмодзи-префиксы, нет лога) |
parser.py:77 |
часть фильмов без метки ⬇️ |
| 3 | Нет фильтра по ужасам (метка 🧟♀️ есть, но в sync_down не проверяется) |
sync_down.py:54 |
Midsommar и др. скачались |
Текущее состояние (2026-09-21)
- Диск: 100% → 77%, свободно 404 ГБ
- Radarr: ✅ API жив, 49 фильмов
- Крон
watchlist-nightlyиwatchlist-discover: ЗАГЛУШЕНЫ (бэкап~/crontab-backup-20260921.txt) - Код (
sync_down.py,parser.py): правки закоммичены,2642882 - Бэкапы:
~/backup-movies-watchlist-20260921-150759.md,~/backup-radarr-unmonitor-20260921-151117.json,~/backup-wlsync-20260921-151855/(sync_down.py + parser.py до правок),~/backup-movies-watchlist-unmark-20260921-152018.md
План — 6 фаз
Фаза 0 — Освободить место ✅ ВЫПОЛНЕНО
Удалены 5 remux-файлов (≈403 ГБ), Radarr пересканирован, monitored=false выставлен всем 5:
| Фильм | Размер | Radarr id |
|---|---|---|
| Babylon (2022) | 93 ГБ | 37 |
| Schindler's List (1993) | 90 ГБ | 18 |
| Midsommar (2019) | 86 ГБ | 35 |
| Triangle of Sadness (2022) | 80 ГБ | 36 |
| Unhinged (2020) | 57 ГБ | 4 |
Папки НЕ удалены (там .nfo, постеры).
Скрипт удаления: /tmp/rm_remux.sh на Mac → /tmp/rm_remux.sh на Kraken (удалён при перезагрузке).
Снятие monitored: /tmp/unmonitor_remux.sh; порядок — PUT monitored=false ДО любого поиска,
иначе Radarr скачает файлы заново.
Все 5 фильмов были в watchlist (проверено): стр. 83 Babylon, 81 Midsommar, 82 Triangle,
396 «Список Шиндлера» (по-русски!), 107 Неистовый. ⚠️ Поиск латиницей schindler даёт
ложно-отрицательный результат — «Список Шиндлера» записан по-русски.
Фаза 1 — MIN_FREE_GB (стоп-кран по месту) ✅ ВЫПОЛНЕНО
Файл: watchlist_sync/sync_down.py — коммит 2642882
MIN_FREE_GB = 100.0
def _free_gb(path: Path) -> float:
p = Path(path).resolve()
while not p.exists() and p != p.parent:
p = p.parent
return shutil.disk_usage(p).free / (1024 ** 3)
Проверка стоит в run() сразу после получения Jellyfin-user, перед parse():
при нехватке места печатает [STOP] free space ... < 100 GiB — nothing queued и return.
Путь берётся из env WATCHLIST_MEDIA_ROOT, иначе watchlist.parent.
⚠️ Использовать shutil.disk_usage, не df (df врёт на ~4%, учитывает root-резерв ext4).
Порог: 100 ГБ (согласован Alex).
Проверено dry-run: free space on /home/kraken/obsidian/family/documents: 403.4 GiB (threshold 100 GiB).
Фаза 2 — Фильтр ужасов + приоритет ⭐ > 🧟 ✅ ВЫПОЛНЕНО
Файл: watchlist_sync/sync_down.py (фильтр перед проверкой рейтинга), коммит 2642882
| Условие | Действие |
|---|---|
есть ⭐ (U+2B50) |
качаем всегда — обходит и рейтинг, и ужасы (ветка continue до всех фильтров) |
ужас (candidate.is_horror()) без ⭐ |
пропускаем |
рейтинг < MIN_RATING (6.0) без ⭐ |
пропускаем (как сейчас) |
Счётчик skipped_horror, в конце: (N horror item(s) skipped; add ⭐ to force).
Проверено dry-run: 33 horror item(s) skipped, 14 ниже рейтинга, ⭐ 1 priority item.
⚠️ is_horror() работает по жанрам TMDB (genres = список int-id, 27 = Horror),
не по эмодзи. Поэтому отсекается больше (33), чем помечено 🧟♀️ в списке (41, включая
те, что не резолвятся). В .resolve_cache.json 51 запись содержит genre id 27.
Проверка: jq -r "[to_entries[] | select((.value.genres // .value.candidate.genres // []) | index(27))] | length" .resolve_cache.json
⚠️ ⭐ (U+2B50) ≠ ✭ (U+272D). Это разные символы: ✭ — декоративная звёздочка
в конце названия, срезается _clean(). Приоритет — только ⭐.
Фаза 3 — Фикс mark_queued ✅ ВЫПОЛНЕНО
Файлы: watchlist_sync/parser.py + watchlist_sync/sync_down.py, коммит 2642882
Было:
pattern = re.compile(r'(^\s*-\s*\[ \]\s*)(' + re.escape(title) + r')', re.MULTILINE)
new_text = pattern.sub(r'\1⬇️ \2', text, count=1)
if new_text != text:
path.write_text(new_text) # ← тихий провал, если не совпало
Стало:
mark_queued()возвращаетbool- регексп поглощает опциональный эмодзи-префикс между чекбоксом и названием:
🧟 🗓️ ❤ ⭐ ✭ ❓ ✅ ⬇️ ▶️ ZWJ VS16 ♀️+ пробелы - метка пишется перед префиксом:
- [ ] ⬇️ 🧟♀️ midsommar 2019 - в
sync_down.pyдобавлена обёртка_mark()(4 места вызова), которая приFalseпечатает[!] could not write ⬇️ marker for ... (line not matched — check the watchlist syntax)
Тесты: 7/7 прошли. Негативный случай (несуществующая строка) → False.
Проверка на копии боевого файла: 3 ранее падавшие строки (81, 82, 83) получили метку,
число строк 677 → 677 не изменилось.
Фаза 4 — Снять ⬇️ у удалённых фильмов ✅ ВЫПОЛНЕНО
Добавлена unmark_queued() в parser.py (инверсия mark_queued, тоже возвращает bool,
тоже поглощает эмодзи-префикс с обеих сторон маркера).
Применено на боевом файле:
107: - [x] ⬇️ Неистовый → - [x] Неистовый
396: - [ ] ⬇️ Список Шиндлера → - [ ] Список Шиндлера
677 строк — не изменилось
Бэкап: ~/backup-movies-watchlist-unmark-20260921-152018.md
Тесты: unmark 5/5 + round-trip mark → unmark → исходная строка ✅
⚠️ unmark_queued в коде НЕ существовала (была только unmark_ambiguous).
⚠️ Не «восстанавливать» ⬇️ после удаления — метка означает «добавлен в Radarr»;
после удаления файла + monitored=false она врёт и блокировала бы повторную постановку.
Фаза 5 — Вернуть крон ⏳ ОЖИДАЕТ
0 1 * * * bash ~/scripts/watchlist-nightly.sh
С --batch 2 вместо 5 (watchlist-nightly.sh).
Возвращать только после проверки Фаз 1–2 в бою (сначала dry-run).
🔴 Открытые баги, найденные при работе (НЕ исправлены)
Обнаружены в watchlist_sync/parser.py::_clean(). Существовали до правок 2026-09-21,
проверены на старом коде — не регрессия. Решение Alex по ним ещё не принято.
| # | Баг | Строка | Эффект |
|---|---|---|---|
| A | re.sub(r'\s*\(\d{4}(?:/\d{4})+\)\s*$', ...) — квантификатор + после группы требует 2+ варианта года. Одиночный год не срезается. |
parser.py:47 |
_clean('⬇️ Babylon (2022)') → 'Babylon (2022)'. Год уходит в resolve() (запрос в TMDB) и в mark_queued. |
| B | _EMOJI_PREFIX = ^[<символы>]+\s* — заканчивается на \s*, поэтому захватывает только первый эмодзи. |
parser.py:19-38 |
'⬇️ 🧟♀️ midsommar 2019' → после sub → '🧟♀️ midsommar 2019' (эмодзи остаётся в title). Лечится формой ^(?:[<символы>]+\s*)+. |
Проверка A: _clean('⬇️ Babylon (2022)') → 'Babylon (2022)' (ожидалось 'Babylon').
Проверка B: _EMOJI_PREFIX.match('⬇️ 🧟♀️ midsommar 2019').group(0) → '⬇️ ',
остаток '🧟♀️ midsommar 2019'.
Почему это важно: item.title с эмодзи/годом попадает в resolve() (TMDB/KP запрос),
в _normalize() при sync_up (не сойдётся с названием из Jellyfin) и в mark_queued.
Фикс B частично компенсирован правкой Фазы 3 (регексп поглощает префикс), но title
всё равно уходит грязным в resolve.
Фаза 3.5 — _mark_watched в sync_up.py (исправлено, НЕ залито/НЕ закоммичено)
Тот же класс бага: регексп r'\s*-\s*\[ \]\s*⬇️?\s*(.*)' не пропускал эмодзи-префикс,
поэтому строка - [ ] ⬇️ 🧟♀️ midsommar 2019 никогда не перешла бы в [x]
(Jellyfin → watchlist). Исправлено симметрично (локально /tmp/wlsync/sync_up.py),
тесты 5/6 (падение на Babylon (2022) — это баг A, не мой).
⚠️ На Kraken НЕ залито, в git НЕ закоммичено.
⚠️ На момент правки строк с ⬇️ + эмодзи-префиксом в файле не было ни одной
(всего ⬇️ — 127, из них [ ] ⬇️ — 59). Баг проявился бы после первого боевого прогона
sync-down, то есть моя правка Фазы 3 его бы создала. Поэтому чинить синхронно.
Порядок и правила
- Правки в боевых файлах: коммит ДО → правка → заливка → ПРОВЕРКА АЛЕКСОМ → коммит ПОСЛЕ
- Проверка каждой фазы обязательна
- Ничего не удалять без явного апрува
- Не гадать про содержимое файла по одному grep — «Список Шиндлера» по-русски не нашёлся латиницей, из-за чего был сделан ложный вывод «фильма нет в списке»
Факты по меткам (2026-09-21)
| Метка | Символ | Кол-во строк | Смысл |
|---|---|---|---|
⭐ |
U+2B50 | 1 («Простите за беспокойство ⭐ 2018») | приоритет, обходит все фильтры |
✭ |
U+272D | 4 | декоративная звёздочка в конце названия, НЕ приоритет |
🧟♀️ |
U+1F9DF U+200D U+2640 U+FE0F | 41 | ужасы |
⬇️ |
U+2B07 U+FE0F | 127 (59 как [ ] ⬇️ ) |
добавлен в Radarr |
⚠️ Пересечений ⭐ + 🧟♀️ сейчас НЕТ. После включения фильтра 41 ужас перестанет качаться
(многие — свежие релизы 2025–2026). Решение Alex: это ожидаемое поведение.
⚠️ 🧟♀️ — один составной эмодзи (U+1F9DF + ZWJ + U+2640 + VS16), выглядит как две метки,
но это одна группа. Не путать с двумя отдельными маркерами.
Связанные заметки
- watchlist-automation — полный flow pipeline
- watchlist-cleanup — авто-удаление просмотренного
- arr-stack-kraken — Radarr/Sonarr/Prowlarr