237 lines
7.3 KiB
Vala
237 lines
7.3 KiB
Vala
|
public errordomain Subsonic.Error {
|
||
|
BAD_AUTHN,
|
||
|
ERROR,
|
||
|
}
|
||
|
|
||
|
public delegate void Subsonic.SongCallback (Song song);
|
||
|
|
||
|
public class Subsonic.Artist : Object {
|
||
|
public string index;
|
||
|
public string id;
|
||
|
public string name { get; private set; }
|
||
|
public string? cover_art;
|
||
|
public string? artist_image_url;
|
||
|
public int64 album_count;
|
||
|
|
||
|
public Artist (string index, Json.Reader reader) {
|
||
|
this.index = index;
|
||
|
|
||
|
reader.read_member ("id");
|
||
|
this.id = reader.get_string_value ();
|
||
|
reader.end_member ();
|
||
|
|
||
|
reader.read_member ("name");
|
||
|
this.name = reader.get_string_value ();
|
||
|
reader.end_member ();
|
||
|
|
||
|
reader.read_member ("coverArt");
|
||
|
this.cover_art = reader.get_string_value ();
|
||
|
reader.end_member ();
|
||
|
|
||
|
reader.read_member ("artistImageUrl");
|
||
|
this.artist_image_url = reader.get_string_value ();
|
||
|
reader.end_member ();
|
||
|
|
||
|
reader.read_member ("albumCount");
|
||
|
this.album_count = reader.get_int_value ();
|
||
|
reader.end_member ();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public class Subsonic.Album : Object {
|
||
|
public string id;
|
||
|
public string name;
|
||
|
|
||
|
public Album (Json.Reader reader) {
|
||
|
reader.read_member ("id");
|
||
|
this.id = reader.get_string_value ();
|
||
|
reader.end_member ();
|
||
|
|
||
|
reader.read_member ("name");
|
||
|
this.name = reader.get_string_value ();
|
||
|
reader.end_member ();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public struct Subsonic.Song {
|
||
|
public string id;
|
||
|
public string title;
|
||
|
public string album;
|
||
|
public string artist;
|
||
|
public int64 track;
|
||
|
public int64 year;
|
||
|
public DateTime? starred; // TODO
|
||
|
public int64 duration;
|
||
|
public int64 play_count;
|
||
|
public string? genre;
|
||
|
public string cover_art;
|
||
|
|
||
|
public Song (Json.Reader reader) {
|
||
|
reader.read_member ("id");
|
||
|
this.id = reader.get_string_value ();
|
||
|
reader.end_member ();
|
||
|
|
||
|
reader.read_member ("title");
|
||
|
this.title = reader.get_string_value ();
|
||
|
reader.end_member ();
|
||
|
|
||
|
reader.read_member ("album");
|
||
|
this.album = reader.get_string_value ();
|
||
|
reader.end_member ();
|
||
|
|
||
|
reader.read_member ("artist");
|
||
|
this.artist = reader.get_string_value ();
|
||
|
reader.end_member ();
|
||
|
|
||
|
reader.read_member ("track");
|
||
|
this.track = reader.get_int_value ();
|
||
|
reader.end_member ();
|
||
|
|
||
|
reader.read_member ("year");
|
||
|
this.year = reader.get_int_value ();
|
||
|
reader.end_member ();
|
||
|
|
||
|
reader.read_member ("duration");
|
||
|
this.duration = reader.get_int_value ();
|
||
|
reader.end_member ();
|
||
|
|
||
|
reader.read_member ("playCount");
|
||
|
this.play_count = reader.get_int_value ();
|
||
|
reader.end_member ();
|
||
|
|
||
|
reader.read_member ("genre");
|
||
|
this.genre = reader.get_string_value ();
|
||
|
reader.end_member ();
|
||
|
|
||
|
reader.read_member ("coverArt");
|
||
|
this.cover_art = reader.get_string_value ();
|
||
|
reader.end_member ();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public class Subsonic.Client : Object {
|
||
|
private Soup.Session session;
|
||
|
private string url;
|
||
|
private string parameters;
|
||
|
|
||
|
public Client.with_token (string url, string username, string token, string salt) {
|
||
|
this.url = url;
|
||
|
this.parameters = @"u=$(Uri.escape_string(username))&t=$(Uri.escape_string(token))&s=$(Uri.escape_string(salt))&v=1.16.1&c=eu.callcc.audrey";
|
||
|
|
||
|
this.session = new Soup.Session ();
|
||
|
this.session.user_agent = Audrey.Const.user_agent;
|
||
|
}
|
||
|
|
||
|
private void unwrap_response (Json.Reader reader) throws GLib.Error {
|
||
|
reader.read_member ("subsonic-response");
|
||
|
|
||
|
reader.read_member ("status");
|
||
|
if (reader.get_string_value () != "ok") {
|
||
|
reader.end_member ();
|
||
|
reader.read_member ("error");
|
||
|
reader.read_member ("message");
|
||
|
throw new Subsonic.Error.ERROR (reader.get_string_value () ?? "???");
|
||
|
}
|
||
|
reader.end_member();
|
||
|
}
|
||
|
|
||
|
public async void ping () throws GLib.Error {
|
||
|
var msg = new Soup.Message ("GET", @"$(this.url)/rest/ping?f=json&$(this.parameters)");
|
||
|
if (msg == null) {
|
||
|
throw new Subsonic.Error.BAD_AUTHN ("Bad message");
|
||
|
}
|
||
|
|
||
|
var bytes = yield this.session.send_and_read_async (msg, Priority.DEFAULT, null);
|
||
|
assert (msg.get_status () == Soup.Status.OK);
|
||
|
|
||
|
var parser = new Json.Parser ();
|
||
|
parser.load_from_data ((string) bytes.get_data ());
|
||
|
|
||
|
var reader = new Json.Reader (parser.get_root ());
|
||
|
this.unwrap_response (reader);
|
||
|
}
|
||
|
|
||
|
public async void scrobble (string id) throws GLib.Error {
|
||
|
var msg = new Soup.Message ("GET", @"$(this.url)/rest/scrobble?id=$(Uri.escape_string (id))&f=json&$(this.parameters)");
|
||
|
assert (msg != null);
|
||
|
|
||
|
var bytes = yield this.session.send_and_read_async (msg, Priority.DEFAULT, null);
|
||
|
assert (msg.get_status () == Soup.Status.OK);
|
||
|
|
||
|
var parser = new Json.Parser ();
|
||
|
parser.load_from_data ((string) bytes.get_data ());
|
||
|
|
||
|
var reader = new Json.Reader (parser.get_root ());
|
||
|
this.unwrap_response (reader);
|
||
|
}
|
||
|
|
||
|
public async void get_random_songs (string? parameters, SongCallback callback) throws GLib.Error {
|
||
|
string str_parameters;
|
||
|
if (parameters == null) {
|
||
|
str_parameters = "";
|
||
|
} else {
|
||
|
str_parameters = @"$parameters&";
|
||
|
}
|
||
|
|
||
|
var msg = new Soup.Message("GET", @"$(this.url)/rest/getRandomSongs?$(str_parameters)size=500&f=json&$(this.parameters)");
|
||
|
assert (msg != null);
|
||
|
|
||
|
var bytes = yield this.session.send_and_read_async (msg, Priority.DEFAULT, null);
|
||
|
assert (msg.get_status () == Soup.Status.OK);
|
||
|
|
||
|
var parser = new Json.Parser ();
|
||
|
parser.load_from_data ((string) bytes.get_data ());
|
||
|
|
||
|
var reader = new Json.Reader (parser.get_root ());
|
||
|
this.unwrap_response (reader);
|
||
|
|
||
|
reader.read_member ("randomSongs");
|
||
|
reader.read_member ("song");
|
||
|
|
||
|
for (int i = 0; i < reader.count_elements (); i += 1) {
|
||
|
reader.read_element (i);
|
||
|
callback (Song (reader));
|
||
|
reader.end_element ();
|
||
|
|
||
|
Idle.add (this.get_random_songs.callback);
|
||
|
yield;
|
||
|
}
|
||
|
|
||
|
assert (reader.get_error () == null);
|
||
|
}
|
||
|
|
||
|
public string stream_uri (string id) {
|
||
|
return @"$(this.url)/rest/stream?id=$(Uri.escape_string(id))&$(this.parameters)";
|
||
|
}
|
||
|
|
||
|
public string cover_art_uri (string id, int size = -1) {
|
||
|
if (size >= 0) {
|
||
|
return @"$(this.url)/rest/getCoverArt?id=$(Uri.escape_string(id))&$(this.parameters)";
|
||
|
} else {
|
||
|
return @"$(this.url)/rest/getCoverArt?size=$size&id=$(Uri.escape_string(id))&$(this.parameters)";
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public async Gdk.Pixbuf cover_art (
|
||
|
string id,
|
||
|
int size = -1,
|
||
|
int priority = Priority.DEFAULT,
|
||
|
Cancellable? cancellable = null
|
||
|
)
|
||
|
throws GLib.Error
|
||
|
{
|
||
|
var msg = new Soup.Message("GET", this.cover_art_uri (id, size));
|
||
|
assert (msg != null);
|
||
|
|
||
|
var stream = yield this.session.send_async (msg, priority, cancellable);
|
||
|
if (msg.get_status () != Soup.Status.OK) {
|
||
|
warning ("could not load cover art for %s: %s", id, Soup.Status.get_phrase (msg.get_status ()));
|
||
|
}
|
||
|
return yield new Gdk.Pixbuf.from_stream_async (stream, cancellable);
|
||
|
}
|
||
|
|
||
|
~Client () {
|
||
|
debug ("destroying subsonic client");
|
||
|
}
|
||
|
}
|