public errordomain Audrey.Subsonic.Error { BAD_AUTHN, ERROR, } public delegate void Audrey.Subsonic.SongCallback (Song song); public class Audrey.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 Audrey.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 Audrey.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 Audrey.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"); } }