2024-10-06 11:21:53 +00:00
|
|
|
/* application.vala
|
|
|
|
*
|
|
|
|
* Copyright 2024 Erica Z
|
|
|
|
*
|
|
|
|
* This program is free software: you can redistribute it and/or modify
|
|
|
|
* it under the terms of the GNU Affero General Public License as published by
|
|
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
|
|
* (at your option) any later version.
|
|
|
|
*
|
|
|
|
* This program is distributed in the hope that it will be useful,
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
* GNU Affero General Public License for more details.
|
|
|
|
*
|
|
|
|
* You should have received a copy of the GNU Affero General Public License
|
|
|
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
|
|
*
|
|
|
|
* SPDX-License-Identifier: AGPL-3.0-or-later
|
|
|
|
*/
|
|
|
|
|
|
|
|
errordomain SubsonicError {
|
2024-10-10 09:53:52 +00:00
|
|
|
ERROR
|
2024-10-06 11:21:53 +00:00
|
|
|
}
|
|
|
|
|
2024-10-10 10:51:12 +00:00
|
|
|
public delegate void Wavelet.SongCallback (Song song);
|
|
|
|
|
2024-10-06 11:21:53 +00:00
|
|
|
public class Wavelet.Artist : Object, Json.Serializable {
|
|
|
|
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 DateTime? starred;
|
|
|
|
|
|
|
|
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 ();
|
|
|
|
|
|
|
|
reader.read_member ("starred");
|
|
|
|
if (reader.is_value ()) {
|
|
|
|
this.starred = new DateTime.from_iso8601 (reader.get_string_value (), null);
|
|
|
|
}
|
|
|
|
reader.end_member ();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public class Wavelet.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 class Wavelet.Song : Object {
|
|
|
|
public string id { get; private set; }
|
|
|
|
public string title { get; private set; }
|
|
|
|
public string album { get; private set; }
|
|
|
|
public string artist { get; private set; }
|
|
|
|
public int64 track { get; private set; }
|
2024-10-11 07:53:30 +00:00
|
|
|
public int64 year { get; private set; }
|
2024-10-06 11:21:53 +00:00
|
|
|
|
|
|
|
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 ();
|
2024-10-11 07:53:30 +00:00
|
|
|
|
|
|
|
reader.read_member ("year");
|
|
|
|
this.year = reader.get_int_value ();
|
|
|
|
reader.end_member ();
|
2024-10-06 11:21:53 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-10-10 09:53:52 +00:00
|
|
|
public struct Wavelet.API.PlayQueue {
|
|
|
|
public string current;
|
|
|
|
public int64 position;
|
|
|
|
public DateTime changed;
|
|
|
|
public string changed_by;
|
|
|
|
public ListStore songs;
|
|
|
|
|
|
|
|
internal PlayQueue.from_reader (Json.Reader reader) {
|
|
|
|
reader.read_member ("current");
|
|
|
|
this.current = reader.get_string_value ();
|
|
|
|
reader.end_member ();
|
|
|
|
|
|
|
|
reader.read_member ("position");
|
|
|
|
this.position = reader.get_int_value ();
|
|
|
|
reader.end_member ();
|
|
|
|
|
|
|
|
reader.read_member ("changed");
|
|
|
|
this.changed = new DateTime.from_iso8601 (reader.get_string_value (), null);
|
|
|
|
reader.end_member ();
|
|
|
|
|
|
|
|
reader.read_member ("changed_by");
|
|
|
|
this.changed_by = reader.get_string_value ();
|
|
|
|
reader.end_member ();
|
|
|
|
|
2024-10-10 10:51:12 +00:00
|
|
|
//print("%s %lli %s %s\n",this.current, this.position, this.changed, this.changed_by);
|
2024-10-10 09:53:52 +00:00
|
|
|
|
|
|
|
this.songs = new ListStore (typeof (Song));
|
|
|
|
|
|
|
|
reader.read_member ("song");
|
|
|
|
for (int i = 0; i < reader.count_elements (); i += 1) {
|
|
|
|
reader.read_element (i);
|
|
|
|
this.songs.append (new Song (reader));
|
|
|
|
reader.end_element ();
|
|
|
|
}
|
|
|
|
reader.end_member ();
|
|
|
|
assert (reader.get_error () == null);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-10-06 11:21:53 +00:00
|
|
|
public class Wavelet.Subsonic : Object {
|
|
|
|
public ListStore artist_list;
|
|
|
|
public ListStore album_list;
|
|
|
|
public ListStore song_list;
|
|
|
|
|
|
|
|
private Soup.Session session;
|
|
|
|
private string url;
|
|
|
|
private string parameters;
|
|
|
|
|
|
|
|
public Subsonic.with_password (string url, string username, string password) {
|
|
|
|
this.url = url;
|
|
|
|
this.parameters = @"u=$(Uri.escape_string(username))&p=$(Uri.escape_string(password))&v=1.16.1&c=eu.callcc.Wavelet&f=json";
|
|
|
|
|
|
|
|
this.session = new Soup.Session ();
|
|
|
|
|
|
|
|
this.artist_list = new ListStore (typeof (Artist));
|
|
|
|
this.album_list = new ListStore (typeof (Album));
|
|
|
|
this.song_list = new ListStore (typeof (Song));
|
|
|
|
}
|
|
|
|
|
|
|
|
public Subsonic.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.Wavelet&f=json";
|
|
|
|
|
|
|
|
this.session = new Soup.Session ();
|
|
|
|
|
|
|
|
this.artist_list = new ListStore (typeof (Artist));
|
|
|
|
this.album_list = new ListStore (typeof (Album));
|
|
|
|
this.song_list = new ListStore (typeof (Song));
|
|
|
|
}
|
|
|
|
|
|
|
|
private void unwrap_response (Json.Reader reader) throws 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");
|
2024-10-10 09:53:52 +00:00
|
|
|
throw new SubsonicError.ERROR (reader.get_string_value () ?? "???");
|
2024-10-06 11:21:53 +00:00
|
|
|
}
|
|
|
|
reader.end_member();
|
|
|
|
}
|
|
|
|
|
|
|
|
public async void ping () throws Error {
|
|
|
|
var msg = new Soup.Message ("GET", @"$(this.url)/rest/ping?$(this.parameters)");
|
|
|
|
var bytes = yield this.session.send_and_read_async (msg, Priority.DEFAULT, null);
|
2024-10-10 10:51:12 +00:00
|
|
|
assert (msg.get_status () == Soup.Status.OK);
|
2024-10-06 11:21:53 +00:00
|
|
|
|
|
|
|
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 signal void done_reloading ();
|
|
|
|
|
|
|
|
private int remaining_artists;
|
|
|
|
private int remaining_albums;
|
|
|
|
|
|
|
|
public async void reload () throws Error {
|
|
|
|
this.artist_list.remove_all ();
|
|
|
|
this.album_list.remove_all ();
|
|
|
|
this.song_list.remove_all ();
|
|
|
|
|
|
|
|
this.remaining_artists = 0;
|
|
|
|
this.remaining_albums = 0;
|
|
|
|
|
|
|
|
var msg = new Soup.Message ("GET", @"$(this.url)/rest/getArtists?$(this.parameters)");
|
|
|
|
var bytes = yield this.session.send_and_read_async (msg, Priority.DEFAULT, null);
|
|
|
|
|
|
|
|
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 ("artists");
|
|
|
|
reader.read_member ("index");
|
|
|
|
|
|
|
|
for (int i = 0; i < reader.count_elements (); i += 1) {
|
|
|
|
//for (int i = 0; i < 1; i += 1) {
|
|
|
|
reader.read_element (i);
|
|
|
|
|
|
|
|
reader.read_member ("name");
|
|
|
|
var index = reader.get_string_value ();
|
|
|
|
reader.end_member ();
|
|
|
|
|
|
|
|
reader.read_member ("artist");
|
|
|
|
for (int j = 0; j < reader.count_elements (); j += 1) {
|
|
|
|
reader.read_element (j);
|
|
|
|
|
|
|
|
var artist = new Artist (index, reader);
|
|
|
|
artist_list.append (artist);
|
|
|
|
|
|
|
|
this.remaining_artists += 1;
|
|
|
|
reload_artist.begin (artist.id);
|
|
|
|
|
|
|
|
reader.end_element ();
|
|
|
|
}
|
|
|
|
reader.end_member ();
|
|
|
|
|
|
|
|
reader.end_element ();
|
|
|
|
|
|
|
|
assert (reader.get_error () == null);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private async void reload_artist (string artist_id) throws Error {
|
|
|
|
try {
|
|
|
|
var msg = new Soup.Message ("GET", @"$(this.url)/rest/getArtist?id=$(artist_id)&$(this.parameters)");
|
|
|
|
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 ("artist");
|
|
|
|
reader.read_member ("album");
|
|
|
|
|
|
|
|
for (int i = 0; i < reader.count_elements (); i += 1) {
|
|
|
|
reader.read_element (i);
|
|
|
|
|
|
|
|
var album = new Album (reader);
|
|
|
|
album_list.append (album);
|
|
|
|
|
|
|
|
this.remaining_albums += 1;
|
|
|
|
reload_album.begin (album.id);
|
|
|
|
|
|
|
|
reader.end_element ();
|
|
|
|
}
|
|
|
|
|
|
|
|
assert (reader.get_error () == null);
|
|
|
|
} catch (Error e) {
|
|
|
|
warning ("could not reload artist %s: %s\n", artist_id, e.message);
|
|
|
|
} finally {
|
|
|
|
this.remaining_artists -= 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private async void reload_album (string album_id) throws Error {
|
|
|
|
try {
|
|
|
|
var msg = new Soup.Message ("GET", @"$(this.url)/rest/getAlbum?id=$(album_id)&$(this.parameters)");
|
|
|
|
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 ("album");
|
|
|
|
reader.read_member ("song");
|
|
|
|
|
|
|
|
for (int i = 0; i < reader.count_elements (); i += 1) {
|
|
|
|
reader.read_element (i);
|
|
|
|
|
|
|
|
var song = new Song (reader);
|
|
|
|
song_list.append (song);
|
|
|
|
|
|
|
|
reader.end_element ();
|
|
|
|
}
|
|
|
|
|
|
|
|
assert (reader.get_error () == null);
|
|
|
|
} catch (Error e) {
|
|
|
|
warning ("could not reload album %s: %s\n", album_id, e.message);
|
|
|
|
} finally {
|
|
|
|
this.remaining_albums -= 1;
|
|
|
|
if (this.remaining_artists == 0 && this.remaining_albums == 0) {
|
|
|
|
this.done_reloading ();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2024-10-10 10:51:12 +00:00
|
|
|
|
|
|
|
public async void get_random_songs (string? parameters, SongCallback callback) throws 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&$(this.parameters)");
|
|
|
|
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 (new Song (reader));
|
|
|
|
reader.end_element ();
|
|
|
|
}
|
|
|
|
|
|
|
|
assert (reader.get_error () == null);
|
|
|
|
}
|
2024-10-10 20:04:55 +00:00
|
|
|
|
|
|
|
public string stream_uri (string id) {
|
|
|
|
return @"$(this.url)/rest/stream?id=$(id)&$(this.parameters)";
|
|
|
|
}
|
2024-10-06 11:21:53 +00:00
|
|
|
}
|