fix(discover): refresh content when a server comes online mid-session
This commit is contained in:
+8
-3
@@ -916,19 +916,24 @@ class _MainAppState extends State<MainApp> with WidgetsBindingObserver {
|
||||
// bind in waves on sign-in / profile switch and slow ones
|
||||
// reconnect after the initial load; without this they stay
|
||||
// missing from the sidebar until a re-switch or restart.
|
||||
context.read<MultiServerProvider>().onOnlineServersChanged = provider.syncToOnlineServers;
|
||||
context.read<MultiServerProvider>().addOnlineServersListener(provider.syncToOnlineServers);
|
||||
return provider;
|
||||
},
|
||||
),
|
||||
ChangeNotifierProvider(
|
||||
create: (context) {
|
||||
final activeProfile = context.read<ActiveProfileProvider>();
|
||||
return DiscoverProvider(
|
||||
context.read<MultiServerProvider>(),
|
||||
final multiServer = context.read<MultiServerProvider>();
|
||||
final provider = DiscoverProvider(
|
||||
multiServer,
|
||||
context.read<HiddenLibrariesProvider>(),
|
||||
context.read<LibrariesProvider>(),
|
||||
isProfileBinding: () => activeProfile.isBinding,
|
||||
);
|
||||
// Late server connects (reconnect after outage, slow wave)
|
||||
// refresh discover the same way they refresh libraries.
|
||||
multiServer.addOnlineServersListener(provider.syncToOnlineServers);
|
||||
return provider;
|
||||
},
|
||||
),
|
||||
ChangeNotifierProvider(create: (context) => PlaybackStateProvider()),
|
||||
|
||||
@@ -73,6 +73,9 @@ class DiscoverProvider extends ChangeNotifier with DisposableChangeNotifierMixin
|
||||
Set<String> _lastSeenHiddenKeys = {};
|
||||
List<String> _lastSeenLibraryOrderKeys = const [];
|
||||
|
||||
/// Online servers that contributed to the last successful [load] pass.
|
||||
Set<String> _loadedOnlineServerIds = {};
|
||||
|
||||
Future<void>? _inFlightLoad;
|
||||
bool _hasPendingLoad = false;
|
||||
|
||||
@@ -96,6 +99,19 @@ class DiscoverProvider extends ChangeNotifier with DisposableChangeNotifierMixin
|
||||
/// a background Continue Watching refresh (clamp only).
|
||||
int get loadGeneration => _loadGeneration;
|
||||
|
||||
/// Reload when a server comes online *mid-session* (reconnect, late wave) —
|
||||
/// its hubs and continue-watching rows are otherwise missing until a manual
|
||||
/// refresh. During profile binding this is a no-op: servers bind in waves
|
||||
/// and main_screen primes one [load] when binding settles, so reacting to
|
||||
/// each wave would multiply the (expensive) hub fan-out at startup.
|
||||
Future<void> syncToOnlineServers(Set<String> onlineServerIds) {
|
||||
if (onlineServerIds.isEmpty || isProfileBinding()) return Future<void>.value();
|
||||
if (_onDeckState == DiscoverLoadState.loaded && _loadedOnlineServerIds.containsAll(onlineServerIds)) {
|
||||
return Future<void>.value();
|
||||
}
|
||||
return load();
|
||||
}
|
||||
|
||||
/// Full load of Continue Watching + hubs. Concurrent calls coalesce into
|
||||
/// the in-flight pass plus at most one trailing pass (so a request that
|
||||
/// arrives mid-load still observes its own fresh fetch).
|
||||
@@ -148,10 +164,13 @@ class DiscoverProvider extends ChangeNotifier with DisposableChangeNotifierMixin
|
||||
includePlaybackHubs: false,
|
||||
);
|
||||
|
||||
final fetchedFromServerIds = Set<String>.of(_multiServer.onlineServerIds);
|
||||
|
||||
final fetchedOnDeck = await onDeckFuture;
|
||||
if (isDisposed) return;
|
||||
_applyOnDeck(fetchedOnDeck);
|
||||
_onDeckState = DiscoverLoadState.loaded;
|
||||
_loadedOnlineServerIds = fetchedFromServerIds;
|
||||
_loadGeneration++;
|
||||
safeNotifyListeners();
|
||||
unawaited(_syncSystemShelf(_onDeck));
|
||||
|
||||
@@ -43,11 +43,19 @@ class MultiServerProvider extends ChangeNotifier with DisposableChangeNotifierMi
|
||||
|
||||
/// Invoked with the current visibility-filtered online server ids whenever
|
||||
/// the manager's status stream fires (a server connects, reconnects, drops,
|
||||
/// or its auth state changes). Lets `LibrariesProvider` reload when the
|
||||
/// online set grows — servers bind in waves and slow ones reconnect after
|
||||
/// the initial load — without coupling the two providers by type. Wired once
|
||||
/// in `main.dart`.
|
||||
void Function(Set<String> onlineServerIds)? onOnlineServersChanged;
|
||||
/// or its auth state changes). Lets data providers (`LibrariesProvider`,
|
||||
/// `DiscoverProvider`) reload when the online set grows — servers bind in
|
||||
/// waves and slow ones reconnect after the initial load — without coupling
|
||||
/// the providers by type. Wired once per consumer in `main.dart`.
|
||||
final List<void Function(Set<String> onlineServerIds)> _onlineServersListeners = [];
|
||||
|
||||
void addOnlineServersListener(void Function(Set<String> onlineServerIds) listener) {
|
||||
_onlineServersListeners.add(listener);
|
||||
}
|
||||
|
||||
void removeOnlineServersListener(void Function(Set<String> onlineServerIds) listener) {
|
||||
_onlineServersListeners.remove(listener);
|
||||
}
|
||||
|
||||
/// Visibility filter applied by the active app profile. `null` means
|
||||
/// "all servers visible" (no profile restriction); otherwise only server
|
||||
@@ -148,11 +156,13 @@ class MultiServerProvider extends ChangeNotifier with DisposableChangeNotifierMi
|
||||
|
||||
safeNotifyListeners();
|
||||
|
||||
// Reload libraries when the online set changes. LibrariesProvider owns
|
||||
// the "is anything actually new to me?" decision (its loaded set can
|
||||
// Reload data providers when the online set changes. Each listener owns
|
||||
// the "is anything actually new to me?" decision (their loaded sets can
|
||||
// differ from _previousOnlineServerIds after a load error or a profile
|
||||
// switch that cleared it), so notify unconditionally and let it decide.
|
||||
onOnlineServersChanged?.call(currentOnline);
|
||||
// switch that cleared them), so notify unconditionally and let them decide.
|
||||
for (final listener in List.of(_onlineServersListeners)) {
|
||||
listener(currentOnline);
|
||||
}
|
||||
|
||||
// Only re-check live TV when a new server came online
|
||||
if (hasNewServer) {
|
||||
|
||||
@@ -286,6 +286,26 @@ void main() {
|
||||
expect(provider.onDeck.single.id, 'ep-1');
|
||||
});
|
||||
|
||||
test('syncToOnlineServers reloads for mid-session connects only', () async {
|
||||
aggregation.onDeckResult = () => [_item('a')];
|
||||
await provider.load();
|
||||
final onDeckCallsBefore = aggregation.onDeckCalls;
|
||||
|
||||
// Same server set → already covered, no fetch.
|
||||
await provider.syncToOnlineServers({'server_1'});
|
||||
expect(aggregation.onDeckCalls, onDeckCallsBefore);
|
||||
|
||||
// New server mid-session → full reload.
|
||||
await provider.syncToOnlineServers({'server_1', 'server_2'});
|
||||
expect(aggregation.onDeckCalls, onDeckCallsBefore + 1);
|
||||
|
||||
// During profile binding the startup priming owns loading — waves are
|
||||
// ignored so the hub fan-out doesn't run once per wave.
|
||||
isBinding = true;
|
||||
await provider.syncToOnlineServers({'server_1', 'server_2', 'server_3'});
|
||||
expect(aggregation.onDeckCalls, onDeckCallsBefore + 1);
|
||||
});
|
||||
|
||||
test('loadGeneration bumps on full loads only', () async {
|
||||
aggregation.onDeckResult = () => [_item('a')];
|
||||
final initial = provider.loadGeneration;
|
||||
|
||||
@@ -86,7 +86,7 @@ void main() {
|
||||
test('invokes onOnlineServersChanged with the visibility-filtered online set', () async {
|
||||
final p = MultiServerProvider(manager, aggregation);
|
||||
final calls = <Set<String>>[];
|
||||
p.onOnlineServersChanged = calls.add;
|
||||
p.addOnlineServersListener(calls.add);
|
||||
|
||||
manager.updateServerStatus(ServerId('srv-1'), true);
|
||||
await Future<void>.delayed(Duration.zero);
|
||||
@@ -218,7 +218,7 @@ void main() {
|
||||
test('expected servers become visible when they reconnect', () async {
|
||||
final p = MultiServerProvider(manager, aggregation);
|
||||
final onlineCalls = <Set<String>>[];
|
||||
p.onOnlineServersChanged = onlineCalls.add;
|
||||
p.addOnlineServersListener(onlineCalls.add);
|
||||
|
||||
p.setVisibleServerIds({'srv-1'});
|
||||
p.setExpectedVisibleServerIds({'srv-1', 'srv-2'});
|
||||
|
||||
Reference in New Issue
Block a user