1 /*
2    Copyright 2009 Last.fm Ltd.
3 
4    This file is part of liblastfm.
5 
6    liblastfm is free software: you can redistribute it and/or modify
7    it under the terms of the GNU General Public License as published by
8    the Free Software Foundation, either version 3 of the License, or
9    (at your option) any later version.
10 
11    liblastfm is distributed in the hope that it will be useful,
12    but WITHOUT ANY WARRANTY; without even the implied warranty of
13    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14    GNU General Public License for more details.
15 
16    You should have received a copy of the GNU General Public License
17    along with liblastfm.  If not, see <http://www.gnu.org/licenses/>.
18 */
19 
20 #include "RadioStation.h"
21 #include "XmlQuery.h"
22 
23 #include <QDebug>
24 #include <QRegExp>
25 #include <QStringList>
26 
27 
28 const float k_defaultRep(0.5);
29 const float k_defaultMainstr(0.5);
30 const bool k_defaultDisco(false);
31 
32 class lastfm::RadioStationData : public QSharedData
33 {
34 public:
35     QUrl m_url;
36     QString m_title;
37     QString m_tagFilter;
38 
39     float m_rep;
40     float m_mainstr;
41     bool m_disco;
42 };
43 
44 
RadioStation()45 lastfm::RadioStation::RadioStation()
46     : d( new RadioStationData )
47 {
48 }
49 
RadioStation(const QString & s)50 lastfm::RadioStation::RadioStation( const QString& s )
51     : d( new RadioStationData )
52 {
53     // If it's a tag filtered station then extract that part
54     QString tempString = s;
55 
56     if ( !tempString.startsWith("lastfm://tag/") )
57     {
58         int index = tempString.indexOf("/tag/");
59 
60         if ( index != -1 )
61         {
62             d->m_tagFilter = tempString.mid( index + 5, tempString.count() - (index + 5) );
63             tempString = tempString.mid( 0, index );
64         }
65     }
66 
67     d->m_url = tempString;
68 }
69 
RadioStation(const RadioStation & other)70 lastfm::RadioStation::RadioStation( const RadioStation& other )
71     : d(other.d)
72 {
73 }
74 
75 lastfm::RadioStation&
operator =(const RadioStation & that)76 lastfm::RadioStation::operator=( const RadioStation& that )
77 {
78     d = that.d;
79     return *this;
80 }
81 
~RadioStation()82 lastfm::RadioStation::~RadioStation()
83 {
84 }
85 
86 lastfm::RadioStation
library(const lastfm::User & user)87 lastfm::RadioStation::library( const lastfm::User& user )
88 {
89     QList<lastfm::User> users;
90     users << user;
91     return library( users );
92 }
93 
94 lastfm::RadioStation
library(QList<lastfm::User> & users)95 lastfm::RadioStation::library( QList<lastfm::User>& users )
96 {
97     qSort(users.begin(), users.end());
98 
99     QString url = (users.count() > 1) ? "lastfm://users/" : "lastfm://user/";
100 
101     url.append( users[0].name() );
102 
103     for ( int i = 1 ; i < users.count() ; ++i )
104         url.append( "," + users[i].name() );
105 
106     url.append("/personal");
107 
108     RadioStation s( url );
109     if( users.count() != 1 )
110     {
111         QString title;
112 
113         for( QList<lastfm::User>::const_iterator i = users.constBegin(); i != users.constEnd(); i++ )
114         {
115             if( i == users.constEnd() - 1 )
116                 title += " and " + *i;
117             else
118                 title += ", " + *i;
119         }
120 
121         s.setTitle( title );
122     }
123 
124     return s;
125 }
126 
127 
128 lastfm::RadioStation
recommendations(const lastfm::User & user)129 lastfm::RadioStation::recommendations( const lastfm::User& user )
130 {
131     return RadioStation( "lastfm://user/" + user + "/recommended" );
132 }
133 
134 lastfm::RadioStation
friends(const lastfm::User & user)135 lastfm::RadioStation::friends( const lastfm::User& user )
136 {
137     return RadioStation( "lastfm://user/" + user + "/friends" );
138 }
139 
140 lastfm::RadioStation
neighbourhood(const lastfm::User & user)141 lastfm::RadioStation::neighbourhood( const lastfm::User& user )
142 {
143     return RadioStation( "lastfm://user/" + user + "/neighbours" );
144 }
145 
146 
147 lastfm::RadioStation
tag(const lastfm::Tag & tag)148 lastfm::RadioStation::tag( const lastfm::Tag& tag )
149 {
150     QList<lastfm::Tag> tags;
151     tags << tag;
152     return lastfm::RadioStation::tag( tags );
153 }
154 
155 
156 lastfm::RadioStation
tag(QList<lastfm::Tag> & tag)157 lastfm::RadioStation::tag( QList<lastfm::Tag>& tag )
158 {
159     qSort(tag.begin(), tag.end());
160 
161     QString url = (tag.count() > 1) ? "lastfm://tag/" : "lastfm://globaltags/";
162 
163     url.append( tag[0].name() );
164 
165     for ( int i = 1 ; i < tag.count() ; ++i )
166         url.append( "*" + tag[i].name() );
167 
168     return RadioStation( url );
169 }
170 
171 
172 lastfm::RadioStation
similar(const lastfm::Artist & artist)173 lastfm::RadioStation::similar( const lastfm::Artist& artist )
174 {
175     QList<lastfm::Artist> artists;
176     artists << artist;
177     return similar( artists );
178 }
179 
180 
181 lastfm::RadioStation
similar(QList<lastfm::Artist> & artists)182 lastfm::RadioStation::similar( QList<lastfm::Artist>& artists )
183 {
184     qSort(artists.begin(), artists.end());
185 
186     QString url = (artists.count() > 1) ? "lastfm://artistnames/" : "lastfm://artist/";
187 
188     url.append( artists[0].name() );
189 
190     for ( int i = 1 ; i < artists.count() ; ++i )
191         url.append( "," + artists[i].name() );
192 
193     if (artists.count() == 1)
194         url.append( "/similarartists" );
195 
196     return RadioStation( url );
197 }
198 
199 
200 lastfm::RadioStation
mix(const lastfm::User & user)201 lastfm::RadioStation::mix( const lastfm::User& user )
202 {
203     return RadioStation( "lastfm://user/" + user + "/mix" );
204 }
205 
206 
207 QString
url() const208 lastfm::RadioStation::url() const
209 {
210     return d->m_url.toString() + (d->m_tagFilter.isEmpty() ? "" : "/tag/" + d->m_tagFilter);
211 }
212 
213 
214 void
setTitle(const QString & title)215 lastfm::RadioStation::setTitle( const QString& title )
216 {
217     // Stop the radio station getting renamed when the web services don't know what it's called
218     if ( !d->m_title.isEmpty() && title.compare( "a radio station", Qt::CaseInsensitive ) == 0 )
219         return;
220 
221     // do not rename the current user's stations if they already have a name
222     if ( !d->m_title.isEmpty() && d->m_url.toString().startsWith( "lastfm://user/" + User().name() ) )
223         return;
224 
225     d->m_title = title.trimmed();
226 }
227 
228 void
setUrl(const QString & url)229 lastfm::RadioStation::setUrl( const QString& url )
230 {
231     d->m_url = url;
232 }
233 
234 
235 QString
title() const236 lastfm::RadioStation::title() const
237 {
238     return d->m_title; // + (d->m_tagFilter.isEmpty() ? "" : ": " + d->m_tagFilter);
239 }
240 
241 
242 void
setTagFilter(const QString & tag)243 lastfm::RadioStation::setTagFilter( const QString& tag )
244 {
245     d->m_tagFilter = tag;
246 }
247 
248 
249 QNetworkReply*
getSampleArtists(int limit) const250 lastfm::RadioStation::getSampleArtists( int limit ) const
251 {
252     QMap<QString, QString> map;
253     map["method"] = "radio.getSampleArtists";
254     map["station"] = d->m_url.toString();
255     map["limit"] = QString::number( limit );
256     return ws::get( map );
257 }
258 
259 
260 QNetworkReply*
getTagSuggestions(int limit) const261 lastfm::RadioStation::getTagSuggestions( int limit ) const
262 {
263     QMap<QString, QString> map;
264     map["method"] = "radio.getTagSuggestions";
265     map["station"] = d->m_url.toString();
266     map["limit"] = QString::number( limit );
267     return ws::get( map );
268 }
269 
270 
271 bool
isLegacyPlaylist() const272 lastfm::RadioStation::isLegacyPlaylist() const
273 {
274     return d->m_url.toString().startsWith( "lastfm://play/" ) ||
275            d->m_url.toString().startsWith( "lastfm://preview/" ) ||
276            d->m_url.toString().startsWith( "lastfm://track/" ) ||
277            d->m_url.toString().startsWith( "lastfm://playlist/" );
278 }
279 
280 
281 //static
282 QList<lastfm::RadioStation>
list(QNetworkReply * r)283 lastfm::RadioStation::list( QNetworkReply* r )
284 {
285     QList<lastfm::RadioStation> result;
286     XmlQuery lfm;
287 
288     if ( lfm.parse( r ) )
289     {
290 
291         foreach (XmlQuery xq, lfm.children("station"))
292         {
293             lastfm::RadioStation rs( QUrl::fromPercentEncoding( xq["url"].text().toUtf8() ) );
294             rs.setTitle(xq["name"].text());
295             result.append(rs);
296         }
297     }
298     else
299     {
300         qWarning() << lfm.parseError().message();
301     }
302 
303     return result;
304 }
305 
306 
307 bool
operator ==(const RadioStation & that) const308 lastfm::RadioStation::operator==( const RadioStation& that ) const
309 {
310     return this->d->m_url == that.d->m_url && this->d->m_tagFilter == that.d->m_tagFilter;
311 }
312 
313 
314 void
setRep(float rep)315 lastfm::RadioStation::setRep(float rep)
316 {
317     d->m_rep = rep;
318 }
319 
320 
321 void
setMainstr(float mainstr)322 lastfm::RadioStation::setMainstr(float mainstr)
323 {
324     d->m_mainstr = mainstr;
325 }
326 
327 
328 void
setDisco(bool disco)329 lastfm::RadioStation::setDisco(bool disco)
330 {
331     d->m_disco = disco;
332 }
333 
334 
rep() const335 float lastfm::RadioStation::rep() const
336 {
337     return d->m_rep;
338 }
339 
340 
mainstr() const341 float lastfm::RadioStation::mainstr() const
342 {
343     return d->m_mainstr;
344 }
345 
346 
disco() const347 bool lastfm::RadioStation::disco() const
348 {
349     return d->m_disco;
350 }
351 
352 
operator <<(QDebug d,const lastfm::RadioStation & station)353 QDebug operator<<( QDebug d, const lastfm::RadioStation& station )
354 {
355     return d << station.url();
356 }
357