Files
plezy/lib/utils/abortable_http_request.dart
T
edde746 04d8070fd4 refactor: pin the look-alike code paths that must not be merged
Several pairs of near-identical code paths differ in one load-bearing
line. Each site now carries a comment naming the invariant that forces it
apart, backed by a characterization test so a future deduplication fails
loudly instead of silently changing behaviour.

Pinned: focusable wrapper vs. chip D-pad activation policy, profile
connection cleanup's raw-id vs. ServerId-typed server projections, live TV
tab loaders, video player display matching and playback service wiring,
track selection container ordering, tracker HTTP client status ladder, and
the MediaServerHttpClient shutdown/cancellation contract versus
ManagedHttpClient's closing guard.

New tests:
  test/focus/dpad_activation_policy_test.dart
  test/services/track_selection_container_ordinal_test.dart
  test/services/trackers/tracker_status_ladder_test.dart
  test/utils/media_server_http_client_shutdown_test.dart
2026-07-26 06:09:47 +02:00

63 lines
1.7 KiB
Dart

import 'dart:async';
import 'dart:convert';
import 'package:http/http.dart' as http;
Future<http.Response> sendAbortableHttpRequest(
http.Client client,
String method,
Uri uri, {
Map<String, String>? headers,
Object? body,
Encoding? encoding,
Duration? timeout,
Future<void>? abortTrigger,
String? operation,
}) {
// Deliberately not `AbortController`: that type lives with the media-server
// client and throws `MediaServerHttpException`, which the tracker/Seerr
// callers of this helper must stay independent of.
final abort = Completer<void>();
void abortRequest() {
if (!abort.isCompleted) abort.complete();
}
if (abortTrigger != null) {
unawaited(abortTrigger.whenComplete(abortRequest));
}
final request = http.AbortableRequest(method, uri, abortTrigger: abort.future);
if (headers != null) request.headers.addAll(headers);
if (encoding != null) request.encoding = encoding;
if (body != null) _setBody(request, body);
final future = client.send(request).then(http.Response.fromStream);
if (timeout == null) return future.whenComplete(abortRequest);
return future
.timeout(
timeout,
onTimeout: () {
abortRequest();
throw TimeoutException('${operation ?? '$method ${uri.path}'} timed out', timeout);
},
)
.whenComplete(abortRequest);
}
void _setBody(http.Request request, Object body) {
if (body is String) {
request.body = body;
return;
}
if (body is List<int>) {
request.bodyBytes = body;
return;
}
if (body is Map) {
request.bodyFields = body.cast<String, String>();
return;
}
throw ArgumentError('Invalid request body "$body".');
}