- 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.
9.3 KiB
9.3 KiB
Прокси-пул
Читать перед правкой Core/Proxies/** и Infrastructure/Proxies/**.
Добавить источник
- Реализовать
IProxySource(илиIMutableProxySource, если список редактируемый). - Зарегистрировать в
AddAvParserProxies(). Порядок регистрации = порядок слияния; свой список идёт последним, чтобы пользовательский адрес перебивал фидовый.
Инварианты
ProxyPoolпереиспользует существующиеProxyEntryпоEndpoint.Key— иначе перезагрузка списка стирала бы статистику, а публичные фиды переиздаются каждые несколько минут.- Проба обязана ходить по https. Обычный HTTP через прокси — это форвард GET с абсолютным URI, а
https —
CONNECT-туннель, и свободные прокси сплошь и рядом умеют первое и отказывают во втором («The proxy tunnel request to proxy … failed with status code 400»). Все собираемые адреса идут по https, поэтому прокси без CONNECT мертва для нас, и проверять надо ровно то, чем пользуемся. Раньше умолчание было http с обоснованием «не отбраковывать тех, кто просто не умеет CONNECT» — это и наполняло пул живыми на бумаге. Старое значение вsettings.jsonподменяется на https вAppSettings.ToProxyOptions(); введённое пользователем не трогается. - Выдаются только подтверждённо живые, пока такие есть. Сортировки «живые вперёд» не хватало: её видит только липкая стратегия, берущая голову списка, а round-robin и взвешенный выбор тянут из всего набора — то есть из пары тысяч непроверенных адресов, и почти каждый запрос платил полный connect-таймаут, чтобы это выяснить. Теперь «живых N» на странице и то, куда реально ходит сбор, — одно и то же число.
AcquireAsync(excludedCountries)— для правила «попробовать из другой страны». Записи, у которых фид не назвал страну, остаются в игре: неизвестная страна — не совпадение, и отбрасывать их значит сужать пул из-за пустого поля.- Фолбэк на всё доступное, когда живых нет. Непроверенная ≠ мёртвая: на холодном пуле строгий фильтр не выдал бы ничего и сбор просто встал бы, не попробовав. Ответившая на этом пути прокси сама себя переводит в живые.
- Карантин остаётся отдельным механизмом от
Health.Health— «что видели в последний раз»; исключать навсегда всё когда-либо упавшее значит потерять прокси после первой осечки. Уже было багом, ловитA_failing_proxy_is_quarantined_and_comes_back_later. LiveCountсчитает толькоAliveи не в карантине. На нём висит гейт сбора, поэтому «доступна» (окно истекло) и «живая» намеренно расходятся: гейт не должен открываться от одного лишь истечения окна.- Проба не трогает
SuccessCount/FailureCount— они про реальные запросы, а свип по паре тысяч адресов перезаписал бы всё, на чём держится взвешенный выбор. Провал пробы даёт карантин черезRecordProbe(..., quarantineOnFailure:). - Лиза без вердикта нейтральна: отменённая операция — не вина прокси, иначе каждый Cancel карантинил бы здоровые адреса.
- Вердикт лизы — про транспорт, а не про ресурс. 404 и 429 — успех прокси; иначе пул карантинил бы рабочие адреса с частотой мёртвых ссылок, а на лимит отвечал бы сменой прокси, то есть обходом лимита.
- Прогрев обрывается по достижении цели, а не проходит список до конца:
WarmUpAsyncлинкует CTS и гасит остаток наMinimumLiveProxies. Порядок кандидатов —WarmUpOrder(), публичный ровно ради проверки без прогона проб. - Остаток дочищает
TopUpAsyncв фоне. Прогрев по построению оставляет тысячи адресов вUnknown— это читается как «проверка не запускалась», и, что хуже, сбор получает пул из десяти прокси вместо сотен рабочих. Фоновый проход запускаетProxyPoolLoaderпосле прогрева, с четвертьюProbeConcurrency: пользовательский свип — это всплеск, за которым смотрят, а этот идёт минутами и, возможно, параллельно с закачкой. TopUpAsyncтрогает толькоUnknown. Перепроверять мёртвых — это и есть свип; перепроверять живых значит посадить в карантин прокси, которой прямо сейчас пользуется прогон.TopUpAsyncне делает_strategy.Reset(), в отличие от свипа: сброс уронил бы липкий выбор посреди сбора, причём ради записей, которых в этом выборе и нет.Changedон поднимает раз вChangeIntervalпроб — иначе страница молчит несколько минут.- Фоновый проход не идёт при
ProxyHealthCheck.Lazy: «проверять в момент выдачи» — это ровно просьба не свипать. SaveStateAsyncсериализован семафором. Писателей теперь двое (фоновый проход по завершении и выход из приложения), а запись идёт через один и тот же временный файл с переименованием.RestoreStateне выставляетHealth = Alive— «работала вчера» живёт вWasAliveOnLastRunи влияет только на порядок прогрева. Иначе пул отрапортует живыми тех, с кем не разговаривал: прогрев сочтёт цель достигнутой, а гейт сбора откроется по данным недельной давности. ЛовитA_remembered_proxy_is_not_reported_live_until_it_answers_again.RestoreStateне восстанавливает карантин: окно — стенные часы, между запусками могли пройти сутки, и перенос сажал бы прокси за давно истёкшее.- Сохраняются только
HasEverAnswered=SuccessCount > 0 || IsBelievedAlive(«believed» — вердикт этой сессии, а без него прошлой). Прогрев обрывается рано, поэтому большинство запомненных заканчивают сессию непроверенными — строгоеHealth == Aliveстёрло бы накопленный список за пару запусков. SelectиNextзарезервированы для CA1716 — метод стратегии называетсяPick.
Статистика на «Обзоре»
DashboardViewModel держит сводку пула и обновляет её по событию IProxyPool.Changed с throttle
250 мс — событие приходит на каждый исход лизы и на каждую пробу. «Живых» на карточке — это
LiveCount, то же число, что читает гейт сбора; RefreshProxyStats() публичен, чтобы тесты не ждали
throttle.