Files
PLib/README.md
T

80 lines
5.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# PLib
Менеджер видеотеки на Avalonia: сканирует папки, вытаскивает превью через ffmpeg и
показывает всё сеткой карточек.
## Что уже работает
- Сканирование указанных папок, инкрементальное — файл, который не изменился, не переиндексируется.
- Метаданные (длительность, разрешение, кодек) через ffprobe.
- Постеры кадром из видео через ffmpeg, с кэшем на диске.
- Виртуализированная сетка карточек, ленивая загрузка превью, поиск и сортировка.
- Светлая и тёмная темы.
- Клик или Enter по карточке — открыть в системном плеере, правая кнопка — контекстное меню.
## Требования
- .NET 10 SDK
- `ffmpeg` и `ffprobe` в `PATH`
## Запуск
```bash
dotnet run --project src/PLib.Desktop
```
```bash
dotnet test
```
## Архитектура
Четыре слоя, зависимости направлены только внутрь:
| Проект | Отвечает за | Знает о |
| --- | --- | --- |
| `PLib.Domain` | Сущность `VideoItem` и её инварианты | ни о чём |
| `PLib.Application` | Сценарии (`LibraryService`) и абстракции портов | Domain |
| `PLib.Infrastructure` | EF Core + SQLite, ffmpeg, файловая система | Application |
| `PLib.Desktop` | Avalonia, ViewModel'и, composition root | Infrastructure |
Ключевые решения:
- **MVVM на ReactiveUI.** Свойства — `[Reactive]` из `ReactiveUI.SourceGenerators`, команды —
`ReactiveCommand`, производные значения (`IsScanning`, `IsEmpty`) — `ToProperty`. Отмена
сканирования сделана штатным способом: скан живёт как observable, а `CancelScanCommand`
просто отписывает его через `TakeUntil`, что отменяет `CancellationToken`.
- **Сетка — проекция DynamicData, а не пересборка.** `SourceCache``AutoRefresh``Filter`
`SortAndBind` отдаёт диффы: добавился один файл — одна вставка в нужную позицию. Скролл,
контейнеры `ItemsRepeater` и уже загруженные превью остаются на месте. Поиск дебаунсится
на 200 мс, изменения карточек во время скана коалесцируются в 250 мс.
- **Сканирование — поток событий.** `ILibraryService.ScanAsync` возвращает
`IAsyncEnumerable<LibraryScanEvent>`: карточки появляются по мере находок, а не после
завершения всего прохода. Тяжёлая часть (ffprobe + ffmpeg) идёт параллельно через
`Parallel.ForEachAsync`, результаты собираются в `Channel` и применяются к сущностям
по одному — трекер изменений EF не потокобезопасен.
- **Вся работа вне UI-потока.** ViewModel оборачивает конвейер в `Task.Run` и возвращает
каждое событие в UI явно через `Dispatcher.UIThread`.
- **Превью живут только пока видны.** `AsyncImage` запрашивает битмап при попадании в
визуальное дерево и отпускает при выходе; `ThumbnailCache` — LRU на 256 записей с
декодированием в нужную ширину. Память зависит от размера окна, а не от размера библиотеки.
- **Scope на операцию.** `DbContext` живёт ровно одну операцию — ViewModel берёт
`IServiceScopeFactory` и создаёт scope на каждый вызов.
- **Кэш превью самовосстанавливается.** Диск — ключ `sha256(путь|размер|mtime)`, память — LRU
на 256 декодированных битмапов. Сканирование проверяет, что запомненный кадр физически
на месте (`IThumbnailGenerator.IsAvailable`), и перерисовывает удалённые; после полного
прохода лишние файлы вычищаются (`PurgeUnusedAsync`). Незавершённые `.tmp` удаляются
только если им больше часа — иначе можно снести рендер второго запущенного экземпляра.
## Данные
Всё пользовательское лежит в `%LOCALAPPDATA%\PLib`:
- `library.db` — SQLite с метаданными;
- `thumbnails/` — кэш постеров (ключ = путь + размер + время изменения файла);
- `settings.json` — список папок, перечитывается на лету;
- `logs/` — Serilog, ротация по дням.
Схема создаётся через `EnsureCreated`. Когда форма таблицы устоится — заменить на
миграции EF Core (`DatabaseInitializer` — единственное место, которое надо будет тронуть).