- 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.
7.5 KiB
CLAUDE.md
Конвенции репозитория. Читать до правок.
Перед правкой подсистемы прочитать её файл — там инварианты, которые ломаются молча:
| Правишь | Читай |
|---|---|
Core/Proxies/**, Infrastructure/Proxies/** |
docs/proxies.md |
Collecting/**, Media/**, CollectViewModel, GalleryViewModel |
docs/collecting.md |
.axaml, стили, тестовые проекты, файлы сборки |
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. Логика туда не переезжает.
Добавить источник
- Реализовать
IMediaSource— вCore/Collecting/Sources/, если сети не нужно, иначе вInfrastructure/Collecting/. - Одна строка регистрации:
AddAvParserCore()для доменного,AddAvParserCollecting()для сетевого. - Ключи
Source.{id}.NameиSource.{id}.Descriptionв оба resx (без них отрисуется английский текст самого класса, а не сломается).
Каталог, страница «Сбор» и список источников подхватят его сами.
- Источник ищет, а не качает. Он отдаёт
MediaCandidate; скачивание, редиректы, тайм-ауты, сниффинг и троттлинг — наMediaFetcher, одном на всех. - Сетевой обязан переопределить
RequiresNetwork => true— иначе поедет напрямую в обход гейта.
Добавить страницу
- Наследник
PageViewModelвUI/ViewModels/XxxViewModel.cs(TitleKey,IconKey). UI/Views/XxxView.axaml— имя по конвенцииViewLocator:...ViewModels.XxxViewModel→...Views.XxxView.- Регистрация в
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
- Ключ и оба перевода — в
Strings.resxиStrings.ru.resx, иначе упадётLocalizationTests.Russian_translates_every_english_key. - В XAML
{l:Loc Ключ}, во ViewModelLocalizer.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 целиком ради одной картинки в плитке.