[2026-09-21] eagle: family/plans/watchlist-disk-overflow-fix.md family/projects/watchlist-automation.md
This commit is contained in:
1 parent
a74d6691ec
commit
f9e8206b4d
2 files changed
+240
-36
No files matched your search
@@ -566,19 +566,139 @@ crontab -l | grep -c "watchlist-nightly\|watchlist-discover" # → 0 (пров
|
||||
|
||||
### 🔴 Незакрытые дефекты скрипта (моя ответственность)
|
||||
|
||||
| # | Дефект | Следствие |
|
||||
|---|---|---|
|
||||
| 1 | Нет проверки свободного места перед `sync-down` | Radarr качает, пока диск не кончится |
|
||||
| 2 | Нет лимита размера файла | Один фильм = 60–100 ГБ |
|
||||
| 3 | Нет стоп-крана при заполнении > порога | Нет автоматической остановки |
|
||||
| 4 | `cleanup` не видит `/media/movies` | Просмотренное не удаляется, диск не освобождается |
|
||||
|
||||
**План ремонта (согласован):** сначала Фаза 1 — `MIN_FREE_GB` в скрипт (главный фикс), потом возврат крона с меньшим `--batch`. Cleanup — отдельно.
|
||||
| # | Дефект | Следствие | Статус |
|
||||
|---|---|---|---|
|
||||
| 1 | Нет проверки свободного места перед `sync-down` | Radarr качает, пока диск не кончится | ✅ исправлено 2026-09-21 |
|
||||
| 2 | Нет лимита размера файла | Один фильм = 60–100 ГБ | ⚠️ Alex считает лимит качества достаточным — не делаем |
|
||||
| 3 | Нет стоп-крана при заполнении > порога | Нет автоматической остановки | ✅ = п.1 |
|
||||
| 4 | `cleanup` не видит `/media/movies` | Просмотренное не удаляется, диск не освобождается | ⏸ отдельная задача |
|
||||
| 5 | `mark_queued` молча проваливается | часть фильмов без метки `⬇️` | ✅ исправлено 2026-09-21 |
|
||||
|
||||
**Правило на будущее:** любой скрипт, ставящий закачки в очередь, ОБЯЗАН проверять свободное место **до** добавления. Лимита качества недостаточно — он ограничивает тип релиза, но не размер.
|
||||
|
||||
---
|
||||
|
||||
## ✅ 2026-09-21 (продолжение): Фазы 1–4 РЕМОНТА ВЫПОЛНЕНЫ — коммит `2642882`
|
||||
|
||||
> Полный план и разбор — [[family/plans/watchlist-disk-overflow-fix]].
|
||||
> Ниже — операционная выжимка для будущих сессий.
|
||||
|
||||
**Коммит:** `2642882 fix(sync-down): MIN_FREE_GB guard, horror filter (star overrides), reliable mark_queued`
|
||||
Предыдущий HEAD — `aafbaf7`. Рабочее дерево чистое.
|
||||
|
||||
### Фаза 1 — `MIN_FREE_GB` (стоп-кран по месту)
|
||||
|
||||
```python
|
||||
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).
|
||||
⚠️ Питфолл из [[watchlist-cleanup]]: `/media` — **не медиа-диск**, а пустая папка на SD-карте. Учитывать при задании `WATCHLIST_MEDIA_ROOT`.
|
||||
|
||||
### Фаза 2 — Фильтр ужасов + `⭐ > 🧟`
|
||||
|
||||
Расширен существующий фильтр (был только рейтинговый), точка — бывший `sync_down.py:54`:
|
||||
|
||||
| Условие | Действие |
|
||||
|---|---|
|
||||
| есть `⭐` (U+2B50) | качаем **всегда** — `continue` до всех фильтров |
|
||||
| `candidate.is_horror()` без `⭐` | **пропускаем** |
|
||||
| рейтинг < `MIN_RATING` (6.0) без `⭐` | пропускаем |
|
||||
|
||||
Счётчик `skipped_horror`, в конце лога: `(N horror item(s) skipped; add ⭐ to force)`.
|
||||
|
||||
⚠️ **`is_horror()` работает по жанрам TMDB, не по эмодзи.** `genres` — список int-id, `27` = Horror.
|
||||
Поэтому отсекается **33** (dry-run), хотя меток `🧟♀️` в файле 41 — часть строк не резолвится в TMDB.
|
||||
В `.resolve_cache.json` **51 запись содержит genre id 27**. Проверка:
|
||||
```bash
|
||||
jq -r "[to_entries[] | select((.value.genres // .value.candidate.genres // []) | index(27))] | length" .resolve_cache.json
|
||||
```
|
||||
|
||||
⚠️ `⭐` (U+2B50) ≠ `✭` (U+272D). Приоритет — только `⭐`.
|
||||
|
||||
**Dry-run подтверждён:**
|
||||
```
|
||||
free space on /home/kraken/obsidian/family/documents: 403.4 GiB (threshold 100 GiB)
|
||||
⭐ 1 priority item(s) ['Простите за беспокойство 2018']
|
||||
(14 item(s) below rating 6.0 skipped)
|
||||
(33 horror item(s) skipped; add ⭐ to force)
|
||||
Queued 5/5 items [dry-run]
|
||||
```
|
||||
|
||||
### Фаза 3 — Надёжный `mark_queued`
|
||||
|
||||
`parser.py::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. Проверка на копии боевого файла: строки 81/82/83 получили метку, 677 строк → 677.
|
||||
|
||||
### Фаза 4 — `unmark_queued()` добавлена и применена
|
||||
|
||||
`unmark_queued()` в `parser.py` — инверсия `mark_queued`, тоже возвращает `bool`, поглощает эмодзи-префикс
|
||||
с обеих сторон маркера. Применено на боевом файле:
|
||||
```
|
||||
107: - [x] ⬇️ Неистовый → - [x] Неистовый
|
||||
396: - [ ] ⬇️ Список Шиндлера → - [ ] Список Шиндлера
|
||||
```
|
||||
**Тесты:** unmark 5/5 + round-trip `mark → unmark → исходная строка` ✅
|
||||
|
||||
⚠️ **Не «восстанавливать» `⬇️` после удаления файла** — метка = «добавлен в Radarr»; после
|
||||
удаления + `monitored=false` она врёт и блокировала бы повторную постановку.
|
||||
|
||||
### 🔴 Открытые баги в `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'` → `'🧟♀️ midsommar 2019'` (эмодзи остаётся в `title`). Лечится формой `^(?:[<символы>]+\s*)+` |
|
||||
|
||||
**Почему важно:** грязный `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]`**. Исправлено симметрично
|
||||
(`/tmp/wlsync/sync_up.py`), тесты 5/6 (падение на `Babylon (2022)` — баг A, не от правки).
|
||||
|
||||
⚠️ **На Kraken НЕ залито, в git НЕ закоммичено.** На момент правки строк с `⬇️` + эмодзи-префиксом
|
||||
в файле **не было ни одной** — баг проявился бы **после первого боевого прогона** `sync-down`
|
||||
(правка Фазы 3 создаёт именно такой формат). Чинить синхронно.
|
||||
|
||||
### Бэкапы этой сессии
|
||||
```
|
||||
~/backup-movies-watchlist-20260921-150759.md (перед удалением remux)
|
||||
~/backup-radarr-unmonitor-20260921-151117.json (состояние Radarr до unmonitor)
|
||||
~/backup-wlsync-20260921-151855/ (sync_down.py + parser.py до правок)
|
||||
~/backup-movies-watchlist-unmark-20260921-152018.md (перед снятием ⬇️)
|
||||
~/crontab-backup-20260921.txt (crontab до остановки)
|
||||
```
|
||||
|
||||
### Скрипты сессии (на Кракене `/tmp/`, теряются при перезагрузке)
|
||||
`rm_remux.sh` (удаление 5 remux), `unmonitor_remux.sh` (снятие monitored + RescanMovie),
|
||||
`commit_wlsync.sh`, `test_marker_real.sh` (тест mark_queued на копии боевого файла),
|
||||
`fase4_unmark.sh` (снятие ⬇️).
|
||||
|
||||
---
|
||||
|
||||
## Cron Jobs (на Кракене, восстановлены 2026-09-20, дополнены 2026-09-21)
|
||||
|
||||
Весь watchlist pipeline работает на Кракене (мигрировал с Eagle 2026-05-27). **Крон был мёртв с 2026-06-20 по 2026-09-20, восстановлен** (детали в начале документа).
|
||||
@@ -709,11 +829,14 @@ Jellyfin library mapping: Movies → `movies`, Cartoons → `mixed` (movies + ep
|
||||
⚠️ Питфолл: `/media` — **не медиа-диск**, а пустая папка на SD-карте (`/dev/mmcblk0p2`), отдаёт 33.9%. Реальный диск `/dev/sda1` в `/srv/dev-disk-by-uuid-6194539b-.../`. Использовать `os.statvfs`, не `shutil.disk_usage("/media")`.
|
||||
- ✅ **[2026-09-21] ДИСК ОСВОБОЖДЁН: 100% → 77%, свободно 404 ГБ.** Удалены 5 remux-файлов (403 ГБ); в Radarr снят `monitored` у id 4/18/35/36/37 (`hasFile=false`) — иначе он перекачал бы всё заново. Radarr API ожил (49 фильмов). Бэкапы: `~/backup-movies-watchlist-20260921-150759.md`, `~/backup-radarr-unmonitor-20260921-151117.json`.
|
||||
- ⏹ **[2026-09-21] `watchlist-nightly` + `watchlist-discover` ОСТАНОВЛЕНЫ** (диск был 100%, `sync-down --batch 5` без проверки места). Бэкап crontab: `~/crontab-backup-20260921.txt`. **Вернуть только после Фаз 1–2, с `--batch 2`.** Активен пока только `watchlist-cleanup` (02:00).
|
||||
- 🔴 **[2026-09-21] ФАЗА 2 РЕМОНТА (главный фикс): `MIN_FREE_GB` в `sync-down`.** Стоп-кран: если свободно < порога — не добавлять в Radarr. Порог **не согласован** (предложение 100 ГБ, свободно 404). Без этого возврат крона бессмыслен. Лимит качества Alex считает уже настроенным — **новый не делать**.
|
||||
- 🔴 **[2026-09-21] ФАЗА 1 (не начата): фикс `mark_queued` + `unmark_queued`.** `parser.py:77` — regex не учитывает эмодзи-префикс (`- [ ] 🧟♀️ midsommar 2019`), при провале **молча не пишет**. Нужно: опциональные маркеры в паттерне, возврат `bool`, проверка результата в `sync_down.py` (строки 98/104/113/122). Плюс `unmark_queued()` для снятия `⬇️` (у Шиндлера 396 и Неистового 107).
|
||||
- 🔴 **[2026-09-21] ФАЗА 3 (не начата): фильтр ужасов + `⭐ > 🧟`.** Точка правки — `sync_down.py:54` (расширить существующий фильтр рейтинга). Правило Alex: ужас + `⭐` → качаем; ужас без `⭐` → пропускаем. ⚠️ **Под фильтр попадает 41 строка `🧟♀️`** (включая свежие релизы 2025–2026) — масштаб подтверждён Alex.
|
||||
- ✅ **[2026-09-21] ФАЗА 1 РЕМОНТА ВЫПОЛНЕНА: `MIN_FREE_GB = 100.0` в `sync-down`** — коммит `2642882`. Проверка через `shutil.disk_usage` (НЕ `df`, врёт на ~4%), путь из env `WATCHLIST_MEDIA_ROOT` иначе `watchlist.parent`. Печатает `[STOP] free space ... < 100 GiB — nothing queued` и выходит. Подтверждено dry-run: `403.4 GiB (threshold 100 GiB)`. Полный план — [[family/plans/watchlist-disk-overflow-fix]].
|
||||
- ✅ **[2026-09-21] ФАЗА 2 ВЫПОЛНЕНА: фильтр ужасов + `⭐ > 🧟`** — коммит `2642882`. Точка — бывший `sync_down.py:54`. `⭐` обходит **все** фильтры (`continue` до проверок); ужас без `⭐` пропускается; рейтинг < 6.0 пропускается. Dry-run: **33 horror skipped**, 14 по рейтингу. ⚠️ `is_horror()` считает по жанрам TMDB (id 27), не по эмодзи — поэтому 33, а не 41.
|
||||
- ✅ **[2026-09-21] ФАЗА 3 ВЫПОЛНЕНА: надёжный `mark_queued`** — коммит `2642882`. Возвращает `bool`, поглощает эмодзи-префикс (`- [ ] ⬇️ 🧟♀️ Title`), обёртка `_mark()` в `sync_down.py` логирует провал `[!]`. Тесты 7/7 + проверка на копии боевого файла. **Корень потери 3 меток `⬇️`.**
|
||||
- ✅ **[2026-09-21] ФАЗА 4 ВЫПОЛНЕНА: `unmark_queued()` добавлена, `⬇️` снят** — у Шиндлера (396) и Неистового (107). Функции в коде **не было**. Тесты unmark 5/5 + round-trip. Бэкап `~/backup-movies-watchlist-unmark-20260921-152018.md`.
|
||||
- 🔴 **[2026-09-21] ФАЗА 5: ВЕРНУТЬ КРОН — ждёт.** `watchlist-nightly` (01:00) + `watchlist-discover` (вс 09:00) остановлены, бэкап `~/crontab-backup-20260921.txt`. Возвращать с **`--batch 2`** (не 5) и **только после проверки Фаз 1–2 в бою** (сначала dry-run).
|
||||
- 🔴 **[2026-09-21] ОТКРЫТЫЕ БАГИ `_clean()` — решение Alex НЕ принято.** Баг **A** (`parser.py:47`): `(?:/\d{4})+` требует 2+ варианта года → одиночный `(2022)` не срезается, уходит в `resolve()`. Баг **B** (`parser.py:19-38`): `_EMOJI_PREFIX` заканчивается на `\s*` → берёт только первый эмодзи, `🧟♀️` остаётся в `title`. Оба **существовали до правок** (проверено на старом коде), не регрессия. Лечение B — форма `^(?:[<символы>]+\s*)+`.
|
||||
- 🔴 **[2026-09-21] `_mark_watched` в `sync_up.py` — исправлено локально, НЕ залито/НЕ закоммичено.** Тот же класс бага: строка `- [ ] ⬇️ 🧟♀️ Title` не перешла бы в `[x]`. ⚠️ Правка Фазы 3 **создаёт** именно такой формат → чинить синхронно с возвратом крона, иначе `sync-up` перестанет видеть новые закачки.
|
||||
- 🔴 **[2026-09-21] КОРЕНЬ: `[x]` не проставляется на новые закачки.** `sync-up` (Jellyfin watched → `[x]`) видит только 6 старых фильмов из `/media/movies`, ни одного из `/media/movies-radarr`. Пока это не починено, cleanup бесполезен, а диск будет заполняться.
|
||||
- 🔴 **[2026-09-21] ДИСК КРИТИЧЕН: 94.7% / свободно 4.8 ГБ (замер до полного заполнения).** Порог 90% пройден, cleanup реально запускается, но **удаляет 0 файлов**: пересечение `[x]` ∩ Radarr-библиотека = **пусто** (177 просмотренных vs 28 фильмов с файлами).
|
||||
- 🟡 **[2026-09-21] Cleanup не видит `/media/movies`** — смотрит только Radarr-библиотеку, поэтому просмотренное из папки ручных торрентов не удаляется. Расширять — отдельно и осторожно (удаление файлов).
|
||||
- 🟡 **[2026-09-20] `sync-down` берёт всего 5 за прогон** — при 250 кандидатах разбор очереди займёт ~50 ночей. Рассмотреть увеличение `--batch` (сейчас `--batch 5` в `watchlist-nightly.sh`). Особенно актуально, раз фильтр `<6.0` уже отсеял 18.
|
||||
- 🟡 **[2026-09-20] Разобрать 5 застрявших закачек в очереди Radarr** со статусом `warning`: `Brokeback Mountain`, `Мортал Комбат 2`, `Sorry to Bother You`, `Город бога`, `День хомячка`. Висят давно, релиз не находится/не качается.
|
||||
|
||||
Reference in new issue
Block a user