fix(jellyfin): flip server status when all endpoints are exhausted
This commit is contained in:
@@ -106,6 +106,7 @@ class JellyfinClient
|
||||
static Future<JellyfinClient> create(
|
||||
JellyfinConnection connection, {
|
||||
FavoriteChannelsRepository? favoritesRepository,
|
||||
void Function()? onAllEndpointsExhausted,
|
||||
}) async {
|
||||
// Register before any HTTP traffic so the very first probe URL doesn't
|
||||
// leak the token verbatim. `LogRedactionManager.redact()` also has
|
||||
@@ -142,6 +143,7 @@ class JellyfinClient
|
||||
logLabel: 'Jellyfin',
|
||||
prioritizedEndpoints: connection.baseUrls,
|
||||
onEndpointSwitch: (newBaseUrl, {required persist}) => client._handleEndpointSwitch(newBaseUrl, persist: persist),
|
||||
onAllEndpointsExhausted: onAllEndpointsExhausted,
|
||||
);
|
||||
client = JellyfinClient._(connection: connection, http: http, favoritesRepository: favoritesRepository);
|
||||
return client;
|
||||
@@ -154,6 +156,7 @@ class JellyfinClient
|
||||
required JellyfinConnection connection,
|
||||
required http.Client httpClient,
|
||||
FavoriteChannelsRepository? favoritesRepository,
|
||||
void Function()? onAllEndpointsExhausted,
|
||||
}) {
|
||||
late JellyfinClient client;
|
||||
final mediaHttp = FailoverHttpClient(
|
||||
@@ -162,6 +165,7 @@ class JellyfinClient
|
||||
logLabel: 'Jellyfin',
|
||||
prioritizedEndpoints: connection.baseUrls,
|
||||
onEndpointSwitch: (newBaseUrl, {required persist}) => client._handleEndpointSwitch(newBaseUrl, persist: persist),
|
||||
onAllEndpointsExhausted: onAllEndpointsExhausted,
|
||||
client: httpClient,
|
||||
);
|
||||
client = JellyfinClient._(connection: connection, http: mediaHttp, favoritesRepository: favoritesRepository);
|
||||
|
||||
@@ -575,7 +575,12 @@ class MultiServerManager {
|
||||
}
|
||||
}
|
||||
|
||||
final client = await JellyfinClient.create(resolvedConnection);
|
||||
final exhaustedMachineId = resolvedConnection.serverMachineId;
|
||||
final exhaustedCompoundId = resolvedConnection.id;
|
||||
final client = await JellyfinClient.create(
|
||||
resolvedConnection,
|
||||
onAllEndpointsExhausted: () => _onJellyfinEndpointsExhausted(exhaustedMachineId, exhaustedCompoundId),
|
||||
);
|
||||
// Admin status can change server-side; re-broadcast and persist so
|
||||
// admin-gated UI survives app restarts without requiring re-auth.
|
||||
_wireJellyfinConnectionUpdates(client);
|
||||
@@ -1022,8 +1027,10 @@ class MultiServerManager {
|
||||
_reconnectDebounce[serverId] = Timer(const Duration(seconds: 5), () {
|
||||
_reconnectDebounce.remove(serverId);
|
||||
|
||||
final server = _plexServers[serverId];
|
||||
if (server == null) return;
|
||||
final plexServer = _plexServers[serverId];
|
||||
final jellyfinCompoundId = _activeJellyfinMachine[serverId];
|
||||
final jellyfinClient = jellyfinCompoundId != null ? _jellyfinByCompoundId[jellyfinCompoundId] : null;
|
||||
if (plexServer == null && jellyfinClient == null) return;
|
||||
|
||||
appLogger.i('All endpoints exhausted for $serverId, triggering reconnection');
|
||||
updateServerStatus(serverId, false);
|
||||
@@ -1031,12 +1038,26 @@ class MultiServerManager {
|
||||
// Guard with _activeOptimizations to prevent duplicate reconnections
|
||||
if (_activeOptimizations.containsKey(serverId)) return;
|
||||
|
||||
_activeOptimizations[serverId] = _reconnectServer(serverId, server).whenComplete(() {
|
||||
final reconnect = plexServer != null
|
||||
? _reconnectServer(serverId, plexServer)
|
||||
: _reconnectJellyfinServer(serverId, jellyfinClient!);
|
||||
_activeOptimizations[serverId] = reconnect.whenComplete(() {
|
||||
_activeOptimizations.remove(serverId);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/// Jellyfin clients outlive their active binding (a previous profile's
|
||||
/// client stays in [_jellyfinByCompoundId]); only the currently bound
|
||||
/// client's exhaustion may flip the machine's status.
|
||||
void _onJellyfinEndpointsExhausted(String machineId, String compoundId) {
|
||||
if (_activeJellyfinMachine[machineId] != compoundId) {
|
||||
appLogger.d('Ignoring endpoint exhaustion from inactive Jellyfin client', error: compoundId);
|
||||
return;
|
||||
}
|
||||
_onServerEndpointsExhausted(ServerId(machineId));
|
||||
}
|
||||
|
||||
/// Disconnect all servers
|
||||
void disconnectAll() {
|
||||
appLogger.i('Disconnecting all servers');
|
||||
|
||||
@@ -165,6 +165,23 @@ void main() {
|
||||
expect(client.connection.baseUrls, ['https://fallback.example.com', 'https://primary.example.com']);
|
||||
});
|
||||
|
||||
test('exhausting every endpoint fires onAllEndpointsExhausted', () async {
|
||||
var exhausted = 0;
|
||||
final client = JellyfinClient.forTesting(
|
||||
connection: _conn(
|
||||
baseUrl: 'https://primary.example.com',
|
||||
baseUrls: const ['https://primary.example.com', 'https://fallback.example.com'],
|
||||
),
|
||||
httpClient: MockClient((req) async => throw TimeoutException('endpoint down')),
|
||||
onAllEndpointsExhausted: () => exhausted++,
|
||||
);
|
||||
addTearDown(client.close);
|
||||
|
||||
await client.getMachineIdentifier();
|
||||
|
||||
expect(exhausted, 1);
|
||||
});
|
||||
|
||||
test('resets live base URL after fallback endpoint is exhausted', () async {
|
||||
final requests = <Uri>[];
|
||||
final client = JellyfinClient.forTesting(
|
||||
|
||||
Reference in New Issue
Block a user