Распознаём ручное удаление источника (раздача в qBittorrent) и/или цели (разложенные хардлинки) и отражаем его в состоянии задачи, без автодействий. - Новая capability state-reconciliation (OpenSpec): фоновая сверка по матрице «источник × цель» → состояния target_missing/orphaned/deleted, переходы и самовосстановление (healing). - worker: reconcileDesync в Poll (только разложенные/desync-задачи), дебаунс пропажи источника (порог [worker].source_missing_threshold) и синхронный preflight перед действиями (relink/recognize/apply/undo) — не доверяем state в БД. - layout.Undo: отказ снять последнюю копию (nlink<=1 или нет источника), отказ всего батча без частичного отката (ErrLastCopy). - store: единый список terminalStates для IsTerminal и FindActiveByInfohash (иначе семантика «активности» разъезжается), столбец source_miss_count, миграция 0003. - httpapi/web и Telegram: показ новых состояний и уведомления о рассинхроне. - Доки: workflow.md, jellyfin-layout.md, database.md (+0003), config. Change заархивирован в openspec/changes/archive, дельта влита в openspec/specs. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
5.3 KiB
Конвенции раскладки Jellyfin
Целевые имена и структура, в которые jellybit раскладывает файлы хардлинками. Источники: Movies, Shows.
Фильмы
movies/
Дюна Часть вторая (2024) [tmdbid-693134]/
Дюна Часть вторая (2024).mkv
Дюна Часть вторая (2024).ru.srt
- Папка и файл —
Название (Год). - provider-id в имени папки (
[tmdbid-...]) добавляется при работе с базой — снимает неоднозначность для русских названий, которые Jellyfin иначе может опознать неверно. - Внешние субтитры —
Имя.<lang>[.flag].srt(флагиforced/sdh/default/hi), напр.…ru.forced.srt; база имени совпадает с именем видеофайла. Пары VobSub —.idx+.sub.
Сериалы
series/
Название (2024) [tvdbid-123456]/
Season 01/
Название (2024) S01E01.mkv
Название (2024) S01E02.mkv
- provider-id — на папке сериала.
- Сезоны —
Season 01, файлы —... SxxEyy.
Сопоставление источник → цель
Источник берём по пути из qBittorrent (save_path + относительное имя
файла из /torrents/files, которое уже содержит корневую папку
многофайловой раздачи; это уже хост-путь, path_map — фолбэк). Для каждого
распознанного файла (не каталога) создаётся хардлинк в
paths.movies/paths.series; целевые каталоги — mkdir (0755,
1000:1000). Исходный файл остаётся на месте (раздача продолжается),
inode общий — диск не дублируется.
Целевое имя строится из распознанных полей и санитизируется (без
разделителей пути, .., управляющих символов); финальный путь обязан
быть строго под библиотекой. Существующую цель не перезаписываем (тот
же inode → готово; другой файл → коллизия → review). Инварианты и undo —
в architecture.md → «Раскладка файлов».
Желательно: целевой и исходный каталоги — на одной ФС/одном mount'е
(внутри контейнера это обеспечивает единая песочница /srv/media), тогда
работает дешёвый хардлинк. Если хардлинк невозможен (разные ФС или ФС без
поддержки жёстких ссылок), layout не падает, а копирует файл с
предупреждением в лог — см. architecture.md → «Раскладка файлов».
Безопасный undo (не снимать последнюю копию)
Undo снимает лишний хардлинк, а не единственный файл. Перед удалением
батча layout проверяет каждую цель: если исходный файл уже не существует
или у цели не осталось других жёстких ссылок (nlink <= 1), это —
последняя копия данных, и весь Undo отклоняется целиком (ошибка
ErrLastCopy), не сняв ни одной ссылки (частичный откат тоже стёр бы часть
данных). Так нарушенный инвариант «источник неприкосновенен» (источник
удалён вручную) не приводит к потере данных. Отсутствующую цель Undo
пропускает как уже снятую (идемпотентность). Связь с состояниями
рассинхрона — workflow.md → «Сверка с реальностью».
Крайние случаи
- Многофайловый фильм (части) — стэкинг по точному токену Jellyfin
(
… - part1/cd1); точный формат уточнить при реализации. - Редакции —
Имя (Год) [edition-Director's Cut]либо отдельные версии в папке фильма. - Двойная серия в одном файле —
… SxxEyy-Eyy. - Спецвыпуски —
Season 00. - Сезон-пак — серии в один
Season xx; смешанный пак — по per-file сезонам. - Несколько аудиодорожек — обычно внутри mkv, не наша забота.
- Аниме с абсолютной нумерацией — пересчёт в S·E, отдельная проработка (drafts/ideas.md).