Codebase list liblastfm / b265fd9
Import Upstream version 1.0.3 Boyuan Yang 2 years ago
17 changed file(s) with 135 addition(s) and 86 deletion(s). Raw diff Collapse all Expand all
33 set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules)
44
55 # general settings
6 set(LASTFM_VERSION 1.0.2)
6 set(LASTFM_VERSION 1.0.3)
77 set(LASTFM_SOVERSION 1)
88
99 # options
0 Join us for chats on IRC!
1
2 Server: irc.last.fm
3 Channel: #last.desktop
4
05 # liblastfm
16
27 liblastfm is a collection of libraries to help you integrate Last.fm services
38 into your rich desktop software. It is officially supported software developed
49 by Last.fm staff.
510
6 Michael Coffey http://twitter.com/eartle
11 Michael Coffey http://twitter.com/eartle
712
813 Fork it: http://github.com/lastfm/liblastfm
914
185185
186186 XmlQuery lfm;
187187
188 if ( lfm.parse( r->readAll() ) )
188 if ( lfm.parse( r ) )
189189 {
190190 foreach (XmlQuery e, lfm.children( "artist" ))
191191 {
209209 try
210210 {
211211 XmlQuery lfm;
212 lfm.parse( r->readAll() );
212 lfm.parse( r );
213213 foreach (XmlQuery e, lfm.children( "track" ))
214214 {
215215 tracks << e["name"].text();
229229 QList<Artist> artists;
230230 XmlQuery lfm;
231231
232 if ( lfm.parse( r->readAll() ) )
232 if ( lfm.parse( r ) )
233233 {
234234 foreach (XmlQuery xq, lfm.children( "artist" ))
235235 {
250250 {
251251 XmlQuery lfm;
252252
253 if ( lfm.parse( r->readAll() ) )
253 if ( lfm.parse( r ) )
254254 {
255255 Artist artist = Artist( lfm["artist"] );
256256 return artist;
9292 lastfm::Audioscrobbler::cacheBatch( const QList<lastfm::Track>& tracks, const QString& )
9393 {
9494 d->m_cache.add( tracks );
95
96 foreach ( const Track& track, d->m_cache.tracks() )
97 MutableTrack( track ).setScrobbleStatus( Track::Cached );
98
9995 emit scrobblesCached( tracks );
100
10196 submit();
97 }
98
99 void
100 lastfm::Audioscrobbler::cacheBatch( const QList<lastfm::Track>& tracks )
101 {
102 cacheBatch( tracks, "" );
102103 }
103104
104105
156157 {
157158 lastfm::XmlQuery lfm;
158159
159 if ( lfm.parse( static_cast<QNetworkReply*>(sender())->readAll() ) )
160 if ( lfm.parse( d->m_nowPlayingReply ) )
160161 {
161162 qDebug() << lfm;
162163
164165 d->parseTrack( lfm["nowplaying"], d->m_nowPlayingTrack );
165166 else
166167 emit nowPlayingError( lfm["error"].attribute("code").toInt(), lfm["error"].text() );
167
168 d->m_nowPlayingTrack = Track();
169 d->m_nowPlayingReply = 0;
170168 }
171169 else
172170 {
183181 {
184182 lastfm::XmlQuery lfm;
185183
186 if ( lfm.parse( d->m_scrobbleReply->readAll() ) )
184 if ( lfm.parse( d->m_scrobbleReply ) )
187185 {
188186 qDebug() << lfm;
189187
5656 void nowPlaying( const Track& );
5757 /** will cache the track and call submit() */
5858 void cache( const Track& );
59 void cacheBatch( const QList<lastfm::Track>&, const QString& id = "" );
59 void cacheBatch( const QList<lastfm::Track>&, const QString& id );
60 void cacheBatch( const QList<lastfm::Track>& );
6061
6162 /** will submit the submission cache for this user */
6263 void submit();
8181 QMap<float, Track> tracks;
8282 lastfm::XmlQuery lfm;
8383
84 if ( lfm.parse( reply->readAll() ) )
84 if ( lfm.parse( reply ) )
8585 {
8686 foreach ( const lastfm::XmlQuery& track, lfm["tracks"].children("track") )
8787 {
307307 QList<lastfm::RadioStation> result;
308308 XmlQuery lfm;
309309
310 if ( lfm.parse( r->readAll() ) )
310 if ( lfm.parse( r ) )
311311 {
312312
313313 foreach (XmlQuery xq, lfm.children("station"))
184184
185185 XmlQuery lfm;
186186
187 if ( lfm.parse( qobject_cast<QNetworkReply*>(sender())->readAll() ) )
187 if ( lfm.parse( qobject_cast<QNetworkReply*>(sender()) ) )
188188 {
189189 qDebug() << lfm;
190190
215215
216216 XmlQuery lfm;
217217
218 if ( lfm.parse( qobject_cast<QNetworkReply*>(sender())->readAll() ) )
218 if ( lfm.parse( qobject_cast<QNetworkReply*>(sender()) ) )
219219 {
220220 qDebug() << lfm;
221221
3030
3131 class lastfm::ScrobbleCachePrivate
3232 {
33 public:
34 enum Invalidity
35 {
36 TooShort,
37 ArtistNameMissing,
38 TrackNameMissing,
39 ArtistInvalid,
40 NoTimestamp,
41 FromTheFuture,
42 FromTheDistantPast
43 };
44
33 public:
4534 QString m_username;
4635 QString m_path;
4736 QList<Track> m_tracks;
4837
49 bool isValid( const Track& track, Invalidity* = 0 );
5038 void write(); /// writes m_tracks to m_path
5139 void read( QDomDocument& xml ); /// reads from m_path into m_tracks
5240
5341 };
54
55
56 bool
57 lastfm::ScrobbleCachePrivate::isValid( const lastfm::Track& track, Invalidity* v )
58 {
59 #define TEST( test, x ) \
60 if (test) { \
61 if (v) *v = x; \
62 return false; \
63 }
64
65 TEST( track.duration() < ScrobblePoint::scrobbleTimeMin(), TooShort );
66
67 TEST( !track.timestamp().isValid(), NoTimestamp );
68
69 // actual spam prevention is something like 12 hours, but we are only
70 // trying to weed out obviously bad data, server side criteria for
71 // "the future" may change, so we should let the server decide, not us
72 TEST( track.timestamp() > QDateTime::currentDateTime().addMonths( 1 ), FromTheFuture );
73
74 TEST( track.timestamp() < QDateTime::fromString( "2003-01-01", Qt::ISODate ), FromTheDistantPast );
75
76 // Check if any required fields are empty
77 TEST( track.artist().isNull(), ArtistNameMissing );
78 TEST( track.title().isEmpty(), TrackNameMissing );
79
80 TEST( (QStringList() << "unknown artist"
81 << "unknown"
82 << "[unknown]"
83 << "[unknown artist]").contains( track.artist().name().toLower() ),
84 ArtistInvalid );
85
86 return true;
87 }
8842
8943
9044 ScrobbleCache::ScrobbleCache( const QString& username )
175129 {
176130 foreach (const Track& track, tracks)
177131 {
178 ScrobbleCachePrivate::Invalidity invalidity;
132 Invalidity invalidity;
179133
180 if ( !d->isValid( track, &invalidity ) )
134 if ( !isValid( track, &invalidity ) )
181135 {
182136 qWarning() << invalidity;
137 MutableTrack mt = MutableTrack( track );
138 mt.setScrobbleStatus( Track::Error );
139 mt.setScrobbleErrorText( QObject::tr( "Invalid" ) );
183140 }
184141 else if (track.isNull())
185142 qDebug() << "Will not cache an empty track";
186143 else
187 d->m_tracks += track;
144 {
145 bool ok;
146 int plays = track.extra( "playCount" ).toInt( &ok );
147 if ( !ok ) plays = 1;
148
149 for ( int i = 0 ; i < plays ; ++i )
150 d->m_tracks += track;
151
152 MutableTrack( track ).setScrobbleStatus( Track::Cached );
153 }
188154 }
189155
190156 d->write();
210176 }
211177
212178
179 bool
180 ScrobbleCache::isValid( const lastfm::Track& track, Invalidity* v )
181 {
182 #define TEST( test, x ) \
183 if (test) { \
184 if (v) *v = x; \
185 return false; \
186 }
187
188 TEST( track.duration() < ScrobblePoint::scrobbleTimeMin(), ScrobbleCache::TooShort );
189
190 TEST( !track.timestamp().isValid(), ScrobbleCache::NoTimestamp );
191
192 // actual spam prevention is something like 12 hours, but we are only
193 // trying to weed out obviously bad data, server side criteria for
194 // "the future" may change, so we should let the server decide, not us
195 TEST( track.timestamp() > QDateTime::currentDateTime().addMonths( 1 ), ScrobbleCache::FromTheFuture );
196
197 TEST( track.timestamp().daysTo( QDateTime::currentDateTime() ) > 14, ScrobbleCache::FromTheDistantPast );
198
199 // Check if any required fields are empty
200 TEST( track.artist().isNull(), ScrobbleCache::ArtistNameMissing );
201 TEST( track.title().isEmpty(), ScrobbleCache::TrackNameMissing );
202
203 TEST( (QStringList() << "unknown artist"
204 << "unknown"
205 << "[unknown]"
206 << "[unknown artist]").contains( track.artist().name().toLower() ),
207 ScrobbleCache::ArtistInvalid );
208
209 return true;
210 }
211
212
213213 QList<lastfm::Track>
214214 ScrobbleCache::tracks() const
215215 {
2828 class LASTFM_DLLEXPORT ScrobbleCache
2929 {
3030 public:
31 enum Invalidity
32 {
33 TooShort,
34 ArtistNameMissing,
35 TrackNameMissing,
36 ArtistInvalid,
37 NoTimestamp,
38 FromTheFuture,
39 FromTheDistantPast
40 };
41
3142 explicit ScrobbleCache( const QString& username );
3243 ScrobbleCache( const ScrobbleCache& that );
3344 ~ScrobbleCache();
3849
3950 /** returns the number of tracks left in the queue */
4051 int remove( const QList<Track>& );
52
53 static bool isValid( const lastfm::Track& track, Invalidity* v = 0 );
4154
4255 ScrobbleCache& operator=( const ScrobbleCache& that );
4356
119119
120120 XmlQuery lfm;
121121
122 if ( lfm.parse( r->readAll() ) )
122 if ( lfm.parse( r ) )
123123 {
124124
125125 foreach ( XmlQuery xq, lfm.children("tag") )
269269 {
270270 XmlQuery lfm;
271271
272 if ( lfm.parse( static_cast<QNetworkReply*>(sender())->readAll() ) )
272 if ( lfm.parse( static_cast<QNetworkReply*>(sender()) ) )
273273 {
274274 if ( lfm.attribute( "status" ) == "ok")
275275 loved = Track::Loved;
285285 {
286286 XmlQuery lfm;
287287
288 if ( lfm.parse( static_cast<QNetworkReply*>(sender())->readAll() ) )
288 if ( lfm.parse( static_cast<QNetworkReply*>(sender()) ) )
289289 {
290290 if ( lfm.attribute( "status" ) == "ok")
291291 loved = Track::Unloved;
307307 break;
308308 }
309309 }
310
311 const QByteArray data = static_cast<QNetworkReply*>(sender())->readAll();
310
311 QNetworkReply* reply = static_cast<QNetworkReply*>(sender());
312 reply->deleteLater();
313 const QByteArray data = reply->readAll();
312314
313315 lastfm::XmlQuery lfm;
314316
654656 QMap<int, QPair< QString, QString > > tracks;
655657 try
656658 {
657 QByteArray b = r->readAll();
658659 XmlQuery lfm;
659660
660 if ( lfm.parse( b ) )
661 if ( lfm.parse( r ) )
661662 {
662663 foreach (XmlQuery e, lfm.children( "track" ))
663664 {
751752 if (tag.isEmpty())
752753 return 0;
753754 QMap<QString, QString> map = params( "removeTag" );
754 map["tags"] = tag;
755 map["tag"] = tag;
755756 return ws::post(map);
756757 }
757758
500500
501501 XmlQuery lfm;
502502
503 lfm.parse( r->readAll() );
503 lfm.parse( r );
504504 return UserList( lfm );
505505 }
506506
8888 QDomElement error = d->e.firstChildElement( "error" );
8989 uint const n = d->e.childNodes().count();
9090
91 // no elements beyond the lfm is perfectably acceptable <-- wtf?
91 // no elements beyond the lfm is perfectably acceptable for example when
92 // XmlQuery is used parse response of a POST request.
9293 // if (n == 0) // nothing useful in the response
9394 if (status == "failed" || (n == 1 && !error.isNull()) )
9495 d->error = error.isNull()
121122 return d->error.enumValue() == lastfm::ws::NoError;
122123 }
123124
124
125 lastfm::ws::ParseError XmlQuery::parseError() const
125 bool
126 XmlQuery::parse( QNetworkReply* reply )
127 {
128 reply->deleteLater();
129 return parse( reply->readAll() );
130 }
131
132
133 lastfm::ws::ParseError
134 XmlQuery::parseError() const
126135 {
127136 return d->error;
128137 }
4242 XmlQuery();
4343 XmlQuery( const XmlQuery& that );
4444 ~XmlQuery();
45
46 /**
47 * Fills in the XmlQuery response by parsing raw reply @param data from the
48 * webservice.
49 *
50 * @return true if successfully parsed and the response does not signify an error,
51 * false otherwise. When false is returned, parseError() contains the error.
52 */
4553 bool parse( const QByteArray& data );
54
55 /**
56 * Convenience parse() overload that takes data from the @param reply and calls
57 * deleteLater() on it.
58 *
59 * @return true if successfully parsed and the response does not signify an error,
60 * false otherwise. When false is returned, parseError() contains the error.
61 */
62 bool parse( QNetworkReply* reply );
63
4664 ws::ParseError parseError() const;
47
65
4866 XmlQuery( const QDomElement& e, const char* name = "" );
4967
5068 /** Selects a DIRECT child element, you can specify attributes like so:
5472 XmlQuery operator[]( const QString& name ) const;
5573 QString text() const;
5674 QString attribute( const QString& name ) const;
57
75
5876 /** selects all children with specified name, recursively */
5977 QList<XmlQuery> children( const QString& named ) const;
6078
305305 // In the case of an error, there will be no initial number, just
306306 // an error string.
307307
308 reply->deleteLater();
308309 QString const response( reply->readAll() );
309310 QStringList const list = response.split( ' ' );
310311
362363 CASE(InternalError)
363364 }
364365 #undef CASE
365 }
366
367 return d;
368 }
194194 case QSysInfo::MV_10_5: return "Mac OS X 10.5";
195195 case QSysInfo::MV_10_6: return "Mac OS X 10.6";
196196 case QSysInfo::MV_10_7: return "Mac OS X 10.7";
197 case QSysInfo::MV_10_8: return "Mac OS X 10.8";
197198
198199 default: return "Unknown";
199200 }