fix(ci): restore sanity checks
This commit is contained in:
@@ -252,8 +252,7 @@ class DiscoverProvider extends ChangeNotifier with DisposableChangeNotifierMixin
|
|||||||
safeNotifyListeners();
|
safeNotifyListeners();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (fetchedHubs.succeededServerIds.isEmpty &&
|
if (fetchedHubs.succeededServerIds.isEmpty && (fetchedHubs.cancelledServerIds.isNotEmpty || isProfileBinding())) {
|
||||||
(fetchedHubs.cancelledServerIds.isNotEmpty || isProfileBinding())) {
|
|
||||||
appLogger.d('DiscoverProvider: hub pass disrupted with no prior content; keeping loading state');
|
appLogger.d('DiscoverProvider: hub pass disrupted with no prior content; keeping loading state');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -93,8 +93,7 @@ class PlaybackStateProvider with ChangeNotifier, DisposableChangeNotifierMixin {
|
|||||||
|
|
||||||
/// The queue item the cursor currently points at, or null when no queue
|
/// The queue item the cursor currently points at, or null when no queue
|
||||||
/// is active or the cursor is outside the loaded window.
|
/// is active or the cursor is outside the loaded window.
|
||||||
MediaItem? get currentQueueItem =>
|
MediaItem? get currentQueueItem => _currentPlayQueueItemID == null ? null : _findLoadedItem(_currentPlayQueueItemID!);
|
||||||
_currentPlayQueueItemID == null ? null : _findLoadedItem(_currentPlayQueueItemID!);
|
|
||||||
|
|
||||||
/// Set the client reference for loading more items
|
/// Set the client reference for loading more items
|
||||||
void setPlayQueueWindowFetcher(PlayQueueWindowFetcher? fetcher) {
|
void setPlayQueueWindowFetcher(PlayQueueWindowFetcher? fetcher) {
|
||||||
|
|||||||
@@ -13,9 +13,17 @@ import '../utils/global_key_utils.dart';
|
|||||||
import '../utils/search_relevance.dart';
|
import '../utils/search_relevance.dart';
|
||||||
import 'multi_server_manager.dart';
|
import 'multi_server_manager.dart';
|
||||||
|
|
||||||
typedef OnDeckAggregationResult = ({List<MediaItem> items, Set<String> succeededServerIds, Set<String> cancelledServerIds});
|
typedef OnDeckAggregationResult = ({
|
||||||
|
List<MediaItem> items,
|
||||||
|
Set<String> succeededServerIds,
|
||||||
|
Set<String> cancelledServerIds,
|
||||||
|
});
|
||||||
typedef HubAggregationResult = ({List<MediaHub> hubs, Set<String> succeededServerIds, Set<String> cancelledServerIds});
|
typedef HubAggregationResult = ({List<MediaHub> hubs, Set<String> succeededServerIds, Set<String> cancelledServerIds});
|
||||||
typedef LibraryAggregationResult = ({List<MediaLibrary> libraries, Set<String> succeededServerIds, Set<String> cancelledServerIds});
|
typedef LibraryAggregationResult = ({
|
||||||
|
List<MediaLibrary> libraries,
|
||||||
|
Set<String> succeededServerIds,
|
||||||
|
Set<String> cancelledServerIds,
|
||||||
|
});
|
||||||
|
|
||||||
/// Whether [error] is a client-side abort (client teardown mid-request)
|
/// Whether [error] is a client-side abort (client teardown mid-request)
|
||||||
/// rather than a genuine server failure. Aggregation reports these servers
|
/// rather than a genuine server failure. Aggregation reports these servers
|
||||||
@@ -62,7 +70,11 @@ class DataAggregationService {
|
|||||||
final clients = _clientsFor(serverIds);
|
final clients = _clientsFor(serverIds);
|
||||||
if (clients.isEmpty) {
|
if (clients.isEmpty) {
|
||||||
appLogger.w('No online servers available for fetching libraries (neutral)');
|
appLogger.w('No online servers available for fetching libraries (neutral)');
|
||||||
return (libraries: const <MediaLibrary>[], succeededServerIds: const <String>{}, cancelledServerIds: const <String>{});
|
return (
|
||||||
|
libraries: const <MediaLibrary>[],
|
||||||
|
succeededServerIds: const <String>{},
|
||||||
|
cancelledServerIds: const <String>{},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
final succeededServerIds = <String>{};
|
final succeededServerIds = <String>{};
|
||||||
final cancelledServerIds = <String>{};
|
final cancelledServerIds = <String>{};
|
||||||
|
|||||||
@@ -118,6 +118,7 @@ class _SharedHttpClient extends http.BaseClient {
|
|||||||
void close() {}
|
void close() {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ignore: unused-code
|
||||||
/// Test hook: builds the throttled artwork client with an isolated limiter.
|
/// Test hook: builds the throttled artwork client with an isolated limiter.
|
||||||
@visibleForTesting
|
@visibleForTesting
|
||||||
http.Client createArtworkHttpClientForTest(http.Client inner, {int maxConcurrent = 6}) =>
|
http.Client createArtworkHttpClientForTest(http.Client inner, {int maxConcurrent = 6}) =>
|
||||||
|
|||||||
@@ -136,11 +136,7 @@ class PlaybackInitializationService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
appLogger.d('Found offline video: $readablePath');
|
appLogger.d('Found offline video: $readablePath');
|
||||||
return (
|
return (path: readablePath, mediaIndex: downloadedItem.mediaIndex, mediaSourceId: downloadedItem.mediaSourceId);
|
||||||
path: readablePath,
|
|
||||||
mediaIndex: downloadedItem.mediaIndex,
|
|
||||||
mediaSourceId: downloadedItem.mediaSourceId,
|
|
||||||
);
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
appLogger.w('Error checking offline video path', error: e);
|
appLogger.w('Error checking offline video path', error: e);
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@@ -105,9 +105,7 @@ class DeviceIdentityService {
|
|||||||
/// usable remains.
|
/// usable remains.
|
||||||
String? sanitizeHeaderValue(String? value) {
|
String? sanitizeHeaderValue(String? value) {
|
||||||
if (value == null) return null;
|
if (value == null) return null;
|
||||||
final filtered = String.fromCharCodes(
|
final filtered = String.fromCharCodes(value.codeUnits.where((unit) => unit != 0x0D && unit != 0x0A && unit <= 0xFF));
|
||||||
value.codeUnits.where((unit) => unit != 0x0D && unit != 0x0A && unit <= 0xFF),
|
|
||||||
);
|
|
||||||
final trimmed = filtered.trim();
|
final trimmed = filtered.trim();
|
||||||
return trimmed.isEmpty ? null : trimmed;
|
return trimmed.isEmpty ? null : trimmed;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,11 +8,7 @@ import '../database/app_database.dart';
|
|||||||
/// trustworthy discriminator there. Otherwise fall back to the media index.
|
/// trustworthy discriminator there. Otherwise fall back to the media index.
|
||||||
/// A null [requestedMediaIndex] means "any version" (the caller has no
|
/// A null [requestedMediaIndex] means "any version" (the caller has no
|
||||||
/// version opinion, e.g. external-player launches keyed by item only).
|
/// version opinion, e.g. external-player launches keyed by item only).
|
||||||
bool downloadedVersionMatches(
|
bool downloadedVersionMatches(DownloadedMediaItem row, {int? requestedMediaIndex, String? requestedMediaSourceId}) {
|
||||||
DownloadedMediaItem row, {
|
|
||||||
int? requestedMediaIndex,
|
|
||||||
String? requestedMediaSourceId,
|
|
||||||
}) {
|
|
||||||
final downloadedSourceId = row.mediaSourceId;
|
final downloadedSourceId = row.mediaSourceId;
|
||||||
final requestedSourceId = requestedMediaSourceId?.trim();
|
final requestedSourceId = requestedMediaSourceId?.trim();
|
||||||
final comparedBySourceId =
|
final comparedBySourceId =
|
||||||
|
|||||||
@@ -177,8 +177,7 @@ Future<bool?> navigateToVideoPlayer(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final mediaIndex =
|
final mediaIndex = selectedMediaIndex ?? downloadedMediaIndex ?? await savedMediaVersionIndexFor(metadata) ?? 0;
|
||||||
selectedMediaIndex ?? downloadedMediaIndex ?? await savedMediaVersionIndexFor(metadata) ?? 0;
|
|
||||||
final mediaSourceId = selectedMediaSourceId ?? downloadedMediaSourceId;
|
final mediaSourceId = selectedMediaSourceId ?? downloadedMediaSourceId;
|
||||||
|
|
||||||
var markedInFlight = false;
|
var markedInFlight = false;
|
||||||
|
|||||||
@@ -118,7 +118,9 @@ class _PlayerPerformanceOverlayState extends State<PlayerPerformanceOverlay> {
|
|||||||
// On height-limited screens (landscape phones) widen the card so the Wrap
|
// On height-limited screens (landscape phones) widen the card so the Wrap
|
||||||
// packs sections side by side instead of stacking into an off-screen column.
|
// packs sections side by side instead of stacking into an off-screen column.
|
||||||
final compactHeight = constraints.hasBoundedHeight && constraints.maxHeight < 500;
|
final compactHeight = constraints.hasBoundedHeight && constraints.maxHeight < 500;
|
||||||
final cardMaxWidth = compactHeight && constraints.hasBoundedWidth ? math.min(constraints.maxWidth, 560.0) : 400.0;
|
final cardMaxWidth = compactHeight && constraints.hasBoundedWidth
|
||||||
|
? math.min(constraints.maxWidth, 560.0)
|
||||||
|
: 400.0;
|
||||||
|
|
||||||
// Text scaling only inflates the intrinsic size that FittedBox scales right
|
// Text scaling only inflates the intrinsic size that FittedBox scales right
|
||||||
// back down, trading layout sharpness for nothing on this diagnostics card.
|
// back down, trading layout sharpness for nothing on this diagnostics card.
|
||||||
|
|||||||
@@ -8,14 +8,10 @@ import 'package:plezy/focus/input_mode_tracker.dart';
|
|||||||
/// must only exist in keyboard/d-pad mode: on touch it is pure dead weight
|
/// must only exist in keyboard/d-pad mode: on touch it is pure dead weight
|
||||||
/// multiplied by every card in a grid (see library scroll jank).
|
/// multiplied by every card in a grid (see library scroll jank).
|
||||||
void main() {
|
void main() {
|
||||||
Finder chromeIn(Type type) =>
|
Finder chromeIn(Type type) => find.descendant(of: find.byType(FocusableWrapper), matching: find.byType(type));
|
||||||
find.descendant(of: find.byType(FocusableWrapper), matching: find.byType(type));
|
|
||||||
|
|
||||||
Widget buildWrapper() => Scaffold(
|
Widget buildWrapper() => Scaffold(
|
||||||
body: FocusableWrapper(
|
body: FocusableWrapper(onSelect: () {}, child: const SizedBox(width: 10, height: 10)),
|
||||||
onSelect: () {},
|
|
||||||
child: const SizedBox(width: 10, height: 10),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
testWidgets('pointer mode builds no focus chrome around the child', (tester) async {
|
testWidgets('pointer mode builds no focus chrome around the child', (tester) async {
|
||||||
@@ -46,11 +42,7 @@ void main() {
|
|||||||
await tester.pumpWidget(
|
await tester.pumpWidget(
|
||||||
MaterialApp(
|
MaterialApp(
|
||||||
home: Scaffold(
|
home: Scaffold(
|
||||||
body: FocusableWrapper(
|
body: FocusableWrapper(focusNode: node, onSelect: () {}, child: const SizedBox(width: 10, height: 10)),
|
||||||
focusNode: node,
|
|
||||||
onSelect: () {},
|
|
||||||
child: const SizedBox(width: 10, height: 10),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -360,7 +360,10 @@ void main() {
|
|||||||
final heroFades = tester
|
final heroFades = tester
|
||||||
.widgetList<Container>(
|
.widgetList<Container>(
|
||||||
find.byWidgetPredicate(
|
find.byWidgetPredicate(
|
||||||
(w) => w is Container && w.decoration is BoxDecoration && (w.decoration! as BoxDecoration).gradient is LinearGradient,
|
(w) =>
|
||||||
|
w is Container &&
|
||||||
|
w.decoration is BoxDecoration &&
|
||||||
|
(w.decoration! as BoxDecoration).gradient is LinearGradient,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.map((c) => (c.decoration! as BoxDecoration).gradient! as LinearGradient)
|
.map((c) => (c.decoration! as BoxDecoration).gradient! as LinearGradient)
|
||||||
|
|||||||
@@ -390,14 +390,20 @@ void main() {
|
|||||||
|
|
||||||
test('changed access token requires recreation', () {
|
test('changed access token requires recreation', () {
|
||||||
expect(
|
expect(
|
||||||
MultiServerManager.canReuseJellyfinClient(live: base, incoming: base.copyWith(accessToken: 'rotated')),
|
MultiServerManager.canReuseJellyfinClient(
|
||||||
|
live: base,
|
||||||
|
incoming: base.copyWith(accessToken: 'rotated'),
|
||||||
|
),
|
||||||
isFalse,
|
isFalse,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('changed device id requires recreation', () {
|
test('changed device id requires recreation', () {
|
||||||
expect(
|
expect(
|
||||||
MultiServerManager.canReuseJellyfinClient(live: base, incoming: base.copyWith(deviceId: 'other-device')),
|
MultiServerManager.canReuseJellyfinClient(
|
||||||
|
live: base,
|
||||||
|
incoming: base.copyWith(deviceId: 'other-device'),
|
||||||
|
),
|
||||||
isFalse,
|
isFalse,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -385,8 +385,9 @@ void MpvPlayer::TryAudioReload(const char* reason, int attempt) {
|
|||||||
std::string reason_str = reason;
|
std::string reason_str = reason;
|
||||||
CommandAsync({"ao-reload"}, [this, reason_str, attempt](int error) {
|
CommandAsync({"ao-reload"}, [this, reason_str, attempt](int error) {
|
||||||
audio_reload_pending_ = false;
|
audio_reload_pending_ = false;
|
||||||
LogRecovery("ao-reload completed (reason=" + reason_str + ", attempt " + std::to_string(attempt) +
|
LogRecovery(
|
||||||
", error=" + std::to_string(error) + ")");
|
"ao-reload completed (reason=" + reason_str + ", attempt " + std::to_string(attempt) +
|
||||||
|
", error=" + std::to_string(error) + ")");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -397,8 +398,7 @@ void MpvPlayer::MaybeRunAudioRecovery() {
|
|||||||
if (file_loaded_) {
|
if (file_loaded_) {
|
||||||
resume_attempts_left_ = kResumeReloadAttempts;
|
resume_attempts_left_ = kResumeReloadAttempts;
|
||||||
resume_next_attempt_ = now + kResumeFirstDelay;
|
resume_next_attempt_ = now + kResumeFirstDelay;
|
||||||
LogRecovery("power resume detected; scheduling ao-reload in " + std::to_string(kResumeFirstDelay.count()) +
|
LogRecovery("power resume detected; scheduling ao-reload in " + std::to_string(kResumeFirstDelay.count()) + "ms");
|
||||||
"ms");
|
|
||||||
} else {
|
} else {
|
||||||
LogRecovery("power resume detected; no file loaded, nothing to recover");
|
LogRecovery("power resume detected; no file loaded, nothing to recover");
|
||||||
}
|
}
|
||||||
@@ -545,8 +545,8 @@ void MpvPlayer::HandleMpvEvent(mpv_event* event) {
|
|||||||
null_attempts_left_ = kNullRetryBudget;
|
null_attempts_left_ = kNullRetryBudget;
|
||||||
null_backoff_ = kNullFirstDelay;
|
null_backoff_ = kNullFirstDelay;
|
||||||
null_next_attempt_ = std::chrono::steady_clock::now() + kNullFirstDelay;
|
null_next_attempt_ = std::chrono::steady_clock::now() + kNullFirstDelay;
|
||||||
LogRecovery("current-ao fell back to null; starting recovery (budget " + std::to_string(kNullRetryBudget) +
|
LogRecovery(
|
||||||
")");
|
"current-ao fell back to null; starting recovery (budget " + std::to_string(kNullRetryBudget) + ")");
|
||||||
} else if (!is_null && current_ao_is_null_) {
|
} else if (!is_null && current_ao_is_null_) {
|
||||||
null_attempts_left_ = 0;
|
null_attempts_left_ = 0;
|
||||||
LogRecovery(std::string("current-ao is now '") + (current_ao ? current_ao : "") + "'");
|
LogRecovery(std::string("current-ao is now '") + (current_ao ? current_ao : "") + "'");
|
||||||
|
|||||||
Reference in New Issue
Block a user