Add collector settings and per-source purge

Every limit the fetcher was using was a constant. They are settings now, and
CollectOptions became the single place policy lives: AppSettings.ToCollectOptions
clamps them, and the HTTP layer's FetchOptions is projected from that. One
clamping site rather than two sets of ceilings drifting apart.

Clamping rather than validating, for the reason the proxy options already do it:
a hand-edited file must not stop the app from starting. A MaxItemBytes edited to
zero would otherwise refuse everything, and a zeroed concurrency would deadlock
the run outright - so both are pulled into range instead. An empty format filter
is read as "everything", because switching every format off is far more likely
to be a slip than an instruction to collect nothing.

The media root has an ordering problem - it is a setting that decides the paths
the container is built from - so the file is read once before the container
exists rather than making every path lazy for one value.

Purge is scoped to a source and lives on the Collect page, where the source is
already chosen. Content another source also holds survives, which is what the
index's reference count was for.

The showcase hint says out loud what a hard link means: editing the browsable
copy edits the original, and deleting it frees nothing until the last name goes.
That is surprising enough to belong in the UI rather than only in the code.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Leonid Pershin
2026-08-13 21:52:43 +03:00
co-authored by Claude Opus 5
parent 70fb3a1df3
commit fe62bcf53f
21 changed files with 834 additions and 26 deletions
@@ -676,4 +676,67 @@
<data name="Parse.Error.Failed" xml:space="preserve">
<value>Не удалось собрать.</value>
</data>
<data name="Enum.ShowcaseMode.None" xml:space="preserve">
<value>Без витрины</value>
</data>
<data name="Enum.ShowcaseMode.HardLink" xml:space="preserve">
<value>Жёсткая ссылка (без лишнего места)</value>
</data>
<data name="Enum.ShowcaseMode.SymbolicLink" xml:space="preserve">
<value>Символическая ссылка</value>
</data>
<data name="Enum.ShowcaseMode.Copy" xml:space="preserve">
<value>Копия (удваивает расход диска)</value>
</data>
<data name="Settings.Collecting" xml:space="preserve">
<value>Сбор</value>
</data>
<data name="Settings.MaxConcurrentDownloads" xml:space="preserve">
<value>ЗАГРУЗОК ОДНОВРЕМЕННО</value>
</data>
<data name="Settings.MaxConcurrentPerHost" xml:space="preserve">
<value>НА ОДИН САЙТ</value>
</data>
<data name="Settings.HostDelay" xml:space="preserve">
<value>ПАУЗА МЕЖДУ ЗАПРОСАМИ (МС)</value>
</data>
<data name="Settings.HostDelayHint" xml:space="preserve">
<value>Насколько вежливо приложение обходится с одним сайтом. Обычно ограничивают именно эти два, а не общее число выше.</value>
</data>
<data name="Settings.MaxItemSize" xml:space="preserve">
<value>МАКСИМАЛЬНЫЙ РАЗМЕР (МБ)</value>
</data>
<data name="Settings.MaxItemSizeHint" xml:space="preserve">
<value>Всё, что больше, отклоняется — по возможности ещё до скачивания тела.</value>
</data>
<data name="Settings.ShowcaseMode" xml:space="preserve">
<value>ЧИТАЕМАЯ ВИТРИНА</value>
</data>
<data name="Settings.ShowcaseModeHint" xml:space="preserve">
<value>Файлы хранятся по хешу содержимого. Витрина даёт им читаемые пути по датам. Жёсткая ссылка — это второе имя того же файла: правка меняет оригинал, а удаление ничего не освобождает, пока не исчезнет последнее имя.</value>
</data>
<data name="Settings.MediaDirectory" xml:space="preserve">
<value>КАТАЛОГ МЕДИА</value>
</data>
<data name="Collect.Storage" xml:space="preserve">
<value>В хранилище {0}, {1}.</value>
</data>
<data name="Collect.Purge" xml:space="preserve">
<value>Удалить собранное этим источником</value>
</data>
<data name="Collect.PurgeHint" xml:space="preserve">
<value>Удаляет собранное этим источником. Файлы, на которые ссылается другой источник, остаются.</value>
</data>
<data name="Collect.PurgeDone" xml:space="preserve">
<value>Удалено {0}; освобождено {1}.</value>
</data>
<data name="Collect.Count.Files.One" xml:space="preserve">
<value>{0} файл</value>
</data>
<data name="Collect.Count.Files.Few" xml:space="preserve">
<value>{0} файла</value>
</data>
<data name="Collect.Count.Files.Many" xml:space="preserve">
<value>{0} файлов</value>
</data>
</root>