PageRenderTime 820ms CodeModel.GetById 61ms app.highlight 692ms RepoModel.GetById 60ms app.codeStats 0ms

/thirdparty/liblastfm2/src/types/Track.cpp

http://github.com/tomahawk-player/tomahawk
C++ | 613 lines | 467 code | 115 blank | 31 comment | 79 complexity | 90ab24a55ddb528700d58ecf8d1af1b1 MD5 | raw file
  1/*
  2   Copyright 2009 Last.fm Ltd. 
  3      - Primarily authored by Max Howell, Jono Cole and Doug Mansell
  4
  5   This file is part of liblastfm.
  6
  7   liblastfm is free software: you can redistribute it and/or modify
  8   it under the terms of the GNU General Public License as published by
  9   the Free Software Foundation, either version 3 of the License, or
 10   (at your option) any later version.
 11
 12   liblastfm is distributed in the hope that it will be useful,
 13   but WITHOUT ANY WARRANTY; without even the implied warranty of
 14   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 15   GNU General Public License for more details.
 16
 17   You should have received a copy of the GNU General Public License
 18   along with liblastfm.  If not, see <http://www.gnu.org/licenses/>.
 19*/
 20
 21#include <QFileInfo>
 22#include <QStringList>
 23#include <QAbstractNetworkCache>
 24
 25#include "Track.h"
 26#include "User.h"
 27#include "../core/UrlBuilder.h"
 28#include "../core/XmlQuery.h"
 29#include "../ws/ws.h"
 30
 31
 32lastfm::TrackContext::TrackContext()
 33    :m_type( Unknown )
 34{
 35
 36}
 37
 38lastfm::TrackContext::TrackContext( const QString& type, const QList<QString>& values )
 39    :m_type( getType( type ) ), m_values( values )
 40{
 41}
 42
 43lastfm::TrackContext::Type
 44lastfm::TrackContext::getType( const QString& typeString )
 45{
 46    Type type = Unknown;
 47
 48    if ( typeString == "artist" )
 49        type = Artist;
 50    else if ( typeString == "user" )
 51        type = User;
 52    else if ( typeString == "neighbour" )
 53        type = Neighbour;
 54    else if ( typeString == "friend" )
 55        type = Friend;
 56
 57    return type;
 58}
 59
 60lastfm::TrackContext::Type
 61lastfm::TrackContext::type() const
 62{
 63    return m_type;
 64}
 65
 66
 67QList<QString>
 68lastfm::TrackContext::values() const
 69{
 70    return m_values;
 71}
 72
 73
 74lastfm::TrackData::TrackData()
 75             : trackNumber( 0 ),
 76               duration( 0 ),
 77               source( Track::Unknown ),
 78               rating( 0 ),
 79               fpid( -1 ),
 80               loved( false ),
 81               null( false ),
 82               scrobbleStatus( Track::Null ),
 83               scrobbleError( Track::None )
 84{}
 85
 86lastfm::Track::Track()
 87    :AbstractType()
 88{
 89    d = new TrackData;
 90    d->null = true;
 91}
 92
 93lastfm::Track::Track( const QDomElement& e )
 94    :AbstractType()
 95{
 96    d = new TrackData;
 97
 98    if (e.isNull()) { d->null = true; return; }
 99    
100    // XML response may have changed
101    QDomNode artistName = e.namedItem( "artist" ).namedItem( "name" );
102    if( artistName.isNull() ) {
103          d->artist = e.namedItem( "artist" ).toElement().text();
104    } else {
105        d->artist = artistName.toElement().text();
106
107    }
108    // XML response may have changed
109    QDomNode trackTitle = e.namedItem( "name" );
110    if( trackTitle.isNull() )
111        d->title = e.namedItem( "track" ).toElement().text();
112    else
113        d->title = trackTitle.toElement().text();
114
115
116    d->albumArtist = e.namedItem( "albumArtist" ).toElement().text();
117    d->album =  e.namedItem( "album" ).toElement().text();
118    d->correctedArtist = e.namedItem( "correctedArtist" ).toElement().text();
119    d->correctedAlbumArtist = e.namedItem( "correctedAlbumArtist" ).toElement().text();
120    d->correctedAlbum =  e.namedItem( "correctedAlbum" ).toElement().text();
121    d->correctedTitle = e.namedItem( "correctedTrack" ).toElement().text();
122    d->trackNumber = 0;
123    d->duration = e.namedItem( "duration" ).toElement().text().toInt();
124    d->url = e.namedItem( "url" ).toElement().text();
125    d->rating = e.namedItem( "rating" ).toElement().text().toUInt();
126    d->source = e.namedItem( "source" ).toElement().text().toInt(); //defaults to 0, or lastfm::Track::Unknown
127    d->time = QDateTime::fromTime_t( e.namedItem( "timestamp" ).toElement().text().toUInt() );
128    d->loved = e.namedItem( "loved" ).toElement().text().toInt();
129    d->scrobbleStatus = e.namedItem( "scrobbleStatus" ).toElement().text().toInt();
130    d->scrobbleError = e.namedItem( "scrobbleError" ).toElement().text().toInt();
131
132    for (QDomElement image(e.firstChildElement("image")) ; !image.isNull() ; image = e.nextSiblingElement("image"))
133    {
134        d->m_images[static_cast<lastfm::ImageSize>(image.attribute("size").toInt())] = image.text();
135    }
136
137    QDomNodeList nodes = e.namedItem( "extras" ).childNodes();
138    for (int i = 0; i < nodes.count(); ++i)
139    {
140        QDomNode n = nodes.at(i);
141        QString key = n.nodeName();
142        d->extras[key] = n.toElement().text();
143    }
144}
145
146void
147lastfm::TrackData::onLoveFinished()
148{
149    try
150    {
151        XmlQuery lfm = static_cast<QNetworkReply*>(sender())->readAll();
152        if ( lfm.attribute( "status" ) == "ok")
153            loved = true;
154
155    }
156    catch (...)
157    {
158    }
159    emit loveToggled( loved );
160}
161
162
163void
164lastfm::TrackData::onUnloveFinished()
165{
166    try
167    {
168        XmlQuery lfm = static_cast<QNetworkReply*>(sender())->readAll();
169        if ( lfm.attribute( "status" ) == "ok")
170            loved = false;
171    }
172    catch (...)
173    {
174    }
175
176    emit loveToggled( loved );
177}
178
179void
180lastfm::TrackData::onGotInfo()
181{
182    const QByteArray data = static_cast<QNetworkReply*>(sender())->readAll();
183
184    try
185    {
186        lastfm::XmlQuery lfm( data );
187
188        QString imageUrl = lfm["track"]["image size=small"].text();
189        if ( !imageUrl.isEmpty() ) m_images[lastfm::Small] = imageUrl;
190        imageUrl = lfm["track"]["image size=medium"].text();
191        if ( !imageUrl.isEmpty() ) m_images[lastfm::Medium] = imageUrl;
192        imageUrl = lfm["track"]["image size=large"].text();
193        if ( !imageUrl.isEmpty() ) m_images[lastfm::Large] = imageUrl;
194        imageUrl = lfm["track"]["image size=extralarge"].text();
195        if ( !imageUrl.isEmpty() ) m_images[lastfm::ExtraLarge] = imageUrl;
196        imageUrl = lfm["track"]["image size=mega"].text();
197        if ( !imageUrl.isEmpty() ) m_images[lastfm::Mega] = imageUrl;
198
199        loved = lfm["track"]["userloved"].text().toInt();
200
201        emit gotInfo( data );
202        emit loveToggled( loved );
203    }
204    catch (...)
205    {
206        emit gotInfo( data );
207    }
208
209    // you should connect everytime you call getInfo
210    disconnect( this, SIGNAL(gotInfo(const QByteArray&)), 0, 0);
211}
212
213
214QDomElement
215lastfm::Track::toDomElement( QDomDocument& xml ) const
216{
217    QDomElement item = xml.createElement( "track" );
218    
219    #define makeElement( tagname, getter ) { \
220        QString v = getter; \
221        if (!v.isEmpty()) \
222        { \
223            QDomElement e = xml.createElement( tagname ); \
224            e.appendChild( xml.createTextNode( v ) ); \
225            item.appendChild( e ); \
226        } \
227    }
228
229    makeElement( "artist", d->artist );
230    makeElement( "albumArtist", d->albumArtist );
231    makeElement( "album", d->album );
232    makeElement( "track", d->title );
233    makeElement( "correctedArtist", d->correctedArtist );
234    makeElement( "correctedAlbumArtist", d->correctedAlbumArtist );
235    makeElement( "correctedAlbum", d->correctedAlbum );
236    makeElement( "correctedTrack", d->correctedTitle );
237    makeElement( "duration", QString::number( d->duration ) );
238    makeElement( "timestamp", QString::number( d->time.toTime_t() ) );
239    makeElement( "url", d->url.toString() );
240    makeElement( "source", QString::number( d->source ) );
241    makeElement( "rating", QString::number(d->rating) );
242    makeElement( "fpId", QString::number(d->fpid) );
243    makeElement( "mbId", mbid() );
244    makeElement( "loved", QString::number( isLoved() ) );
245    makeElement( "scrobbleStatus", QString::number( scrobbleStatus() ) );
246    makeElement( "scrobbleError", QString::number( scrobbleError() ) );
247
248    // put the images urls in the dom
249    QMapIterator<lastfm::ImageSize, QUrl> imageIter( d->m_images );
250    while (imageIter.hasNext()) {
251        QDomElement e = xml.createElement( "image" );
252        e.appendChild( xml.createTextNode( imageIter.next().value().toString() ) );
253        e.setAttribute( "size", imageIter.key() );
254        item.appendChild( e );
255    }
256
257    // add the extras to the dom
258    QDomElement extras = xml.createElement( "extras" );
259    QMapIterator<QString, QString> extrasIter( d->extras );
260    while (extrasIter.hasNext()) {
261        QDomElement e = xml.createElement( extrasIter.next().key() );
262        e.appendChild( xml.createTextNode( extrasIter.value() ) );
263        extras.appendChild( e );
264    }
265    item.appendChild( extras );
266
267    return item;
268}
269
270
271bool
272lastfm::Track::corrected() const
273{
274    // If any of the corrected string have been set and they are different
275    // from the initial strings then this track has been corrected.
276    return ( (!d->correctedTitle.isEmpty() && (d->correctedTitle != d->title))
277            || (!d->correctedAlbum.isEmpty() && (d->correctedAlbum != d->album))
278            || (!d->correctedArtist.isEmpty() && (d->correctedArtist != d->artist))
279            || (!d->correctedAlbumArtist.isEmpty() && (d->correctedAlbumArtist != d->albumArtist)));
280}
281
282lastfm::Artist
283lastfm::Track::artist( Corrections corrected ) const
284{
285    if ( corrected == Corrected && !d->correctedArtist.isEmpty() )
286        return Artist( d->correctedArtist );
287
288    return Artist( d->artist );
289}
290
291lastfm::Artist
292lastfm::Track::albumArtist( Corrections corrected ) const
293{
294    if ( corrected == Corrected && !d->correctedAlbumArtist.isEmpty() )
295        return Artist( d->correctedAlbumArtist );
296
297    return Artist( d->albumArtist );
298}
299
300lastfm::Album
301lastfm::Track::album( Corrections corrected ) const
302{
303    if ( corrected == Corrected && !d->correctedAlbum.isEmpty() )
304        return Album( artist( corrected ), d->correctedAlbum );
305
306    return Album( artist( corrected ), d->album );
307}
308
309QString
310lastfm::Track::title( Corrections corrected ) const
311{
312    /** if no title is set, return the musicbrainz unknown identifier
313      * in case some part of the GUI tries to display it anyway. Note isNull
314      * returns false still. So you should have queried this! */
315
316    if ( corrected == Corrected && !d->correctedTitle.isEmpty() )
317        return d->correctedTitle;
318
319    return d->title.isEmpty() ? "[unknown]" : d->title;
320}
321
322
323QUrl
324lastfm::Track::imageUrl( lastfm::ImageSize size, bool square ) const
325{
326    if( !square ) return d->m_images.value( size );
327
328    QUrl url = d->m_images.value( size );
329    QRegExp re( "/serve/(\\d*)s?/" );
330    return QUrl( url.toString().replace( re, "/serve/\\1s/" ));
331}
332
333
334QString
335lastfm::Track::toString( const QChar& separator, Corrections corrections ) const
336{
337    if ( d->artist.isEmpty() )
338    {
339        if ( d->title.isEmpty() )
340            return QFileInfo( d->url.path() ).fileName();
341        else
342            return title( corrections );
343    }
344
345    if ( d->title.isEmpty() )
346        return artist( corrections );
347
348    return artist( corrections ) + ' ' + separator + ' ' + title( corrections );
349}
350
351
352QString //static
353lastfm::Track::durationString( int const duration )
354{
355    QTime t = QTime().addSecs( duration );
356    if (duration < 60*60)
357        return t.toString( "m:ss" );
358    else
359        return t.toString( "hh:mm:ss" );
360}
361
362
363QNetworkReply*
364lastfm::Track::share( const QStringList& recipients, const QString& message, bool isPublic ) const
365{
366    QMap<QString, QString> map = params("share");
367    map["recipient"] = recipients.join(",");
368    map["public"] = isPublic ? "1" : "0";
369    if (message.size()) map["message"] = message;
370    return ws::post(map);
371}
372
373
374void
375lastfm::Track::invalidateGetInfo()
376{
377    // invalidate the track.getInfo cache
378    QAbstractNetworkCache* cache = lastfm::nam()->cache();
379    if ( cache )
380    {
381        QMap<QString, QString> map = params("getInfo", true);
382        if (!lastfm::ws::Username.isEmpty()) map["username"] = lastfm::ws::Username;
383        if (!lastfm::ws::SessionKey.isEmpty()) map["sk"] = lastfm::ws::SessionKey;
384        cache->remove( lastfm::ws::url( map ) );
385    }
386}
387
388
389void
390lastfm::MutableTrack::setFromLfm( const XmlQuery& lfm )
391{
392    QString imageUrl = lfm["track"]["image size=small"].text();
393    if ( !imageUrl.isEmpty() ) d->m_images[lastfm::Small] = imageUrl;
394    imageUrl = lfm["track"]["image size=medium"].text();
395    if ( !imageUrl.isEmpty() ) d->m_images[lastfm::Medium] = imageUrl;
396    imageUrl = lfm["track"]["image size=large"].text();
397    if ( !imageUrl.isEmpty() ) d->m_images[lastfm::Large] = imageUrl;
398    imageUrl = lfm["track"]["image size=extralarge"].text();
399    if ( !imageUrl.isEmpty() ) d->m_images[lastfm::ExtraLarge] = imageUrl;
400    imageUrl = lfm["track"]["image size=mega"].text();
401    if ( !imageUrl.isEmpty() ) d->m_images[lastfm::Mega] = imageUrl;
402
403    d->loved = lfm["track"]["userloved"].text().toInt();
404
405    d->forceLoveToggled( d->loved );
406}
407
408void
409lastfm::MutableTrack::setImageUrl( lastfm::ImageSize size, const QString& url )
410{
411    d->m_images[size] = url;
412}
413
414
415void
416lastfm::MutableTrack::love()
417{
418    QNetworkReply* reply = ws::post(params("love"));
419    QObject::connect( reply, SIGNAL(finished()), signalProxy(), SLOT(onLoveFinished()));
420
421    invalidateGetInfo();
422}
423
424
425void
426lastfm::MutableTrack::unlove()
427{
428    QNetworkReply* reply = ws::post(params("unlove"));
429    QObject::connect( reply, SIGNAL(finished()), signalProxy(), SLOT(onUnloveFinished()));
430
431    invalidateGetInfo();
432}
433
434QNetworkReply*
435lastfm::MutableTrack::ban()
436{
437    d->extras["rating"] = "B";
438    return ws::post(params("ban"));
439}
440
441
442QMap<QString, QString>
443lastfm::Track::params( const QString& method, bool use_mbid ) const
444{
445    QMap<QString, QString> map;
446    map["method"] = "Track."+method;
447    if (d->mbid.size() && use_mbid)
448        map["mbid"] = d->mbid;
449    else {
450        map["artist"] = d->artist;
451        map["track"] = d->title;
452    }
453    return map;
454}
455
456
457QNetworkReply*
458lastfm::Track::getTopTags() const
459{
460    return ws::get( params("getTopTags", true) );
461}
462
463
464QNetworkReply*
465lastfm::Track::getTopFans() const
466{
467    return ws::get( params("getTopFans", true) );
468}
469
470
471QNetworkReply*
472lastfm::Track::getTags() const
473{
474    return ws::get( params("getTags", true) );
475}
476
477void
478lastfm::Track::getInfo() const
479{
480    QMap<QString, QString> map = params("getInfo", true);
481    if (!lastfm::ws::Username.isEmpty()) map["username"] = lastfm::ws::Username;
482    if (!lastfm::ws::SessionKey.isEmpty()) map["sk"] = lastfm::ws::SessionKey;
483    QObject::connect( ws::get( map ), SIGNAL(finished()), d.data(), SLOT(onGotInfo()));
484}
485
486
487QNetworkReply*
488lastfm::Track::getBuyLinks( const QString& country ) const
489{
490    QMap<QString, QString> map = params( "getBuyLinks", true );
491    map["country"] = country;
492    return ws::get( map );
493}
494
495
496QNetworkReply*
497lastfm::Track::addTags( const QStringList& tags ) const
498{
499    if (tags.isEmpty())
500        return 0;
501    QMap<QString, QString> map = params("addTags");
502    map["tags"] = tags.join( QChar(',') );
503    return ws::post(map);
504}
505
506
507QNetworkReply*
508lastfm::Track::removeTag( const QString& tag ) const
509{
510    if (tag.isEmpty())
511        return 0;
512    QMap<QString, QString> map = params( "removeTag" );
513    map["tags"] = tag;
514    return ws::post(map);
515}
516
517
518QNetworkReply*
519lastfm::Track::updateNowPlaying() const
520{
521    return updateNowPlaying(duration());
522}
523
524QNetworkReply* 
525lastfm::Track::updateNowPlaying( int duration ) const
526{
527    QMap<QString, QString> map = params("updateNowPlaying");
528    map["duration"] = QString::number( duration );
529    if ( !album().isNull() ) map["album"] = album();
530    map["context"] = extra("playerId");
531
532    qDebug() << map;
533
534    return ws::post(map);
535}
536
537QNetworkReply* 
538lastfm::Track::removeNowPlaying() const
539{
540    QMap<QString, QString> map;
541    map["method"] = "track.removeNowPlaying";
542
543    qDebug() << map;
544
545    return ws::post(map);
546}
547
548
549QNetworkReply*
550lastfm::Track::scrobble() const
551{
552    QMap<QString, QString> map = params("scrobble");
553    map["duration"] = QString::number( d->duration );
554    map["timestamp"] = QString::number( d->time.toTime_t() );
555    map["context"] = extra("playerId");
556    map["albumArtist"] = d->albumArtist;
557    if ( !d->album.isEmpty() ) map["album"] = d->album;
558
559    qDebug() << map;
560
561    return ws::post(map);
562}
563
564QNetworkReply*
565lastfm::Track::scrobble(const QList<lastfm::Track>& tracks)
566{
567    QMap<QString, QString> map;
568    map["method"] = "track.scrobble";
569
570    for ( int i(0) ; i < tracks.count() ; ++i )
571    {
572        map["duration[" + QString::number(i) + "]"] = QString::number( tracks[i].duration() );
573        map["timestamp[" + QString::number(i)  + "]"] = QString::number( tracks[i].timestamp().toTime_t() );
574        map["track[" + QString::number(i)  + "]"] = tracks[i].title();
575        map["context[" + QString::number(i)  + "]"] = tracks[i].extra("playerId");
576        if ( !tracks[i].album().isNull() ) map["album[" + QString::number(i)  + "]"] = tracks[i].album();
577        map["artist[" + QString::number(i) + "]"] = tracks[i].artist();
578        map["albumArtist[" + QString::number(i) + "]"] = tracks[i].albumArtist();
579        if ( !tracks[i].mbid().isNull() ) map["mbid[" + QString::number(i)  + "]"] = tracks[i].mbid();
580    }
581
582    qDebug() << map;
583
584    return ws::post(map);
585}
586
587
588QUrl
589lastfm::Track::www() const
590{
591    return UrlBuilder( "music" ).slash( artist( Corrected ) ).slash( album(  Corrected  ).isNull() ? QString("_") : album( Corrected )).slash( title( Corrected ) ).url();
592}
593
594
595bool
596lastfm::Track::isMp3() const
597{
598    //FIXME really we should check the file header?
599    return d->url.scheme() == "file" &&
600           d->url.path().endsWith( ".mp3", Qt::CaseInsensitive );
601}
602
603void
604lastfm::MutableTrack::setCorrections( QString title, QString album, QString artist, QString albumArtist )
605{
606    d->correctedTitle = title;
607    d->correctedAlbum = album;
608    d->correctedArtist = artist;
609    d->correctedAlbumArtist = albumArtist;
610
611    d->forceCorrected( toString() );
612}
613