From 64d5c1f6aac950637502fb1e3ab62f7078922d81 Mon Sep 17 00:00:00 2001 From: edde746 <86283021+edde746@users.noreply.github.com> Date: Mon, 20 Apr 2026 01:10:30 +0200 Subject: [PATCH] fix: force rediscover on manual reconnect --- lib/main.dart | 8 ++++++-- lib/screens/main_screen.dart | 16 ++++++++++------ lib/services/multi_server_manager.dart | 18 ++++++++++++++---- lib/services/plex_client.dart | 7 ++++--- 4 files changed, 34 insertions(+), 15 deletions(-) diff --git a/lib/main.dart b/lib/main.dart index f1d1cb1f..35a0b9d1 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -551,8 +551,12 @@ class _MainAppState extends State with WidgetsBindingObserver { : const Duration(minutes: 2); if (now.difference(_lastResumeProbe) >= cooldown) { _lastResumeProbe = now; - _serverManager.checkServerHealth(); - _serverManager.reconnectOfflineServers(); + // Await health check before reconnecting so stale "online" servers + // get marked offline and included in the reconnection sweep. + unawaited(() async { + await _serverManager.checkServerHealth(); + await _serverManager.reconnectOfflineServers(); + }()); } case AppLifecycleState.paused: case AppLifecycleState.detached: diff --git a/lib/screens/main_screen.dart b/lib/screens/main_screen.dart index c5ca837e..924dc5d9 100644 --- a/lib/screens/main_screen.dart +++ b/lib/screens/main_screen.dart @@ -1,3 +1,4 @@ +import 'dart:async'; import 'dart:io' show Platform, exit; import 'package:flutter/material.dart'; @@ -589,13 +590,16 @@ class _MainScreenState extends State with RouteAware, WindowListener setState(() => _isReconnecting = true); final serverManager = context.read().serverManager; - serverManager.checkServerHealth(); - serverManager.reconnectOfflineServers().whenComplete(() { - // Give a moment for status updates to propagate - Future.delayed(const Duration(seconds: 1), () { + unawaited(() async { + try { + // Health check first so stale "online" servers get marked offline before + // we snapshot the offline list for reconnection. + await serverManager.checkServerHealth(); + await serverManager.reconnectOfflineServers(forceRediscovery: true); + } finally { if (mounted) setState(() => _isReconnecting = false); - }); - }); + } + }()); } void _handleLiveTvChanged() { diff --git a/lib/services/multi_server_manager.dart b/lib/services/multi_server_manager.dart index 8bb95533..af000538 100644 --- a/lib/services/multi_server_manager.dart +++ b/lib/services/multi_server_manager.dart @@ -492,12 +492,17 @@ class MultiServerManager { } } - /// Attempt reconnection for all offline servers - Future reconnectOfflineServers() async { + /// Attempt reconnection for all offline servers. + /// + /// When [forceRediscovery] is true, the cached endpoint is cleared before + /// reconnecting so the fast-path is skipped and a full candidate race runs. + /// Used by the manual reconnect button when the cached URL may be stale + /// (e.g. after a network change while the app was backgrounded). + Future reconnectOfflineServers({bool forceRediscovery = false}) async { // Coalesce concurrent calls — return the in-flight future if one exists if (_activeReconnect != null) return _activeReconnect!; - _activeReconnect = _doReconnectOfflineServers(); + _activeReconnect = _doReconnectOfflineServers(forceRediscovery: forceRediscovery); try { await _activeReconnect; } finally { @@ -505,13 +510,18 @@ class MultiServerManager { } } - Future _doReconnectOfflineServers() async { + Future _doReconnectOfflineServers({required bool forceRediscovery}) async { final offline = offlineServerIds; if (offline.isEmpty) return; appLogger.d('Attempting reconnection for ${offline.length} offline servers'); Sentry.addBreadcrumb(Breadcrumb(message: 'Reconnecting ${offline.length} offline server(s)', category: 'servers')); + if (forceRediscovery) { + final storage = await StorageService.getInstance(); + await Future.wait(offline.map(storage.clearServerEndpoint)); + } + final futures = offline.map((serverId) { final server = _servers[serverId]; if (server == null) return Future.value(); diff --git a/lib/services/plex_client.dart b/lib/services/plex_client.dart index 6a5b695f..24fd6dce 100644 --- a/lib/services/plex_client.dart +++ b/lib/services/plex_client.dart @@ -238,7 +238,7 @@ class PlexClient { 'Switching Plex endpoint after GET failure', error: {'from': failedEndpoint, 'to': nextBaseUrl, 'path': path}, ); - await _handleEndpointSwitch(nextBaseUrl); + await _handleEndpointSwitch(nextBaseUrl, persist: false); final response = await _http.get( path, queryParameters: queryParameters, @@ -247,6 +247,7 @@ class PlexClient { abort: abort, ); appLogger.i('Endpoint failover retry succeeded', error: {'newEndpoint': nextBaseUrl}); + await _onEndpointChanged?.call(nextBaseUrl); return response; } finally { _failoverSwitching = false; @@ -2887,7 +2888,7 @@ class PlexClient { } } - Future _handleEndpointSwitch(String newBaseUrl) async { + Future _handleEndpointSwitch(String newBaseUrl, {bool persist = true}) async { if (config.baseUrl == newBaseUrl) { return; } @@ -2897,7 +2898,7 @@ class PlexClient { config = config.copyWith(baseUrl: newBaseUrl); LogRedactionManager.registerServerUrl(newBaseUrl); - if (_onEndpointChanged != null) { + if (persist && _onEndpointChanged != null) { await _onEndpointChanged(newBaseUrl); } }