/
RadioBrowserDirectory.vala
247 lines (212 loc) · 7.42 KB
/
RadioBrowserDirectory.vala
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
/*
* Copyright (c) 2020 Louis Brauer (https://github.com/louis77)
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public
* License as published by the Free Software Foundation; either
* version 2 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
* General Public License for more details.
*
* You should have received a copy of the GNU General Public
* License along with this program; if not, write to the
* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301 USA
*
* Authored by: Louis Brauer <louis@brauer.family>
*/
using Gee;
// TODO: handle failures
// TODO: use async
namespace Tuner.RadioBrowser {
public errordomain DataError {
PARSE_DATA
}
public enum SortOrder {
NAME,
URL,
HOMEPAGE,
FAVICON,
TAGS,
COUNTRY,
STATE,
LANGUAGE,
VOTES,
CODEC,
BITRATE,
LASTCHECKOK,
LASTCHECKTIME,
CLICKTIMESTAMP,
CLICKCOUNT,
CLICKTREND,
RANDOM;
public string to_string () {
switch (this) {
case NAME:
return "name";
case URL:
return "url";
case HOMEPAGE:
return "homepage";
case FAVICON:
return "favicon";
case TAGS:
return "tags";
case COUNTRY:
return "country";
case STATE:
return "state";
case LANGUAGE:
return "language";
case VOTES:
return "votes";
case CODEC:
return "codec";
case BITRATE:
return "bitrate";
case LASTCHECKOK:
return "lastcheckok";
case LASTCHECKTIME:
return "lastchecktime";
case CLICKTIMESTAMP:
return "clicktimestamp";
case CLICKCOUNT:
return "clickcount";
case CLICKTREND:
return "clicktrend";
case RANDOM:
return "random";
default:
assert_not_reached ();
}
}
}
public class Station : Object {
public string stationuuid { get; set; }
public string name { get; set; }
public string url_resolved { get; set; }
public string country { get; set; }
public string favicon { get; set; }
public uint clickcount { get; set; }
}
public class Tag : Object {
public string name { get; set; }
public uint stationcount { get; set; }
}
public class Client : Object {
// TODO: choose local server from server list
private const string API_BASE_URL = "https://de1.api.radio-browser.info";
private Soup.Session _session;
public Client() {
_session = new Soup.Session ();
// TODO: Automatically generate this
_session.user_agent = "com.github.louis77.tuner/1.1";
/*
Resolver resolver = Resolver.get_default ();
GLib.ArrayList<InetAddress> addresses = resolver.lookup_by_name ("all.api.radio-browser.info");
foreach (var address in addresses) {
var host = resolver.lookup_by_address (address);
debug (@"Found RB host: $address with name $host");
}
*/
}
private Station jnode_to_station (Json.Node node) {
return Json.gobject_deserialize (typeof (Station), node) as Station;
}
private ArrayList<Station> jarray_to_stations (Json.Array data) {
var stations = new ArrayList<Station> ();
data.foreach_element ((array, index, element) => {
Station s = jnode_to_station (element);
stations.add (s);
});
return stations;
}
private Tag jnode_to_tag (Json.Node node) {
return Json.gobject_deserialize (typeof (Tag), node) as Tag;
}
private ArrayList<Tag> jarray_to_tags (Json.Array data) {
var tags = new ArrayList<Tag> ();
data.foreach_element ((array, index, element) => {
Tag s = jnode_to_tag (element);
tags.add (s);
});
return tags;
}
public void track (string stationuuid) {
debug (@"sending listening event for station $stationuuid");
var resource = @"json/url/$stationuuid";
var message = new Soup.Message ("GET", @"$API_BASE_URL/$resource");
var response_code = _session.send_message (message);
debug (@"response: $(response_code)");
}
public void vote (string stationuuid) {
debug (@"sending vote event for station $stationuuid");
var resource = @"json/vote/$stationuuid)";
var message = new Soup.Message ("GET", @"$API_BASE_URL/$resource");
var response_code = _session.send_message (message);
debug (@"response: $(response_code)");
}
public ArrayList<Station> get_stations (string resource) throws DataError {
debug (@"RB $resource");
var message = new Soup.Message ("GET", @"$API_BASE_URL/$resource");
Json.Node rootnode;
var response_code = _session.send_message (message);
debug (@"response from radio-browser.info: $response_code");
var body = (string) message.response_body.data;
try {
rootnode = Json.from_string (body);
} catch (Error e) {
throw new DataError.PARSE_DATA (@"unable to parse JSON response: $(e.message)");
}
var rootarray = rootnode.get_array ();
var stations = jarray_to_stations (rootarray);
return stations;
}
public ArrayList<Station> search (string? name,
string[] tags,
uint rowcount,
SortOrder order,
bool reverse = false,
uint offset = 0) {
var resource = @"json/stations/search?limit=$rowcount&order=$order&offset=$offset";
if (name != null && name != "") {
resource += @"&name=$name";
}
if (tags != null ) {
string tag_list = tags[0];
if (tags.length > 1) {
tag_list = string.joinv (",", tags);
}
resource += @"&tagList=$tag_list&tagExact=true";
}
if (order != SortOrder.RANDOM) {
// random and reverse doesn't make sense
resource += @"&reverse=$reverse";
}
return get_stations (resource);
}
public ArrayList<Station> by_uuid (string uuid) {
var resource = @"json/stations/byuuid/$uuid";
return get_stations (resource);
}
public ArrayList<Tag> get_tags () {
var resource = @"json/tags";
var message = new Soup.Message ("GET", @"$API_BASE_URL/$resource");
Json.Node rootnode;
var response_code = _session.send_message (message);
debug (@"response from radio-browser.info: $response_code");
var body = (string) message.response_body.data;
try {
rootnode = Json.from_string (body);
} catch (Error e) {
throw new DataError.PARSE_DATA (@"unable to parse JSON response: $(e.message)");
}
var rootarray = rootnode.get_array ();
var tags = jarray_to_tags (rootarray);
return tags;
}
}
}