fix(plex): tolerate malformed activity rows
This commit is contained in:
@@ -1,20 +1,23 @@
|
||||
import 'package:json_annotation/json_annotation.dart';
|
||||
|
||||
import '../../utils/json_utils.dart';
|
||||
|
||||
part 'plex_activity.g.dart';
|
||||
|
||||
/// Represents a running background task on a Plex Media Server (from /activities endpoint).
|
||||
@JsonSerializable(createToJson: false)
|
||||
class PlexActivity {
|
||||
@JsonKey(defaultValue: '')
|
||||
@JsonKey(fromJson: stringOrEmpty)
|
||||
final String uuid;
|
||||
@JsonKey(defaultValue: '')
|
||||
@JsonKey(fromJson: stringOrEmpty)
|
||||
final String type;
|
||||
@JsonKey(defaultValue: '')
|
||||
@JsonKey(fromJson: stringOrEmpty)
|
||||
final String title;
|
||||
@JsonKey(readValue: readStringField)
|
||||
final String? subtitle;
|
||||
@JsonKey(defaultValue: 0)
|
||||
@JsonKey(fromJson: flexibleIntOrZero)
|
||||
final int progress; // 0–100
|
||||
@JsonKey(defaultValue: false)
|
||||
@JsonKey(fromJson: flexibleBool)
|
||||
final bool cancellable;
|
||||
|
||||
const PlexActivity({
|
||||
|
||||
@@ -7,10 +7,10 @@ part of 'plex_activity.dart';
|
||||
// **************************************************************************
|
||||
|
||||
PlexActivity _$PlexActivityFromJson(Map<String, dynamic> json) => PlexActivity(
|
||||
uuid: json['uuid'] as String? ?? '',
|
||||
type: json['type'] as String? ?? '',
|
||||
title: json['title'] as String? ?? '',
|
||||
subtitle: json['subtitle'] as String?,
|
||||
progress: (json['progress'] as num?)?.toInt() ?? 0,
|
||||
cancellable: json['cancellable'] as bool? ?? false,
|
||||
uuid: stringOrEmpty(json['uuid']),
|
||||
type: stringOrEmpty(json['type']),
|
||||
title: stringOrEmpty(json['title']),
|
||||
subtitle: readStringField(json, 'subtitle') as String?,
|
||||
progress: flexibleIntOrZero(json['progress']),
|
||||
cancellable: flexibleBool(json['cancellable']),
|
||||
);
|
||||
|
||||
@@ -826,7 +826,17 @@ class PlexClient
|
||||
if (container == null) return [];
|
||||
final activityList = container['Activity'] as List?;
|
||||
if (activityList == null) return [];
|
||||
return activityList.map((json) => PlexActivity.fromJson(json as Map<String, dynamic>)).toList();
|
||||
final activities = <PlexActivity>[];
|
||||
for (final json in activityList) {
|
||||
if (json is! Map<String, dynamic>) continue;
|
||||
try {
|
||||
final activity = PlexActivity.fromJson(json);
|
||||
if (activity.uuid.isNotEmpty) activities.add(activity);
|
||||
} catch (e) {
|
||||
appLogger.d('Skipping malformed Plex activity', error: e);
|
||||
}
|
||||
}
|
||||
return activities;
|
||||
} catch (e) {
|
||||
appLogger.e('Failed to get activities', error: e);
|
||||
return [];
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:plezy/models/plex/plex_activity.dart';
|
||||
|
||||
void main() {
|
||||
test('PlexActivity tolerates stringified and scalar fields', () {
|
||||
final activity = PlexActivity.fromJson({
|
||||
'uuid': 42,
|
||||
'type': 7,
|
||||
'title': true,
|
||||
'subtitle': 99,
|
||||
'progress': '75',
|
||||
'cancellable': '1',
|
||||
});
|
||||
|
||||
expect(activity.uuid, '42');
|
||||
expect(activity.type, '7');
|
||||
expect(activity.title, 'true');
|
||||
expect(activity.subtitle, '99');
|
||||
expect(activity.progress, 75);
|
||||
expect(activity.cancellable, isTrue);
|
||||
});
|
||||
}
|
||||
@@ -85,4 +85,44 @@ void main() {
|
||||
expect(queue?.playQueueVersion, 5);
|
||||
expect(queue?.size, 3);
|
||||
});
|
||||
|
||||
test('activities tolerate scalar drift and skip only malformed rows', () async {
|
||||
final client = makeClient(
|
||||
(_) async => http.Response(
|
||||
jsonEncode({
|
||||
'MediaContainer': {
|
||||
'Activity': [
|
||||
{
|
||||
'uuid': 'activity-1',
|
||||
'type': 'library.update',
|
||||
'title': 'Scanning',
|
||||
'subtitle': 'Movies',
|
||||
'progress': 25,
|
||||
'cancellable': false,
|
||||
},
|
||||
'not-an-activity',
|
||||
{'type': 'library.update', 'title': 'Missing identity'},
|
||||
{'uuid': 42, 'type': 7, 'title': true, 'subtitle': 99, 'progress': '75', 'cancellable': '1'},
|
||||
],
|
||||
},
|
||||
}),
|
||||
200,
|
||||
headers: {'content-type': 'application/json'},
|
||||
),
|
||||
);
|
||||
addTearDown(client.close);
|
||||
|
||||
final activities = await client.getActivities();
|
||||
|
||||
expect(activities, hasLength(2));
|
||||
expect(activities.first.uuid, 'activity-1');
|
||||
expect(activities.first.progress, 25);
|
||||
expect(activities.first.cancellable, isFalse);
|
||||
expect(activities.last.uuid, '42');
|
||||
expect(activities.last.type, '7');
|
||||
expect(activities.last.title, 'true');
|
||||
expect(activities.last.subtitle, '99');
|
||||
expect(activities.last.progress, 75);
|
||||
expect(activities.last.cancellable, isTrue);
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user