Playlisten-Endpunkte fuer die einseitige Sicherung (ohne DELETE)
This commit is contained in:
@@ -42,6 +42,14 @@ class CloudSong {
|
||||
);
|
||||
}
|
||||
|
||||
/// Eine Playlist, wie sie der Server kennt. Mehr als Name und ID braucht die
|
||||
/// einseitige Sicherung nicht.
|
||||
class CloudPlaylist {
|
||||
const CloudPlaylist({required this.id, required this.name});
|
||||
final String id;
|
||||
final String name;
|
||||
}
|
||||
|
||||
/// Ein Wiedergabe-Ereignis, das zum Server gemeldet wird.
|
||||
class CloudVerlauf {
|
||||
const CloudVerlauf({
|
||||
@@ -293,6 +301,128 @@ class MeloCloudService {
|
||||
_pruefeStatus(antwort);
|
||||
}
|
||||
|
||||
/// Der Körper einer Playlisten-Antwort, oder `CloudException`.
|
||||
///
|
||||
/// Der Server meldet Fehler im 200er-Körper unter `error`. Ein bloßes
|
||||
/// `{"status":"not_found"}` **ohne** `error` (so antworten
|
||||
/// `handle_playlist_remove_song` und `handle_playlist_update_positions`)
|
||||
/// geht hier bewusst durch: die Sicherung ist einseitig und
|
||||
/// fire-and-forget, sie verwirft jeden Fehler ohnehin.
|
||||
Map<String, dynamic> _json(http.Response antwort) {
|
||||
final daten = jsonDecode(antwort.body) as Map<String, dynamic>;
|
||||
final fehler = daten['error'] as String?;
|
||||
if (fehler != null) throw CloudException(fehler);
|
||||
return daten;
|
||||
}
|
||||
|
||||
/// Legt eine Playlist am Server an und gibt deren ID zurück.
|
||||
///
|
||||
/// Die Identität stammt **immer** von hier: `user_playlists.id` ist
|
||||
/// AUTOINCREMENT und damit stabil. Eine Zuordnung über den Namen gibt es
|
||||
/// nicht — sie zerbräche beim ersten Umbenennen.
|
||||
Future<String> legePlaylistAn(String name) async {
|
||||
_pruefeAnmeldung();
|
||||
final antwort = await _client
|
||||
.post(
|
||||
Uri.parse('$basisUrl/playlists'),
|
||||
headers: {..._kopf, 'Content-Type': 'application/json'},
|
||||
body: jsonEncode({'name': name}),
|
||||
)
|
||||
.timeout(const Duration(seconds: 30));
|
||||
_pruefeStatus(antwort);
|
||||
// handle_playlist_create antwortet {"status":"ok","playlist":{"id":…}} —
|
||||
// die ID liegt eine Ebene tiefer, nicht auf oberster Ebene.
|
||||
final playlist = _json(antwort)['playlist'];
|
||||
if (playlist is! Map<String, dynamic>) {
|
||||
throw CloudException('Antwort ohne Playlist');
|
||||
}
|
||||
return '${playlist['id']}';
|
||||
}
|
||||
|
||||
Future<void> fuegePlaylistSongsHinzu(
|
||||
String playlistCloudId, List<String> songCloudIds) async {
|
||||
if (songCloudIds.isEmpty) return;
|
||||
_pruefeAnmeldung();
|
||||
final antwort = await _client
|
||||
.post(
|
||||
Uri.parse('$basisUrl/playlists/$playlistCloudId/songs'),
|
||||
headers: {..._kopf, 'Content-Type': 'application/json'},
|
||||
body: jsonEncode({'song_ids': songCloudIds}),
|
||||
)
|
||||
.timeout(const Duration(seconds: 30));
|
||||
_pruefeStatus(antwort);
|
||||
_json(antwort);
|
||||
}
|
||||
|
||||
Future<void> entfernePlaylistSong(
|
||||
String playlistCloudId, String songCloudId) async {
|
||||
_pruefeAnmeldung();
|
||||
final antwort = await _client
|
||||
.delete(
|
||||
Uri.parse('$basisUrl/playlists/$playlistCloudId/songs/$songCloudId'),
|
||||
headers: _kopf,
|
||||
)
|
||||
.timeout(const Duration(seconds: 30));
|
||||
_pruefeStatus(antwort);
|
||||
_json(antwort);
|
||||
}
|
||||
|
||||
/// Schreibt die Reihenfolge einer Playlist am Server fest.
|
||||
///
|
||||
/// Der Körper heißt `positions` und trägt Paare aus `id` und `position`:
|
||||
/// Der Router liest `d.get('positions',[])`, der Handler greift je Eintrag
|
||||
/// auf beide Schlüssel zu. Eine blanke ID-Liste unter `song_ids` käme als
|
||||
/// leere Liste an — der Server antwortete stumm `{"status":"ok"}` und
|
||||
/// änderte nichts.
|
||||
Future<void> setzePlaylistReihenfolge(
|
||||
String playlistCloudId, List<String> songCloudIds) async {
|
||||
_pruefeAnmeldung();
|
||||
final antwort = await _client
|
||||
.put(
|
||||
Uri.parse('$basisUrl/playlists/$playlistCloudId/positions'),
|
||||
headers: {..._kopf, 'Content-Type': 'application/json'},
|
||||
body: jsonEncode({
|
||||
'positions': [
|
||||
for (var i = 0; i < songCloudIds.length; i++)
|
||||
{'id': songCloudIds[i], 'position': i},
|
||||
],
|
||||
}),
|
||||
)
|
||||
.timeout(const Duration(seconds: 30));
|
||||
_pruefeStatus(antwort);
|
||||
_json(antwort);
|
||||
}
|
||||
|
||||
Future<List<CloudPlaylist>> playlisten() async {
|
||||
_pruefeAnmeldung();
|
||||
final antwort = await _client
|
||||
.get(Uri.parse('$basisUrl/playlists'), headers: _kopf)
|
||||
.timeout(const Duration(seconds: 30));
|
||||
_pruefeStatus(antwort);
|
||||
final liste = _json(antwort)['playlists'];
|
||||
if (liste is! List) throw CloudException('Antwort ohne Playlisten');
|
||||
return [
|
||||
for (final j in liste)
|
||||
CloudPlaylist(
|
||||
id: '${(j as Map<String, dynamic>)['id']}',
|
||||
name: j['name'] as String? ?? 'Ohne Namen',
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
Future<List<String>> playlistSongs(String playlistCloudId) async {
|
||||
_pruefeAnmeldung();
|
||||
final antwort = await _client
|
||||
.get(Uri.parse('$basisUrl/playlists/$playlistCloudId'), headers: _kopf)
|
||||
.timeout(const Duration(seconds: 30));
|
||||
_pruefeStatus(antwort);
|
||||
final liste = _json(antwort)['songs'];
|
||||
if (liste is! List) throw CloudException('Antwort ohne Titel');
|
||||
return [
|
||||
for (final j in liste) '${(j as Map<String, dynamic>)['id']}',
|
||||
];
|
||||
}
|
||||
|
||||
/// Meldet Wiedergaben. Der Server nimmt höchstens 100 je Aufruf an und
|
||||
/// verwirft Doppelmeldungen desselben Titels innerhalb einer Stunde.
|
||||
Future<void> meldeVerlauf(List<CloudVerlauf> eintraege) async {
|
||||
|
||||
Reference in New Issue
Block a user