Load the proxy pool at startup, and stop old settings files zeroing new defaults
The pool was empty until someone pressed Refresh, which also meant the parser
would have had nothing to work with. A ProxyPoolLoader now fills it once at
launch; startup does not await it, because blocking on a public list being
reachable would be the wrong trade, and it never throws. The proxy page joins
the same operation rather than starting a second download, so it reports the
outcome whether it is opened during the load or long after.
That change surfaced a worse bug underneath. The first run still loaded zero
entries with no error logged at all, which turned out to be the feed source
never being asked: options said UseFeed=False and Protocols=None. Neither is
reachable from the UI — both are default(T).
The cause is that AppSettings kept its defaults on property initialisers, and
the source-generated deserialiser does not run them. Reflection-based
deserialisation of "{}" keeps them; the generated context does not. So a
settings.json written before a setting existed came back with default(T) for it:
the proxy feed switched off, the protocol filter empty, the probe timeout zero
and the probe URL blank — and the app looked like the network had failed.
Defaults now live on primary constructor parameters, which STJ applies for
absent JSON members on both paths, so an older file upgrades cleanly. The
regression test writes a settings file from before the proxy settings existed
and asserts each one comes back at its default. ToProxyOptions also treats an
empty protocol filter as "all", since a hand-edited file that matches nothing is
the least useful possible reading of it.
Also quietens IHttpClientFactory to Warning: four Information lines per request
buried everything the app said, and a proxy sweep makes thousands of them.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
8b552470b7
commit
85656e70b0
@@ -1,6 +1,7 @@
|
||||
using AvParser.Core.Parsing;
|
||||
using AvParser.Core.Proxies;
|
||||
using AvParser.Core.Settings;
|
||||
using AvParser.Infrastructure.Proxies;
|
||||
using AvParser.Infrastructure.Storage;
|
||||
using AvParser.UI.Navigation;
|
||||
using AvParser.UI.Services;
|
||||
@@ -46,6 +47,7 @@ public static class UiServiceCollectionExtensions
|
||||
services.AddSingleton<ProxiesViewModel>(static sp => new ProxiesViewModel(
|
||||
sp.GetRequiredService<IProxyPool>(),
|
||||
sp.GetRequiredService<IMutableProxySource>(),
|
||||
sp.GetRequiredService<IProxyPoolLoader>(),
|
||||
sp.GetRequiredService<ILogger<ProxiesViewModel>>()
|
||||
));
|
||||
services.AddSingleton<AboutViewModel>();
|
||||
|
||||
@@ -91,11 +91,13 @@ public partial class ProxiesViewModel : PageViewModel, IDisposable
|
||||
/// <summary>Creates the page.</summary>
|
||||
/// <param name="pool">The pool being managed.</param>
|
||||
/// <param name="customSource">The user's editable list.</param>
|
||||
/// <param name="loader">The shared startup load, joined so the page can report its outcome.</param>
|
||||
/// <param name="logger">Diagnostics.</param>
|
||||
/// <param name="mainThread">Scheduler for UI-affine updates; tests pass an immediate one.</param>
|
||||
public ProxiesViewModel(
|
||||
IProxyPool pool,
|
||||
IMutableProxySource customSource,
|
||||
IProxyPoolLoader loader,
|
||||
ILogger<ProxiesViewModel> logger,
|
||||
ISequencer? mainThread = null
|
||||
)
|
||||
@@ -104,6 +106,7 @@ public partial class ProxiesViewModel : PageViewModel, IDisposable
|
||||
_customSource = customSource ?? throw new ArgumentNullException(nameof(customSource));
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
_mainThread = mainThread ?? RxSchedulers.MainThreadScheduler;
|
||||
ArgumentNullException.ThrowIfNull(loader);
|
||||
|
||||
SearchText = string.Empty;
|
||||
NewProxies = string.Empty;
|
||||
@@ -145,6 +148,11 @@ public partial class ProxiesViewModel : PageViewModel, IDisposable
|
||||
RemoveSelectedCommand.ThrownExceptions.Subscribe(OnCommandFailed);
|
||||
|
||||
Rebuild();
|
||||
|
||||
// The pool is normally already loading by the time this page is built, but joining the
|
||||
// same operation means the page reports the outcome whether it is opened during the load
|
||||
// or long after it. Not awaited — a constructor cannot be, and the task never throws.
|
||||
_ = ReportInitialLoadAsync(loader);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -185,6 +193,19 @@ public partial class ProxiesViewModel : PageViewModel, IDisposable
|
||||
/// <summary>Empties the custom list.</summary>
|
||||
public ReactiveCommand<RxVoid, RxVoid> ClearCustomCommand { get; }
|
||||
|
||||
private async Task ReportInitialLoadAsync(IProxyPoolLoader loader)
|
||||
{
|
||||
var count = await loader.EnsureLoadedAsync().ConfigureAwait(false);
|
||||
var message = Localizer.Instance.Format(
|
||||
"Proxies.Status.PoolHolds",
|
||||
Localizer.Instance.Plural("Proxies.Count.Proxies", count)
|
||||
);
|
||||
|
||||
// Anything the user has done since — an add, a check — is more interesting than the
|
||||
// startup count, so do not overwrite it.
|
||||
OnUi(() => StatusMessage ??= message);
|
||||
}
|
||||
|
||||
private async Task RefreshAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
var count = await _pool.RefreshAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
Reference in New Issue
Block a user