202 lines
7.5 KiB
Markdown
202 lines
7.5 KiB
Markdown
---
|
|
title: Media Pipeline — File Routing & Directory Policies
|
|
aliases:
|
|
- "Медиа пайплайн роутинг"
|
|
- "Media Pipeline file routing"
|
|
- "file routing policies"
|
|
created: '2026-05-17'
|
|
updated: '2026-05-24'
|
|
status: planning
|
|
tags:
|
|
- media-pipeline
|
|
- file-routing
|
|
- transmission
|
|
- config
|
|
related:
|
|
- "[[personal/projects/media-pipeline-tracker-parsers]]"
|
|
- "[[family/projects/media-toolbox-kraken]]"
|
|
---
|
|
# Media Pipeline — File Routing & Directory Policies
|
|
|
|
## Текущее состояние (до реализации)
|
|
|
|
Текущий конфиг уже содержит:
|
|
- `directories` — список папок для сканирования (downloads, movies, cartoons, series, documentaries)
|
|
- `transmission.sorting_rules` — genre-based routing для downloads → destination
|
|
- `transmission.default_movies_destination` / `default_series_destination`
|
|
- `output` — куда идут symlink'и library (movies, series, cartoons, documentaries)
|
|
|
|
**Проблема:** политика действий с файлами захардкодена в `MediaFinalizer`:
|
|
- для всех директорий → всегда `symlink` в library
|
|
- нет способа сказать "для music — не делать canonical symlink"
|
|
- нет способа сказать "для downloads — переместить через Transmission; для movies — оставить на месте"
|
|
- `FileAction.move` и `TransmissionRPC.moveTorrent` существуют, но нигде не вызываются из apply-фазы
|
|
|
|
## Цель
|
|
|
|
Политика обработки файлов задаётся **в конфиге per-directory**, не в коде.
|
|
`MediaFinalizer` читает политику и генерирует правильный набор `FileAction`.
|
|
|
|
## Дизайн конфига
|
|
|
|
```json
|
|
"directory_policies": [
|
|
{
|
|
"path": "/media/downloads",
|
|
"unsorted": true,
|
|
"on_match": {
|
|
"move": "transmission",
|
|
"destination_by_kind": {
|
|
"movie": "/media/movies",
|
|
"series": "/media/series",
|
|
"cartoon": "/media/cartoons",
|
|
"documentary": "/media/documentaries",
|
|
"music": "/media/music",
|
|
"audiobook": "/media/audiobooks",
|
|
"book": "/media/books",
|
|
"game": "/media/games"
|
|
},
|
|
"library_symlink": true,
|
|
"nfo": true
|
|
},
|
|
"on_unmatched": {
|
|
"move": "none"
|
|
}
|
|
},
|
|
{
|
|
"path": "/media/movies",
|
|
"on_match": {
|
|
"move": "none",
|
|
"library_symlink": true,
|
|
"nfo": true
|
|
}
|
|
},
|
|
{
|
|
"path": "/media/cartoons",
|
|
"on_match": {
|
|
"move": "none",
|
|
"library_symlink": true,
|
|
"nfo": true
|
|
}
|
|
},
|
|
{
|
|
"path": "/media/series",
|
|
"on_match": {
|
|
"move": "none",
|
|
"library_symlink": true,
|
|
"nfo": true
|
|
}
|
|
},
|
|
{
|
|
"path": "/media/documentaries",
|
|
"on_match": {
|
|
"move": "none",
|
|
"library_symlink": true,
|
|
"nfo": true
|
|
}
|
|
},
|
|
{
|
|
"path": "/media/music",
|
|
"on_match": {
|
|
"move": "none",
|
|
"library_symlink": false,
|
|
"nfo": true
|
|
}
|
|
}
|
|
]
|
|
```
|
|
|
|
### `move` values
|
|
- `"transmission"` — вызвать `TransmissionRPC.moveTorrent`; если торрент не найден → fallback на `"filesystem"`
|
|
- `"filesystem"` — `FileAction.move` (прямое перемещение)
|
|
- `"none"` — файл остаётся на месте
|
|
|
|
### `library_symlink`
|
|
- `true` — создать каноническое имя в `output.*` папке (как сейчас)
|
|
- `false` — не создавать symlink (файл сам по себе — конечная точка, напр. music)
|
|
|
|
### `nfo`
|
|
- `true` — писать NFO рядом с файлом (если нет library_symlink) или в library (если есть)
|
|
- `false` — не писать NFO (игры, книги — нет Kodi-совместимой схемы)
|
|
|
|
### `destination_by_kind`
|
|
Только для `unsorted: true` директорий (downloads). Для остальных не нужно — файл уже в правильном месте.
|
|
`MediaKind` определяется из resolved metadata (TMDb genre → kind mapping) или из категории торрента.
|
|
|
|
### `on_unmatched`
|
|
Что делать если resolve не удался. По умолчанию `move: "none"` — файл остаётся в downloads.
|
|
|
|
## Совместимость с текущим конфигом
|
|
|
|
Старые поля (`transmission.sorting_rules`, `transmission.default_movies_destination`, `output`) сохраняются как fallback. `directory_policies` приоритетнее если задан.
|
|
|
|
## Компоненты реализации
|
|
|
|
### 1. `DirectoryPolicy` struct (Config.swift)
|
|
```swift
|
|
public struct DirectoryPolicy: Codable {
|
|
public let path: String
|
|
public let unsorted: Bool // defaults false
|
|
public let onMatch: MatchPolicy
|
|
public let onUnmatched: UnmatchedPolicy
|
|
}
|
|
|
|
public struct MatchPolicy: Codable {
|
|
public let move: MoveStrategy // .transmission / .filesystem / .none
|
|
public let destinationByKind: [String: String]? // only for unsorted
|
|
public let librarySymlink: Bool // defaults true
|
|
public let nfo: Bool // defaults true
|
|
}
|
|
|
|
public enum MoveStrategy: String, Codable {
|
|
case transmission, filesystem, none = "none"
|
|
}
|
|
```
|
|
|
|
### 2. `FileAction.transmissionMove` (FileAction.swift)
|
|
```swift
|
|
case transmissionMove(torrentId: Int, from: URL, to: URL)
|
|
```
|
|
|
|
### 3. `ApplyExecutor` — выполнение `transmissionMove`
|
|
Нужен `TransmissionRPC` в контексте apply-фазы.
|
|
При ошибке RPC — fallback на `FileAction.move`.
|
|
|
|
### 4. `MediaFinalizer` — читает политику из конфига
|
|
```swift
|
|
func policy(for sourceURL: URL, config: Config) -> DirectoryPolicy?
|
|
```
|
|
Вместо hardcoded `symlink` генерирует actions по политике.
|
|
|
|
### 5. `MediaKind` detection
|
|
- Из TMDb genres через mapping → `movie`, `series`, `cartoon`, `documentary`
|
|
- Из Rutracker категории торрента → `music`, `game`, `audiobook`, `book`
|
|
- Fallback: по исходной директории (`/media/music/` → `music`)
|
|
|
|
## Порядок реализации
|
|
|
|
- [ ] **C1** — `DirectoryPolicy` в `Config.swift` + парсинг из JSON
|
|
- [ ] **C2** — `FileAction.transmissionMove` + выполнение в `ApplyExecutor`
|
|
- [ ] **C3** — `MediaFinalizer` — routing по политике (move + symlink + nfo)
|
|
- [ ] **C4** — `MediaKind` detection из resolved metadata
|
|
- [ ] **C5** — Обновить конфиг на Кракене
|
|
- [ ] **C6** — Тесты: downloads → movies (Transmission), movies (no move, symlink), music (no move, no symlink)
|
|
|
|
## Связанные файлы
|
|
|
|
- `Sources/MediaPipeline/Config/Config.swift` — добавить `DirectoryPolicy`
|
|
- `Sources/MediaPipeline/Apply/FileAction.swift` — `transmissionMove` case
|
|
- `Sources/MediaPipeline/Apply/ApplyExecutor.swift` (или аналог) — выполнение RPC
|
|
- `Sources/MediaPipeline/Apply/MediaFinalizer.swift` — routing по политике
|
|
- `Sources/MediaSources/APIs/TransmissionRPC.swift` — уже есть `moveTorrent`
|
|
|
|
## Связанные планы
|
|
|
|
- [[media-pipeline-tracker-parsers]] — multi-tracker (реализован)
|
|
- Шаг B (новые типы медиа) — блокирует `destination_by_kind` для music/games/books
|
|
|
|
## Связанные заметки
|
|
|
|
- [[personal/projects/media-pipeline-tracker-parsers]]
|
|
- [[family/projects/media-toolbox-kraken]]
|