Files
jellybit/docs/specs/jellyfin-layout.md
T
avandClaude Opus 4.8 6b7c090ce4 Владение целевым путём при повторной раскладке (state-reconciliation)
Завершённая загрузка ложно «воскресала» из deleted в orphaned, когда её
целевой путь переиспользовала другая загрузка (повторная закачка того же
фильма в другом качестве): сверка проверяла лишь существование пути, не
проверяя, что файл по нему — наша раскладка.

Вводим инвариант «один целевой путь — один владелец»:

- при успешной раскладке на освободившийся чужой путь владение переходит
  к новой загрузке — прежние file_link на этот путь помечаются статусом
  superseded и перестают считаться целью при сверке;
- deleted исключён из desyncStates — терминальное состояние больше не
  переоценивается (источник к нему не вернётся из-за идемпотентности,
  цель отбирается переходом владения);
- Undo снимает только реально свои разложенные ссылки (superseded
  пропускает — файл по пути теперь чужой хардлинк);
- ошибку перехода владения трактуем как некритичную (WARN-and-continue):
  файлы уже разложены, рассинхрон чужих задач исправит следующий тик.

Без миграции схемы (status — TEXT). Дельта влита в основную спеку,
обновлены workflow.md и jellyfin-layout.md.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 18:10:05 +03:00

6.5 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 → «Раскладка файлов».

Владение целевым путём

Целевой путь принадлежит одной загрузке. Когда новая раскладка успешно ложится на путь, который раньше занимала другая загрузка (путь к этому моменту свободен — иначе была бы коллизия → review, чужой файл не перезаписываем), владение переходит к новой загрузке: прежние записи file_link на этот путь помечаются статусом superseded и перестают считаться целью прежней загрузки. Это нужно сверке с реальностью: иначе повторная закачка того же фильма (например, в другом качестве) по тому же пути ложно «воскрешала» бы удалённую задачу — см. workflow.md → «Сверка с реальностью». superseded-ссылки не считаются целью при сверке и не снимаются в Undo.

Желательно: целевой и исходный каталоги — на одной ФС/одном 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).