/src/libtomahawk/DropJob.cpp

http://github.com/tomahawk-player/tomahawk · C++ · 1050 lines · 801 code · 185 blank · 64 comment · 215 complexity · 0236ac51f008e08fd8088c4baf826a98 MD5 · raw file

  1. /* === This file is part of Tomahawk Player - <http://tomahawk-player.org> ===
  2. *
  3. * Copyright 2011, Michael Zanetti <mzanetti@kde.org>
  4. * Copyright 2011, Leo Franchi <lfranchi@kde.org>
  5. * Copyright 2011-2012, Jeff Mitchell <jeff@tomahawk-player.org>
  6. * Copyright 2011-2012, Christian Muehlhaeuser <muesli@tomahawk-player.org>
  7. *
  8. * Tomahawk is free software: you can redistribute it and/or modify
  9. * it under the terms of the GNU General Public License as published by
  10. * the Free Software Foundation, either version 3 of the License, or
  11. * (at your option) any later version.
  12. *
  13. * Tomahawk is distributed in the hope that it will be useful,
  14. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  15. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  16. * GNU General Public License for more details.
  17. *
  18. * You should have received a copy of the GNU General Public License
  19. * along with Tomahawk. If not, see <http://www.gnu.org/licenses/>.
  20. */
  21. #include "DropJob.h"
  22. #include <QFileInfo>
  23. #include "jobview/JobStatusView.h"
  24. #include "jobview/JobStatusModel.h"
  25. #include "jobview/ErrorStatusMessage.h"
  26. #include "playlist/PlaylistTemplate.h"
  27. #include "resolvers/ExternalResolver.h"
  28. #include "utils/SpotifyParser.h"
  29. #include "utils/ItunesParser.h"
  30. #include "utils/ItunesLoader.h"
  31. #include "utils/M3uLoader.h"
  32. #include "utils/ShortenedLinkParser.h"
  33. #include "utils/Logger.h"
  34. #include "utils/TomahawkUtils.h"
  35. #include "utils/XspfLoader.h"
  36. #include "Artist.h"
  37. #include "Album.h"
  38. #include "config.h"
  39. #include "GlobalActionManager.h"
  40. #include "Pipeline.h"
  41. #include "Result.h"
  42. #include "Source.h"
  43. #include "ViewManager.h"
  44. #ifdef QCA2_FOUND
  45. #include "utils/GroovesharkParser.h"
  46. #endif //QCA2_FOUND
  47. using namespace Tomahawk;
  48. bool DropJob::s_canParseSpotifyPlaylists = false;
  49. static QString s_dropJobInfoId = "dropjob";
  50. DropJob::DropJob( QObject *parent )
  51. : QObject( parent )
  52. , m_queryCount( 0 )
  53. , m_onlyLocal( false )
  54. , m_getWholeArtists( false )
  55. , m_getWholeAlbums( false )
  56. , m_top10( false )
  57. , m_dropAction( Default )
  58. {
  59. }
  60. DropJob::~DropJob()
  61. {
  62. qDebug() << "destroying DropJob";
  63. }
  64. /// QMIMEDATA HANDLING
  65. QStringList
  66. DropJob::mimeTypes()
  67. {
  68. QStringList mimeTypes;
  69. mimeTypes << "application/tomahawk.query.list"
  70. << "application/tomahawk.plentry.list"
  71. << "application/tomahawk.result.list"
  72. << "application/tomahawk.result"
  73. << "application/tomahawk.metadata.artist"
  74. << "application/tomahawk.metadata.album"
  75. << "application/tomahawk.mixed"
  76. << "text/plain"
  77. << "text/uri-list";
  78. return mimeTypes;
  79. }
  80. void
  81. DropJob::setDropTypes( DropTypes types )
  82. {
  83. m_dropTypes = types;
  84. }
  85. void
  86. DropJob::setDropAction( DropJob::DropAction action )
  87. {
  88. m_dropAction = action;
  89. }
  90. DropJob::DropTypes
  91. DropJob::dropTypes() const
  92. {
  93. return m_dropTypes;
  94. }
  95. DropJob::DropAction
  96. DropJob::dropAction() const
  97. {
  98. return m_dropAction;
  99. }
  100. bool
  101. DropJob::acceptsMimeData( const QMimeData* data, DropJob::DropTypes acceptedType, DropJob::DropAction acceptedAction )
  102. {
  103. Q_UNUSED( acceptedAction );
  104. if ( data->hasFormat( "application/tomahawk.query.list" )
  105. || data->hasFormat( "application/tomahawk.plentry.list" )
  106. || data->hasFormat( "application/tomahawk.result.list" )
  107. || data->hasFormat( "application/tomahawk.result" )
  108. || data->hasFormat( "application/tomahawk.mixed" )
  109. || data->hasFormat( "application/tomahawk.metadata.album" )
  110. || data->hasFormat( "application/tomahawk.metadata.artist" ) )
  111. {
  112. return true;
  113. }
  114. // check plain text url types
  115. if ( !data->hasFormat( "text/plain" ) )
  116. if ( !data->hasFormat( "text/uri-list" ) )
  117. return false;
  118. const QString url = data->data( "text/plain" );
  119. const QString urlList = data->data( "text/uri-list" ).trimmed();
  120. if ( acceptedType.testFlag( Playlist ) )
  121. {
  122. if ( url.contains( "xml" ) && url.contains( "iTunes" ) )
  123. return validateLocalFile( url, "xml" );
  124. if ( urlList.contains( "xml" ) && urlList.contains( "iTunes" ) )
  125. return validateLocalFiles( urlList, "xml" );
  126. if ( url.contains( "xspf" ) )
  127. return true;
  128. if ( url.contains( "m3u" ) )
  129. return true;
  130. if ( urlList.contains( "m3u" ) )
  131. return true;
  132. if ( urlList.contains( "xspf" ) )
  133. return true;
  134. // Not the most elegant
  135. if ( url.contains( "spotify" ) && url.contains( "playlist" ) && s_canParseSpotifyPlaylists )
  136. return true;
  137. if ( url.contains( "grooveshark.com" ) && url.contains( "playlist" ) )
  138. return true;
  139. // Check Scriptresolvers
  140. foreach ( QPointer<ExternalResolver> resolver, Pipeline::instance()->scriptResolvers() )
  141. {
  142. if ( resolver->canParseUrl( url, ExternalResolver::UrlTypePlaylist ) )
  143. return true;
  144. }
  145. }
  146. if ( acceptedType.testFlag( Track ) )
  147. {
  148. if ( url.contains( "m3u" ) )
  149. return true;
  150. if ( urlList.contains( "m3u" ) )
  151. return true;
  152. if ( url.contains( "itunes" ) && url.contains( "album" ) ) // YES itunes is fucked up and song links have album/ in the url.
  153. return true;
  154. if ( url.contains( "spotify" ) && url.contains( "track" ) )
  155. return true;
  156. if ( url.contains( "rdio.com" ) && ( ( ( url.contains( "track" ) && url.contains( "artist" ) && url.contains( "album" ) )
  157. || url.contains( "playlists" ) ) ) )
  158. return true;
  159. // Check Scriptresolvers
  160. foreach ( QPointer<ExternalResolver> resolver, Pipeline::instance()->scriptResolvers() )
  161. {
  162. if ( resolver->canParseUrl( url, ExternalResolver::UrlTypeTrack ) )
  163. return true;
  164. }
  165. }
  166. if ( acceptedType.testFlag( Album ) )
  167. {
  168. if ( url.contains( "itunes" ) && url.contains( "album" ) ) // YES itunes is fucked up and song links have album/ in the url.
  169. return true;
  170. if ( url.contains( "spotify" ) && url.contains( "album" ) )
  171. return true;
  172. if ( url.contains( "rdio.com" ) && ( url.contains( "artist" ) && url.contains( "album" ) && !url.contains( "track" ) ) )
  173. return true;
  174. // Check Scriptresolvers
  175. foreach ( QPointer<ExternalResolver> resolver, Pipeline::instance()->scriptResolvers() )
  176. {
  177. if ( resolver->canParseUrl( url, ExternalResolver::UrlTypeAlbum ) )
  178. return true;
  179. }
  180. }
  181. if ( acceptedType.testFlag( Artist ) )
  182. {
  183. if ( url.contains( "itunes" ) && url.contains( "artist" ) ) // YES itunes is fucked up and song links have album/ in the url.
  184. return true;
  185. if ( url.contains( "spotify" ) && url.contains( "artist" ) )
  186. return true;
  187. if ( url.contains( "rdio.com" ) && ( url.contains( "artist" ) && !url.contains( "album" ) && !url.contains( "track" ) ) )
  188. return true;
  189. // Check Scriptresolvers
  190. foreach ( QPointer<ExternalResolver> resolver, Pipeline::instance()->scriptResolvers() )
  191. {
  192. if ( resolver->canParseUrl( url, ExternalResolver::UrlTypeArtist ) )
  193. return true;
  194. }
  195. }
  196. // We whitelist certain url-shorteners since they do some link checking. Often playable (e.g. spotify) links hide behind them,
  197. // so we do an extra level of lookup
  198. if ( ShortenedLinkParser::handlesUrl( url ) )
  199. return true;
  200. return false;
  201. }
  202. bool
  203. DropJob::validateLocalFile( const QString &path, const QString &suffix )
  204. {
  205. QFileInfo info( QUrl::fromUserInput( path ).toLocalFile() );
  206. if ( suffix.isEmpty() )
  207. return info.exists();
  208. return ( info.exists() && info.suffix() == suffix );
  209. }
  210. bool
  211. DropJob::validateLocalFiles(const QString &paths, const QString &suffix)
  212. {
  213. QStringList filePaths = paths.split( QRegExp( "\\s+" ), QString::SkipEmptyParts );
  214. QStringList::iterator it = filePaths.begin();
  215. while ( it != filePaths.end() )
  216. {
  217. if ( !validateLocalFile( *it, suffix ) )
  218. it = filePaths.erase( it );
  219. else
  220. ++it;
  221. }
  222. return !filePaths.isEmpty();
  223. }
  224. bool
  225. DropJob::isDropType( DropJob::DropType desired, const QMimeData* data )
  226. {
  227. const QString url = data->data( "text/plain" );
  228. const QString urlList = data->data( "text/uri-list" ).trimmed();
  229. if ( desired == Playlist )
  230. {
  231. if ( url.contains( "xml" ) && url.contains( "iTunes" ) )
  232. return validateLocalFile( url, "xml" );
  233. if ( urlList.contains( "xml" ) && urlList.contains( "iTunes" ) )
  234. return validateLocalFiles( urlList, "xml" );
  235. if ( url.contains( "xspf" ) || urlList.contains( "xspf" ) )
  236. return true;
  237. if ( url.contains( "m3u" ) || urlList.contains( "m3u" ) )
  238. return true;
  239. // Not the most elegant
  240. if ( url.contains( "spotify" ) && url.contains( "playlist" ) && s_canParseSpotifyPlaylists )
  241. return true;
  242. if ( url.contains( "rdio.com" ) && url.contains( "people" ) && url.contains( "playlist" ) )
  243. return true;
  244. #ifdef QCA2_FOUND
  245. if ( url.contains( "grooveshark.com" ) && url.contains( "playlist" ) )
  246. return true;
  247. #endif //QCA2_FOUND
  248. if ( ShortenedLinkParser::handlesUrl( url ) )
  249. return true;
  250. // Check Scriptresolvers
  251. foreach ( QPointer<ExternalResolver> resolver, Pipeline::instance()->scriptResolvers() )
  252. {
  253. if ( resolver->canParseUrl( url, ExternalResolver::UrlTypePlaylist ) )
  254. {
  255. tLog( LOGVERBOSE ) << Q_FUNC_INFO << "Accepting current drop as a playlist" << resolver->name();
  256. return true;
  257. }
  258. }
  259. }
  260. return false;
  261. }
  262. void
  263. DropJob::setGetWholeArtists( bool getWholeArtists )
  264. {
  265. m_getWholeArtists = getWholeArtists;
  266. }
  267. void
  268. DropJob::setGetWholeAlbums( bool getWholeAlbums )
  269. {
  270. m_getWholeAlbums = getWholeAlbums;
  271. }
  272. void
  273. DropJob::tracksFromMimeData( const QMimeData* data, bool allowDuplicates, bool onlyLocal, bool top10 )
  274. {
  275. m_allowDuplicates = allowDuplicates;
  276. m_onlyLocal = onlyLocal;
  277. m_top10 = top10;
  278. parseMimeData( data );
  279. if ( m_queryCount == 0 )
  280. {
  281. if ( onlyLocal )
  282. removeRemoteSources();
  283. if ( !allowDuplicates )
  284. removeDuplicates();
  285. emit tracks( m_resultList );
  286. deleteLater();
  287. }
  288. }
  289. void
  290. DropJob::parseMimeData( const QMimeData* data )
  291. {
  292. QList< query_ptr > results;
  293. if ( data->hasFormat( "application/tomahawk.query.list" ) )
  294. results = tracksFromQueryList( data );
  295. else if ( data->hasFormat( "application/tomahawk.result.list" ) )
  296. results = tracksFromResultList( data );
  297. else if ( data->hasFormat( "application/tomahawk.metadata.album" ) )
  298. results = tracksFromAlbumMetaData( data );
  299. else if ( data->hasFormat( "application/tomahawk.metadata.artist" ) )
  300. results = tracksFromArtistMetaData( data );
  301. else if ( data->hasFormat( "application/tomahawk.mixed" ) )
  302. tracksFromMixedData( data );
  303. else if ( data->hasFormat( "text/plain" ) && !data->data( "text/plain" ).isEmpty() )
  304. {
  305. const QString plainData = QString::fromUtf8( data->data( "text/plain" ) );
  306. handleAllUrls( plainData );
  307. }
  308. else if ( data->hasFormat( "text/uri-list" ) )
  309. {
  310. const QString plainData = QString::fromUtf8( data->data( "text/uri-list" ).trimmed() );
  311. handleAllUrls( plainData );
  312. }
  313. m_resultList.append( results );
  314. }
  315. QList< query_ptr >
  316. DropJob::tracksFromQueryList( const QMimeData* data )
  317. {
  318. QList< query_ptr > queries;
  319. QByteArray itemData = data->data( "application/tomahawk.query.list" );
  320. QDataStream stream( &itemData, QIODevice::ReadOnly );
  321. while ( !stream.atEnd() )
  322. {
  323. qlonglong qptr;
  324. stream >> qptr;
  325. query_ptr* query = reinterpret_cast<query_ptr*>(qptr);
  326. if ( query && !query->isNull() )
  327. {
  328. tDebug() << "Dropped query item:" << query->data()->toString();
  329. if ( m_top10 )
  330. {
  331. queries << getTopTen( query->data()->track()->artist() );
  332. }
  333. else if ( m_getWholeArtists )
  334. {
  335. queries << getArtist( query->data()->track()->artist() );
  336. }
  337. else if ( m_getWholeAlbums )
  338. {
  339. queries << getAlbum( query->data()->track()->artist(), query->data()->track()->album() );
  340. }
  341. else
  342. {
  343. queries << *query;
  344. }
  345. }
  346. }
  347. return queries;
  348. }
  349. QList< query_ptr >
  350. DropJob::tracksFromResultList( const QMimeData* data )
  351. {
  352. QList< query_ptr > queries;
  353. QByteArray itemData = data->data( "application/tomahawk.result.list" );
  354. QDataStream stream( &itemData, QIODevice::ReadOnly );
  355. while ( !stream.atEnd() )
  356. {
  357. qlonglong qptr;
  358. stream >> qptr;
  359. result_ptr* result = reinterpret_cast<result_ptr*>(qptr);
  360. if ( result && !result->isNull() )
  361. {
  362. tDebug() << "Dropped result item:" << result->data()->track()->artist() << "-" << result->data()->track()->track();
  363. if ( m_top10 )
  364. {
  365. getTopTen( result->data()->track()->artist() );
  366. }
  367. else if ( m_getWholeArtists )
  368. {
  369. queries << getArtist( result->data()->track()->artist() );
  370. }
  371. else if ( m_getWholeAlbums )
  372. {
  373. queries << getAlbum( result->data()->track()->artist(), result->data()->track()->album() );
  374. }
  375. else
  376. {
  377. queries << result->data()->toQuery();
  378. }
  379. }
  380. }
  381. return queries;
  382. }
  383. QList< query_ptr >
  384. DropJob::tracksFromAlbumMetaData( const QMimeData *data )
  385. {
  386. QList<query_ptr> queries;
  387. QByteArray itemData = data->data( "application/tomahawk.metadata.album" );
  388. QDataStream stream( &itemData, QIODevice::ReadOnly );
  389. while ( !stream.atEnd() )
  390. {
  391. QString artist;
  392. stream >> artist;
  393. QString album;
  394. stream >> album;
  395. if ( m_top10 )
  396. queries << getTopTen( artist );
  397. else if ( m_getWholeArtists )
  398. queries << getArtist( artist );
  399. else
  400. queries << getAlbum( artist, album );
  401. }
  402. return queries;
  403. }
  404. QList< query_ptr >
  405. DropJob::tracksFromArtistMetaData( const QMimeData *data )
  406. {
  407. QList<query_ptr> queries;
  408. QByteArray itemData = data->data( "application/tomahawk.metadata.artist" );
  409. QDataStream stream( &itemData, QIODevice::ReadOnly );
  410. while ( !stream.atEnd() )
  411. {
  412. QString artist;
  413. stream >> artist;
  414. if ( !m_top10 )
  415. {
  416. queries << getArtist( artist );
  417. }
  418. else
  419. {
  420. queries << getTopTen( artist );
  421. }
  422. }
  423. return queries;
  424. }
  425. void
  426. DropJob::tracksFromMixedData( const QMimeData *data )
  427. {
  428. QByteArray itemData = data->data( "application/tomahawk.mixed" );
  429. QDataStream stream( &itemData, QIODevice::ReadOnly );
  430. QString mimeType;
  431. while ( !stream.atEnd() )
  432. {
  433. stream >> mimeType;
  434. QByteArray singleData;
  435. QDataStream singleStream( &singleData, QIODevice::WriteOnly );
  436. QMimeData singleMimeData;
  437. if ( mimeType == "application/tomahawk.query.list" )
  438. {
  439. qlonglong query;
  440. stream >> query;
  441. singleStream << query;
  442. }
  443. else if ( mimeType == "application/tomahawk.result.list" )
  444. {
  445. qlonglong result;
  446. stream >> result;
  447. singleStream << result;
  448. }
  449. else if ( mimeType == "application/tomahawk.metadata.album" )
  450. {
  451. QString artist;
  452. stream >> artist;
  453. singleStream << artist;
  454. QString album;
  455. stream >> album;
  456. singleStream << album;
  457. }
  458. else if ( mimeType == "application/tomahawk.metadata.artist" )
  459. {
  460. QString artist;
  461. stream >> artist;
  462. singleStream << artist;
  463. }
  464. singleMimeData.setData( mimeType, singleData );
  465. parseMimeData( &singleMimeData );
  466. }
  467. }
  468. void
  469. DropJob::handleM3u( const QString& fileUrls )
  470. {
  471. tDebug() << Q_FUNC_INFO << "Got M3U playlist!" << fileUrls;
  472. QStringList urls = fileUrls.split( QRegExp( "\n" ), QString::SkipEmptyParts );
  473. if ( dropAction() == Default )
  474. setDropAction( Create );
  475. tDebug() << "Got a M3U playlist url to parse!" << urls;
  476. M3uLoader* m = new M3uLoader( urls, dropAction() == Create, this );
  477. if ( dropAction() == Append )
  478. {
  479. tDebug() << Q_FUNC_INFO << "Trying to append contents from" << urls;
  480. connect( m, SIGNAL( tracks( QList<Tomahawk::query_ptr> ) ), this, SLOT( onTracksAdded( QList< Tomahawk::query_ptr > ) ) );
  481. m_queryCount++;
  482. }
  483. m->parse();
  484. }
  485. void
  486. DropJob::handleXspfs( const QString& fileUrls )
  487. {
  488. tDebug() << Q_FUNC_INFO << "Got XSPF playlist!" << fileUrls;
  489. bool error = false;
  490. QStringList urls = fileUrls.split( QRegExp( "\n" ), QString::SkipEmptyParts );
  491. if ( dropAction() == Default )
  492. setDropAction( Create );
  493. foreach ( const QString& url, urls )
  494. {
  495. XSPFLoader* l = 0;
  496. QFile xspfFile( QUrl::fromUserInput( url ).toLocalFile() );
  497. if ( xspfFile.exists() )
  498. {
  499. l = new XSPFLoader( dropAction() == Create, false, this );
  500. tDebug( LOGINFO ) << "Loading local XSPF" << xspfFile.fileName();
  501. l->load( xspfFile );
  502. }
  503. else if ( QUrl( url ).isValid() )
  504. {
  505. l = new XSPFLoader( dropAction() == Create, false, this );
  506. tDebug( LOGINFO ) << "Loading remote XSPF" << url;
  507. l->load( QUrl( url ) );
  508. }
  509. else
  510. {
  511. error = true;
  512. tLog() << "Failed to load or parse dropped XSPF";
  513. }
  514. if ( dropAction() == Append && !error && l )
  515. {
  516. qDebug() << Q_FUNC_INFO << "Trying to append XSPF";
  517. connect( l, SIGNAL( tracks( QList<Tomahawk::query_ptr> ) ), this, SLOT( onTracksAdded( QList< Tomahawk::query_ptr > ) ) );
  518. m_queryCount++;
  519. }
  520. }
  521. }
  522. void
  523. DropJob::handleSpotifyUrls( const QString& urlsRaw )
  524. {
  525. // Todo: Allow search querys, and split these in a better way.
  526. // Example: spotify:search:artist:Madonna year:<1970 year:>1990
  527. QStringList urls = urlsRaw.split( QRegExp( "\\s+" ), QString::SkipEmptyParts );
  528. qDebug() << "Got spotify browse uris!" << urls;
  529. /// Lets allow parsing all spotify uris here, if parse server is not available
  530. /// fallback to spotify metadata for tracks /hugo
  531. if ( dropAction() == Default )
  532. setDropAction( Create );
  533. tDebug() << "Got a spotify browse uri in dropjob!" << urls;
  534. SpotifyParser* spot = new SpotifyParser( urls, dropAction() == Create, this );
  535. spot->setSingleMode( false );
  536. /// This currently supports draging and dropping a spotify playlist and artist
  537. if ( dropAction() == Append )
  538. {
  539. tDebug() << Q_FUNC_INFO << "Asking for spotify browse contents from" << urls;
  540. connect( spot, SIGNAL( tracks( QList<Tomahawk::query_ptr> ) ), this, SLOT( onTracksAdded( QList< Tomahawk::query_ptr > ) ) );
  541. m_queryCount++;
  542. }
  543. }
  544. void
  545. DropJob::handleGroovesharkUrls ( const QString& urlsRaw )
  546. {
  547. #ifdef QCA2_FOUND
  548. QStringList urls = urlsRaw.split( QRegExp( "\\s+" ), QString::SkipEmptyParts );
  549. tDebug() << "Got Grooveshark urls!" << urls;
  550. if ( dropAction() == Default )
  551. setDropAction( Create );
  552. GroovesharkParser* groove = new GroovesharkParser( urls, dropAction() == Create, this );
  553. connect( groove, SIGNAL( tracks( QList<Tomahawk::query_ptr> ) ), this, SLOT( onTracksAdded( QList< Tomahawk::query_ptr > ) ) );
  554. if ( dropAction() == Append )
  555. {
  556. tDebug() << Q_FUNC_INFO << "Asking for grooveshark contents from" << urls;
  557. connect( groove, SIGNAL( tracks( QList<Tomahawk::query_ptr> ) ), this, SLOT( onTracksAdded( QList< Tomahawk::query_ptr > ) ) );
  558. m_queryCount++;
  559. }
  560. #else
  561. tLog() << "Tomahawk compiled without QCA support, cannot use groovesharkparser";
  562. #endif
  563. }
  564. bool
  565. DropJob::canParseSpotifyPlaylists()
  566. {
  567. return s_canParseSpotifyPlaylists;
  568. }
  569. void
  570. DropJob::setCanParseSpotifyPlaylists( bool parseable )
  571. {
  572. s_canParseSpotifyPlaylists = parseable;
  573. }
  574. void
  575. DropJob::handleAllUrls( const QString& urls )
  576. {
  577. if ( urls.contains( "xspf" ) )
  578. handleXspfs( urls );
  579. else if ( urls.contains( "m3u" ) )
  580. handleM3u( urls );
  581. else if ( urls.contains( "spotify" ) /// Handle all the spotify uris on internal server, if not avail. fallback to spotify
  582. && ( urls.contains( "playlist" ) || urls.contains( "artist" ) || urls.contains( "album" ) || urls.contains( "track" ) )
  583. && s_canParseSpotifyPlaylists )
  584. handleSpotifyUrls( urls );
  585. #ifdef QCA2_FOUND
  586. else if ( urls.contains( "grooveshark.com" ) )
  587. handleGroovesharkUrls( urls );
  588. #endif
  589. else
  590. handleTrackUrls ( urls );
  591. }
  592. void
  593. DropJob::handleTrackUrls( const QString& urls )
  594. {
  595. if ( urls.contains( "xml" ) && urls.contains( "iTunes" ) )
  596. {
  597. QStringList paths = urls.split( QRegExp( "\\s+" ), QString::SkipEmptyParts );
  598. new ItunesLoader( paths.first(), this );
  599. }
  600. else if ( urls.contains( "itunes.apple.com") )
  601. {
  602. QStringList tracks = urls.split( QRegExp( "\\s+" ), QString::SkipEmptyParts );
  603. tDebug() << "Got a list of itunes urls!" << tracks;
  604. ItunesParser* itunes = new ItunesParser( tracks, this );
  605. connect( itunes, SIGNAL( tracks( QList<Tomahawk::query_ptr> ) ), this, SLOT( onTracksAdded( QList< Tomahawk::query_ptr > ) ) );
  606. m_queryCount++;
  607. }
  608. else if ( urls.contains( "open.spotify.com/track") || urls.contains( "spotify:track" ) )
  609. {
  610. QStringList tracks = urls.split( QRegExp( "\\s+" ), QString::SkipEmptyParts );
  611. tDebug() << "Got a list of spotify urls!" << tracks;
  612. SpotifyParser* spot = new SpotifyParser( tracks, false, this );
  613. connect( spot, SIGNAL( tracks( QList<Tomahawk::query_ptr> ) ), this, SLOT( onTracksAdded( QList< Tomahawk::query_ptr > ) ) );
  614. m_queryCount++;
  615. }
  616. else if ( ShortenedLinkParser::handlesUrl( urls ) )
  617. {
  618. QStringList tracks = urls.split( QRegExp( "\\s+" ), QString::SkipEmptyParts );
  619. tDebug() << "Got a list of shortened urls!" << tracks;
  620. ShortenedLinkParser* parser = new ShortenedLinkParser( tracks, this );
  621. connect( parser, SIGNAL( urls( QStringList ) ), this, SLOT( expandedUrls( QStringList ) ) );
  622. m_queryCount++;
  623. }
  624. else
  625. {
  626. // Try Scriptresolvers
  627. QStringList tracks = urls.split( QRegExp( "\\s+" ), QString::SkipEmptyParts );
  628. foreach ( QString track, tracks )
  629. {
  630. foreach ( QPointer<ExternalResolver> resolver, Pipeline::instance()->scriptResolvers() )
  631. {
  632. if ( resolver->canParseUrl( track, ExternalResolver::UrlTypeAny ) )
  633. {
  634. ScriptCommand_LookupUrl* cmd = new ScriptCommand_LookupUrl( resolver, track );
  635. connect( cmd, SIGNAL( information( QString, QSharedPointer<QObject> ) ), this, SLOT( informationForUrl( QString, QSharedPointer<QObject> ) ) );
  636. cmd->enqueue();
  637. m_queryCount++;
  638. break;
  639. }
  640. }
  641. }
  642. }
  643. }
  644. void
  645. DropJob::expandedUrls( QStringList urls )
  646. {
  647. m_queryCount--;
  648. handleAllUrls( urls.join( "\n" ) );
  649. }
  650. void
  651. DropJob::informationForUrl( const QString&, const QSharedPointer<QObject>& information )
  652. {
  653. if ( information.isNull() )
  654. {
  655. // No information was transmitted, nothing to do.
  656. tLog( LOGVERBOSE ) << Q_FUNC_INFO << "Empty information received.";
  657. return;
  658. }
  659. tLog( LOGVERBOSE ) << Q_FUNC_INFO << "Got a drop from a ScriptResolver.";
  660. // Try to interpret as Album
  661. Tomahawk::album_ptr album = information.objectCast<Tomahawk::Album>();
  662. if ( !album.isNull() )
  663. {
  664. tLog( LOGVERBOSE ) << Q_FUNC_INFO << "Dropped an Album";
  665. if ( m_dropAction == Append )
  666. {
  667. onTracksAdded( album->tracks() );
  668. }
  669. else
  670. {
  671. // The Url describes an album
  672. ViewManager::instance()->show( album );
  673. // We're done.
  674. deleteLater();
  675. }
  676. return;
  677. }
  678. Tomahawk::artist_ptr artist = information.objectCast<Tomahawk::Artist>();
  679. if ( !artist.isNull() )
  680. {
  681. tLog( LOGVERBOSE ) << Q_FUNC_INFO << "Dropped an artist";
  682. ViewManager::instance()->show( artist );
  683. // We're done.
  684. deleteLater();
  685. }
  686. Tomahawk::playlisttemplate_ptr pltemplate = information.objectCast<Tomahawk::PlaylistTemplate>();
  687. if ( !pltemplate.isNull() )
  688. {
  689. tLog( LOGVERBOSE ) << Q_FUNC_INFO << "Dropped a playlist (template)";
  690. if ( m_dropAction == Create )
  691. {
  692. ViewManager::instance()->show( pltemplate->get() );
  693. // We're done.
  694. deleteLater();
  695. }
  696. else
  697. {
  698. onTracksAdded( pltemplate->tracks() );
  699. }
  700. return;
  701. }
  702. // Try to interpret as Playlist
  703. Tomahawk::playlist_ptr playlist = information.objectCast<Tomahawk::Playlist>();
  704. if ( !playlist.isNull() )
  705. {
  706. tLog( LOGVERBOSE ) << Q_FUNC_INFO << "Dropped a playlist";
  707. if ( m_dropAction == Create )
  708. {
  709. QList<Tomahawk::query_ptr> tracks;
  710. foreach( Tomahawk::plentry_ptr entry, playlist->entries() )
  711. {
  712. tracks.append( entry->query() );
  713. }
  714. onTracksAdded( tracks );
  715. }
  716. else
  717. {
  718. // The url describes a playlist
  719. ViewManager::instance()->show( playlist );
  720. // We're done.
  721. deleteLater();
  722. }\
  723. return;
  724. }
  725. // Try to interpret as Track/Query
  726. Tomahawk::query_ptr query = information.objectCast<Tomahawk::Query>();
  727. if ( !query.isNull() )
  728. {
  729. tLog( LOGVERBOSE ) << Q_FUNC_INFO << "Dropped a track";
  730. QList<Tomahawk::query_ptr> tracks;
  731. // The Url describes a track
  732. tracks.append( query );
  733. onTracksAdded( tracks );
  734. return;
  735. }
  736. // Nothing relevant for this url, but still finalize this query.
  737. onTracksAdded( QList<Tomahawk::query_ptr>() );
  738. }
  739. void
  740. DropJob::onTracksAdded( const QList<Tomahawk::query_ptr>& tracksList )
  741. {
  742. tDebug() << Q_FUNC_INFO << tracksList.count();
  743. /* if ( results.isEmpty() )
  744. {
  745. const QString which = album.isEmpty() ? "artist" : "album";
  746. JobStatusView::instance()->model()->addJob( new ErrorStatusMessage( tr( "No tracks found for given %1" ).arg( which ), 5 ) );
  747. }*/
  748. if ( !m_dropJob.isEmpty() )
  749. {
  750. m_dropJob.takeFirst()->setFinished();
  751. }
  752. m_resultList.append( tracksList );
  753. if ( --m_queryCount == 0 )
  754. {
  755. /* if ( m_onlyLocal )
  756. removeRemoteSources();
  757. if ( !m_allowDuplicates )
  758. removeDuplicates();*/
  759. emit tracks( m_resultList );
  760. deleteLater();
  761. }
  762. }
  763. void
  764. DropJob::removeDuplicates()
  765. {
  766. QList< Tomahawk::query_ptr > list;
  767. foreach ( const Tomahawk::query_ptr& item, m_resultList )
  768. {
  769. bool contains = false;
  770. Q_ASSERT( !item.isNull() );
  771. if ( item.isNull() )
  772. {
  773. m_resultList.removeOne( item );
  774. continue;
  775. }
  776. foreach( const Tomahawk::query_ptr &tmpItem, list )
  777. {
  778. if ( tmpItem.isNull() )
  779. {
  780. list.removeOne( tmpItem );
  781. continue;
  782. }
  783. if ( item->track()->album() == tmpItem->track()->album()
  784. && item->track()->artist() == tmpItem->track()->artist()
  785. && item->track()->track() == tmpItem->track()->track() )
  786. {
  787. if ( item->playable() && !tmpItem->playable() )
  788. list.replace( list.indexOf( tmpItem ), item );
  789. contains = true;
  790. break;
  791. }
  792. }
  793. if ( !contains )
  794. list.append( item );
  795. }
  796. m_resultList = list;
  797. }
  798. void
  799. DropJob::removeRemoteSources()
  800. {
  801. QList< Tomahawk::query_ptr > list;
  802. foreach ( const Tomahawk::query_ptr& item, m_resultList )
  803. {
  804. Q_ASSERT( !item.isNull() );
  805. if ( item.isNull() )
  806. {
  807. m_resultList.removeOne( item );
  808. continue;
  809. }
  810. foreach ( const Tomahawk::result_ptr& result, item->results() )
  811. {
  812. if ( !result->isLocal() )
  813. {
  814. list.append( item );
  815. break;
  816. }
  817. }
  818. }
  819. m_resultList = list;
  820. }
  821. QList< query_ptr >
  822. DropJob::getArtist( const QString &artist, Tomahawk::ModelMode mode )
  823. {
  824. Q_UNUSED( mode );
  825. artist_ptr artistPtr = Artist::get( artist );
  826. if ( artistPtr->playlistInterface( Mixed )->tracks().isEmpty() )
  827. {
  828. m_artistsToKeep.insert( artistPtr );
  829. connect( artistPtr.data(), SIGNAL( tracksAdded( QList<Tomahawk::query_ptr>, Tomahawk::ModelMode, Tomahawk::collection_ptr ) ),
  830. SLOT( onTracksAdded( QList<Tomahawk::query_ptr> ) ) );
  831. m_dropJob << new DropJobNotifier( QPixmap( RESPATH "images/album-icon.png" ), Album );
  832. JobStatusView::instance()->model()->addJob( m_dropJob.last() );
  833. m_queryCount++;
  834. }
  835. return artistPtr->playlistInterface( Mixed )->tracks();
  836. }
  837. QList< query_ptr >
  838. DropJob::getAlbum( const QString& artist, const QString& album )
  839. {
  840. artist_ptr artistPtr = Artist::get( artist );
  841. album_ptr albumPtr = Album::get( artistPtr, album );
  842. if ( albumPtr.isNull() )
  843. return QList< query_ptr >();
  844. //FIXME: should check tracksLoaded()
  845. if ( albumPtr->playlistInterface( Mixed )->tracks().isEmpty() )
  846. {
  847. // For albums that don't exist until this moment, we are the main shared pointer holding on.
  848. // fetching the tracks is asynchronous, so the resulting signal is queued. when we go out of scope we delete
  849. // the artist_ptr which means we never get the signal delivered. so we hold on to the album pointer till we're done
  850. m_albumsToKeep.insert( albumPtr );
  851. connect( albumPtr.data(), SIGNAL( tracksAdded( QList<Tomahawk::query_ptr>, Tomahawk::ModelMode, Tomahawk::collection_ptr ) ),
  852. SLOT( onTracksAdded( QList<Tomahawk::query_ptr> ) ) );
  853. m_dropJob << new DropJobNotifier( QPixmap( RESPATH "images/album-icon.png" ), Album );
  854. JobStatusView::instance()->model()->addJob( m_dropJob.last() );
  855. m_queryCount++;
  856. }
  857. return albumPtr->playlistInterface( Mixed )->tracks();
  858. }
  859. QList< query_ptr >
  860. DropJob::getTopTen( const QString& artist )
  861. {
  862. return getArtist( artist, Tomahawk::InfoSystemMode );
  863. }