Implement media rule editor and enhance gallery functionality

- Introduced a rule editor in the gallery for users to define actions when a media item is found again, allowing for better management of media rules.
- Updated `GalleryViewModel` to handle rule actions, including saving, editing, and removing rules, with appropriate UI bindings.
- Enhanced UI components in `GalleryView.axaml` to support rule editing, including action selection and source management.
- Added localization strings for new rule-related features in both English and Russian.
- Improved unit tests to cover new rule management functionalities, ensuring robust behavior during rule creation and editing.

These changes significantly enhance the user experience by providing a more interactive and flexible way to manage media items in the gallery.
This commit is contained in:
Leonid Pershin
2026-08-15 16:03:02 +03:00
parent 0fa8fb89f6
commit 0aa3a7cf11
10 changed files with 463 additions and 12 deletions
+24
View File
@@ -76,6 +76,30 @@
повторный рендер, который тихо разъедется с шаблоном. Ctrl+C без выделения копирует весь журнал.
В Avalonia 12 `SetTextAsync` — расширение из `Avalonia.Input.Platform`, а не член `IClipboard`.
## Правила на картинку
Пользователь открывает картинку в галерее и говорит, что делать, когда она попадётся снова:
`MediaRule` (SHA-256 → действие + список источников), таблица `media_rule`, редактор — оверлей
`x:Name="RuleEditor"` в `GalleryView`.
- **Опознание по байтам, и только.** Ни адрес, ни размер не отличают «удалено» и «недоступно в вашей
стране» от настоящей картинки — сервис отдаёт и то и другое честным 200. Перцептивных хешей здесь
нет намеренно, поэтому пережатая версия того же баннера — другое правило; притворяться иначе значит
тихо терять настоящие находки.
- **Область — источники.** Одни и те же байты у одного хоста заглушка, у другого обычная картинка.
Пусто = все источники. Новое правило открывается отмеченным на источнике этой картинки.
- **`Skip` удаляет и уже собранную копию.** Правило ставят, глядя на мусор в галерее; оставить его на
диске значит выполнить половину просьбы. Удаление идёт через существующий tombstone — он один умеет
снимать копии вместе со ссылками showcase.
- **`RetryElsewhere` перезапрашивает тот же адрес через другую страну**, до `MaxCountryAttempts`, и
**требует прокси даже у источника, которому разрешено прямое подключение**: смысл в том, чтобы
прийти откуда-то ещё, а свой адрес — ровно то место, где баннер уже видели.
- **Некуда идти — ничего не сохраняем, адрес остаётся повторяемым** (`GeoBlocked`, нетерминальный).
Баннер это не картинка, а записать его как решённый исход значит сжечь id навсегда из-за
сегодняшнего состава пула. Прокси без страны в фиде исключить не из чего — это тоже конец попыток.
- **Старые tombstone приезжают в ту же карту как безобластной `Skip`**: у фетчера один поиск на
скачивание, а правило по тому же хешу перебивает, как более позднее и более конкретное.
## Хранилище медиа
- **В `blobs/` попадает только дочитанное.** Загрузка идёт во временный файл в соседнем каталоге на
+3
View File
@@ -24,6 +24,9 @@
всего набора — то есть из пары тысяч непроверенных адресов, и почти каждый запрос платил полный
connect-таймаут, чтобы это выяснить. Теперь «живых N» на странице и то, куда реально ходит сбор, —
одно и то же число.
- **`AcquireAsync(excludedCountries)` — для правила «попробовать из другой страны».** Записи, у
которых фид не назвал страну, остаются в игре: неизвестная страна — не совпадение, и отбрасывать их
значит сужать пул из-за пустого поля.
- **Фолбэк на всё доступное, когда живых нет.** Непроверенная ≠ мёртвая: на холодном пуле строгий
фильтр не выдал бы ничего и сбор просто встал бы, не попробовав. Ответившая на этом пути прокси
сама себя переводит в живые.