ocarina2/lib/api/subsonic/subsonic.dart

150 lines
4.2 KiB
Dart

import 'dart:convert';
import 'package:crypto/crypto.dart';
import 'package:dio/dio.dart';
import 'package:ocarina/api/subsonic/album.dart';
import 'package:ocarina/api/subsonic/artistindex.dart';
import 'package:ocarina/api/subsonic/song.dart';
import 'package:ocarina/util/util.dart';
/// Used to communicate with Subsonic API
class SubsonicApiService {
/// Used to communicate with Subsonic API
factory SubsonicApiService() {
return _subsonicService;
}
SubsonicApiService._internal();
static final SubsonicApiService _subsonicService =
SubsonicApiService._internal();
Dio get _client =>
Dio(BaseOptions(contentType: "application/json", baseUrl: baseUrl));
// ignore: avoid_setters_without_getters
set password(String p) {
_salt = Util.makeSalt;
_hash = md5.convert(utf8.encode(p + _salt)).toString();
}
Future<Response<Map<String, dynamic>>> _get(
String path, {
Map<String, dynamic> params = const {},
}) async {
return _client.get<Map<String, dynamic>>(
path,
queryParameters: {
"u": user,
"t": _hash,
"s": _salt,
"c": "Ocarina",
"f": "json",
"v": _version,
...params,
},
);
}
/// Subsonic-compatible server's base URL
String baseUrl = "";
/// Subsonic username
String user = "";
String _hash = "";
final String _version = "1.16.1";
String _salt = "";
/// Checks if saved details return an OK from server
Future<bool> verifyCredentials() async {
try {
final r = await _get("/rest/ping.view");
if (r.data == null || r.data!.containsKey("error")) return false;
return true;
} catch (e) {
return false;
}
}
Future<List<ArtistIndex>?> getArtistIndex() async {
try {
final r = await _get("/rest/getIndexes");
if (r.data == null) {
return null;
}
final response = r.data!["subsonic-response"] as Map<String, dynamic>;
if (response["status"] != "ok") return null;
final indexes = List<Map<String, dynamic>>.from(
(response["indexes"] as Map<String, dynamic>)["index"] as List<dynamic>,
);
return List<ArtistIndex>.generate(
indexes.length,
(index) => ArtistIndex.fromJson(indexes[index]),
);
} catch (e) {
logger.e(e);
return null;
}
}
/// Returns data from the `getMusicDirectory` endpoint formatted as [Album]
///
/// Checks if it is really a directory and tries to format it with fromJson
Future<List<Album>?> getAlbumList(String artistId) async {
final r = await _get("/rest/getMusicDirectory", params: {"id": artistId});
if (r.data == null || r.data!["subsonic-response"]["status"] != "ok") {
return null;
}
final albums = <Album>[];
for (final albumData in r.data!["subsonic-response"]["directory"]["child"]
as List<dynamic>) {
if ((albumData as Map<String, dynamic>)["isDir"] == false) {
continue;
}
try {
albums.add(Album.fromJson(albumData));
} catch (e) {
logger
..e("Error for $artistId")
..e(e)
..e(albumData);
continue;
}
}
return albums;
}
/// Returns data from the `getMusicDirectory` endpoint formatted as [Album]
///
/// Checks if it is really a directory and tries to format it with fromJson
Future<List<Song>?> getSongList(String albumId) async {
final r = await _get("/rest/getMusicDirectory", params: {"id": albumId});
if (r.data == null || r.data!["subsonic-response"]["status"] != "ok") {
return null;
}
final songs = <Song>[];
for (final songData in r.data!["subsonic-response"]["directory"]["child"]
as List<dynamic>) {
if ((songData as Map<String, dynamic>)["isDir"] == true) {
continue;
}
try {
songs.add(Song.fromJson(songData));
} catch (e) {
logger
..e("Error for $albumId")
..e(e)
..e(songData);
continue;
}
}
return songs;
}
String getCoverArtUrl(String id) =>
"$baseUrl/rest/getCoverArt?id=$id&u=$user&s=$_salt&t=$_hash&c=Ocarina&v=1.16.1";
String getStreamUrl(String id) =>
"$baseUrl/rest/stream?id=$id&u=$user&s=$_salt&t=$_hash&c=Ocarina&v=1.16.1";
}