- Updated `IMediaSourceCatalog` to support user-added media sources, allowing dynamic editing and management of sources. - Removed the `UrlListSource` class as its functionality is now integrated into the new catalog structure. - Enhanced `CollectOptions` to default `RequireProxy` to true, ensuring stricter handling of proxy requirements. - Improved error handling in `ParseError` to include a `Subject` field for better context on failures. - Adjusted dependency injection to reflect changes in media source management, removing old source registrations. - Introduced background proxy checks to ensure a more robust proxy pool management during collection processes. These changes streamline the media collection process and improve the overall user experience by providing clearer error reporting and more flexible source management.
109 lines
7.5 KiB
Markdown
109 lines
7.5 KiB
Markdown
# CLAUDE.md
|
||
|
||
Конвенции репозитория. Читать до правок.
|
||
|
||
**Перед правкой подсистемы прочитать её файл — там инварианты, которые ломаются молча:**
|
||
|
||
| Правишь | Читай |
|
||
|---|---|
|
||
| `Core/Proxies/**`, `Infrastructure/Proxies/**` | [docs/proxies.md](docs/proxies.md) |
|
||
| `Collecting/**`, `Media/**`, `CollectViewModel`, `GalleryViewModel` | [docs/collecting.md](docs/collecting.md) |
|
||
| `.axaml`, стили, тестовые проекты, файлы сборки | [docs/avalonia.md](docs/avalonia.md) |
|
||
|
||
## Команды
|
||
|
||
`./build.ps1` — полный гейт (формат → сборка → тесты), прогнать перед коммитом. Отдельные шаги:
|
||
`dotnet build AvParser.slnx -c Release`, `dotnet test AvParser.slnx -c Release`,
|
||
`dotnet csharpier check .`, `./run.ps1`. На Linux/macOS — `./build.sh` и `./run.sh`.
|
||
|
||
## Слои
|
||
|
||
`Core ← Infrastructure ← UI ← Desktop`, строго в одну сторону.
|
||
|
||
- **`Core` не ссылается на Avalonia** — домен должен запускаться из CLI, worker-а или бенчмарка. Дай
|
||
ему Avalonia, и кто-нибудь потянется к `Dispatcher.UIThread` внутри источника.
|
||
- **`UI` — библиотека, а не exe**: headless-тесты собирают настоящие View без `Program.cs`, Serilog и
|
||
контейнера.
|
||
- **`Desktop` — тонкий composition root.** Логика туда не переезжает.
|
||
|
||
## Добавить источник
|
||
|
||
1. Реализовать `IMediaSource` — в `Core/Collecting/Sources/`, если сети не нужно, иначе в
|
||
`Infrastructure/Collecting/`.
|
||
2. Одна строка регистрации: `AddAvParserCore()` для доменного, `AddAvParserCollecting()` для сетевого.
|
||
3. Ключи `Source.{id}.Name` и `Source.{id}.Description` в оба resx (без них отрисуется английский
|
||
текст самого класса, а не сломается).
|
||
|
||
Каталог, страница «Сбор» и список источников подхватят его сами.
|
||
|
||
- **Источник ищет, а не качает.** Он отдаёт `MediaCandidate`; скачивание, редиректы, тайм-ауты,
|
||
сниффинг и троттлинг — на `MediaFetcher`, одном на всех.
|
||
- **Сетевой обязан переопределить `RequiresNetwork => true`** — иначе поедет напрямую в обход гейта.
|
||
|
||
## Добавить страницу
|
||
|
||
1. Наследник `PageViewModel` в `UI/ViewModels/XxxViewModel.cs` (`TitleKey`, `IconKey`).
|
||
2. `UI/Views/XxxView.axaml` — имя по конвенции `ViewLocator`: `...ViewModels.XxxViewModel` →
|
||
`...Views.XxxView`.
|
||
3. Регистрация в `AddAvParserUI()` конкретным типом **и** как `PageViewModel`; порядок этих
|
||
регистраций = порядок пунктов в рельсе навигации.
|
||
|
||
## ReactiveUI 24 (дистрибутив Primitives)
|
||
|
||
`System.Reactive` не используется:
|
||
|
||
| Классика | Здесь |
|
||
|---|---|
|
||
| `Unit` | `RxVoid` |
|
||
| `IScheduler` | `ISequencer` (`ReactiveUI.Primitives.Concurrency`) |
|
||
| `Subject<T>` / `BehaviorSubject<T>` | `Signal<T>` / `BehaviorSignal<T>` |
|
||
| `RxApp.MainThreadScheduler` | `RxSchedulers.MainThreadScheduler` |
|
||
| `TestScheduler` | `VirtualClock`, `ImmediateSequencer.Instance` |
|
||
|
||
Привычные операторы (`Select`, `Where`, `Throttle`, `DistinctUntilChanged`, `CombineLatest`)
|
||
работают; `using ReactiveUI.Primitives;` нужен ради `Subscribe(Action<T>)`.
|
||
|
||
**ReactiveUI 24 не инициализируется сама** — первый `WhenAnyValue` бросит
|
||
`InvalidOperationException`, пока не отработал builder: в приложении `AppBuilder.UseReactiveUI(...)`,
|
||
в VM-тестах module initializer `ReactiveUiBootstrap`. Новый тестовый проект без Avalonia обязан
|
||
сделать то же самое.
|
||
|
||
## Конвенции ViewModel
|
||
|
||
- **Каждая VM принимает `ISequencer? mainThread = null`** и использует его в `outputScheduler:` и
|
||
`ToProperty(..., scheduler)`. Это и делает тесты синхронными — они передают `ImmediateSequencer`.
|
||
- **Регистрация такой VM в DI — явная фабрика**, не по типу: иначе выбор конструктора зависит от
|
||
порядка регистраций.
|
||
- `[Reactive]` из `ReactiveUI.SourceGenerators` на partial-свойствах; класс — `partial`.
|
||
- **VM с производным от языка текстом переопределяет `OnLanguageChanged`** и зовёт `base`, иначе
|
||
заголовок страницы застрянет на прежнем языке.
|
||
- **VM, подписанная на синглтон** (пул, каталог, настройки), — `IDisposable` и отписывается.
|
||
|
||
## Строки UI
|
||
|
||
1. Ключ и **оба** перевода — в `Strings.resx` и `Strings.ru.resx`, иначе упадёт
|
||
`LocalizationTests.Russian_translates_every_english_key`.
|
||
2. В XAML `{l:Loc Ключ}`, во ViewModel `Localizer.Instance[...]` / `.Format(...)`.
|
||
|
||
Никакого хардкода в `Views/` кроме имени продукта и примеров адресов.
|
||
|
||
- **Счётчики — только через `Localizer.Plural`** с ключами `.One` / `.Few` / `.Many`: у русского три
|
||
формы, «{0} records» с приклеенным окончанием непереводимо.
|
||
- **Перечисления в списках — `LocalizedOption<T>`**, не сырые значения: конвертер разрешил бы подпись
|
||
один раз и не заметил смены языка. Идентичность обёртки — значение перечисления, чтобы выбор не
|
||
слетал.
|
||
- **Текст из домена переводится по коду.** `ParseError` несёт `Code` и `Arguments`, UI ищет
|
||
`Collect.Error.{Code}` с откатом на `Message`; имена источников — `Source.{id}.Name` с откатом на
|
||
`DisplayName`, поэтому «добавить источник = одна строка» остаётся в силе.
|
||
|
||
## Границы, выбранные намеренно
|
||
|
||
- **robots.txt не читается.** Источники либо принадлежат пользователю, либо введены им вручную. Это
|
||
решение, а не забывчивость: **с появлением источника, который ходит по чужому сайту, robots.txt
|
||
становится обязательным.**
|
||
- **Возобновления по `Range` нет**: частичный файл в хранилище дороже повторного скачивания.
|
||
- **SVG не поддерживается сознательно** — это текст, он умеет исполнять скрипты и несёт XXE.
|
||
BMP/ICO/HEIC/JPEG-XL просто отложены.
|
||
- **Перцептивных хешей нет**: дедуп точный, по SHA-256.
|
||
- **Кадры из видео не извлекаются** — это FFmpeg целиком ради одной картинки в плитке.
|