Files
plezy/lib/services/plex_client/parts/metadata_edit.dart
T

199 lines
6.8 KiB
Dart

part of '../../plex_client.dart';
mixin _PlexMetadataEditMethods on MediaServerCacheMixin {
FailoverHttpClient get _http;
PlexApiCache get _cache;
@override
ServerId get serverId;
Future<MediaServerResponse> _getWithFailover(
String path, {
Map<String, dynamic>? queryParameters,
// ignore: unused_element_parameter
Map<String, String>? headers,
// ignore: unused_element_parameter
Duration? timeout,
// ignore: unused_element_parameter
AbortController? abort,
// ignore: unused_element_parameter
bool allowEndpointFailover = true,
});
Map<String, dynamic>? _getMediaContainer(MediaServerResponse response);
Future<bool> _wrapBoolApiCall(Future<MediaServerResponse> Function() apiCall, String errorMessage);
Future<List<T>> _wrapListApiCall<T>(
Future<MediaServerResponse> Function() apiCall,
List<T> Function(MediaServerResponse response) parseResponse,
String errorMessage,
);
Future<bool> updateMetadata({
required int sectionId,
required String ratingKey,
required int typeNumber,
String? title,
String? titleSort,
String? originalTitle,
String? originallyAvailableAt,
String? contentRating,
String? studio,
String? tagline,
String? summary,
Map<String, ({List<String> current, List<String> original})>? tagChanges,
}) async {
final queryParameters = <String, dynamic>{'type': typeNumber, 'id': ratingKey};
void addField(String name, String? value) {
if (value == null) return;
queryParameters['$name.value'] = value;
queryParameters['$name.locked'] = '1';
}
addField('title', title);
addField('titleSort', titleSort);
addField('originalTitle', originalTitle);
addField('originallyAvailableAt', originallyAvailableAt);
addField('contentRating', contentRating);
addField('studio', studio);
addField('tagline', tagline);
addField('summary', summary);
if (tagChanges != null) {
for (final entry in tagChanges.entries) {
final field = entry.key;
final current = entry.value.current;
final original = entry.value.original;
for (var index = 0; index < current.length; index++) {
queryParameters['$field[$index].tag.tag'] = current[index];
}
final removed = original.where((tag) => !current.contains(tag));
if (removed.isNotEmpty) {
queryParameters['$field[].tag.tag-'] = removed.map(Uri.encodeComponent).join(',');
}
queryParameters['$field.locked'] = '1';
}
}
final result = await _wrapBoolApiCall(
() => _http.put('/library/sections/$sectionId/all', queryParameters: queryParameters),
'Failed to update metadata',
);
if (result) await _deleteMetadataEditCache(ratingKey);
return result;
}
Future<List<PlexMatchResult>> findMatches(
String ratingKey, {
String? title,
String? year,
String? agent,
String? language,
}) {
final queryParameters = <String, dynamic>{
'manual': 1,
if (title != null && title.isNotEmpty) 'title': title,
if (year != null && year.isNotEmpty) 'year': year,
if (agent != null && agent.isNotEmpty) 'agent': agent,
if (language != null && language.isNotEmpty) 'language': language,
};
return _wrapListApiCall<PlexMatchResult>(
() => _getWithFailover('/library/metadata/$ratingKey/matches', queryParameters: queryParameters),
(response) {
final results = _getMediaContainer(response)?['SearchResult'];
if (results is! List) return [];
return results.map((json) => PlexMatchResult.fromJson(json as Map<String, dynamic>)).toList();
},
'Failed to search for matches',
);
}
Future<bool> applyMatch(String ratingKey, {required String guid, String? name, String? year}) async {
final queryParameters = <String, dynamic>{
'guid': guid,
if (name != null && name.isNotEmpty) 'name': name,
if (year != null && year.isNotEmpty) 'year': year,
};
final result = await _wrapBoolApiCall(
() => _http.put('/library/metadata/$ratingKey/match', queryParameters: queryParameters),
'Failed to apply match',
);
if (result) await _deleteMetadataEditCache(ratingKey);
return result;
}
Future<bool> unmatchItem(String ratingKey) async {
final result = await _wrapBoolApiCall(
() => _http.put('/library/metadata/$ratingKey/unmatch'),
'Failed to unmatch item',
);
if (result) await _deleteMetadataEditCache(ratingKey);
return result;
}
Future<List<Map<String, dynamic>>> getAvailableArtwork(String ratingKey, String element) async {
try {
final response = await _getWithFailover('/library/metadata/$ratingKey/$element');
final metadata = _getMediaContainer(response)?['Metadata'];
return metadata is List ? metadata.cast<Map<String, dynamic>>() : const [];
} catch (e) {
appLogger.e('Failed to get available artwork', error: e);
return [];
}
}
Future<bool> setArtworkFromUrl(String ratingKey, String element, String url) async {
final target = _artworkTarget(element);
final result = await _wrapBoolApiCall(
() => _http.put('/library/metadata/$ratingKey/$target', queryParameters: {'url': url}),
'Failed to set artwork from URL',
);
if (result) await _deleteMetadataEditCache(ratingKey);
return result;
}
Future<bool> uploadArtwork(String ratingKey, String element, List<int> bytes) async {
final target = _artworkTarget(element);
final result = await _wrapBoolApiCall(
() => _http.put(
'/library/metadata/$ratingKey/$target',
body: bytes,
headers: {'Content-Type': 'application/octet-stream', 'Content-Length': '${bytes.length}'},
),
'Failed to upload artwork',
);
if (result) await _deleteMetadataEditCache(ratingKey);
return result;
}
Future<Map<String, String>> getMetadataPrefs(String ratingKey) async {
final response = await _getWithFailover(
'/library/metadata/$ratingKey',
queryParameters: const {'includePreferences': 1},
);
return PlexMetadataPreferences.fromMediaContainer(_getMediaContainer(response)).values;
}
Future<bool> updateMetadataPrefs(String ratingKey, Map<String, String> prefs) async {
final result = await _wrapBoolApiCall(
() => _http.put('/library/metadata/$ratingKey/prefs', queryParameters: prefs),
'Failed to update metadata preferences',
);
if (result) await _deleteMetadataEditCache(ratingKey);
return result;
}
String _artworkTarget(String element) {
return element.endsWith('s') ? element.substring(0, element.length - 1) : element;
}
Future<void> _deleteMetadataEditCache(String ratingKey) async {
try {
await _cache.deleteForItem(ServerId(cacheServerId), ratingKey);
} catch (e, st) {
appLogger.w('Plex metadata edit cache invalidation failed', error: e, stackTrace: st);
}
}
}