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