/src/libtomahawk/GlobalActionManager.cpp

http://github.com/tomahawk-player/tomahawk · C++ · 1204 lines · 939 code · 172 blank · 93 comment · 265 complexity · 98e90d5bb5f0cd91364c5ca93530e430 MD5 · raw file

  1. /* === This file is part of Tomahawk Player - <http://tomahawk-player.org> ===
  2. *
  3. * Copyright 2011, Leo Franchi <lfranchi@kde.org>
  4. * Copyright 2011-2016, Christian Muehlhaeuser <muesli@tomahawk-player.org>
  5. * Copyright 2011, Jeff Mitchell <jeff@tomahawk-player.org>
  6. * Copyright 2013, Uwe L. Korn <uwelk@xhochy.com>
  7. * Copyright 2013, Teo Mrnjavac <teo@kde.org>
  8. *
  9. * Tomahawk is free software: you can redistribute it and/or modify
  10. * it under the terms of the GNU General Public License as published by
  11. * the Free Software Foundation, either version 3 of the License, or
  12. * (at your option) any later version.
  13. *
  14. * Tomahawk is distributed in the hope that it will be useful,
  15. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  16. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  17. * GNU General Public License for more details.
  18. *
  19. * You should have received a copy of the GNU General Public License
  20. * along with Tomahawk. If not, see <http://www.gnu.org/licenses/>.
  21. */
  22. #include "GlobalActionManager.h"
  23. #include "accounts/AccountManager.h"
  24. #include "accounts/spotify/SpotifyAccount.h"
  25. #include "audio/AudioEngine.h"
  26. #include "jobview/ErrorStatusMessage.h"
  27. #include "jobview/JobStatusModel.h"
  28. #include "jobview/JobStatusView.h"
  29. #include "playlist/dynamic/GeneratorInterface.h"
  30. #include "playlist/PlaylistTemplate.h"
  31. #include "playlist/ContextView.h"
  32. #include "playlist/TrackView.h"
  33. #include "playlist/PlayableModel.h"
  34. #include "resolvers/ExternalResolver.h"
  35. #include "resolvers/ScriptCommand_LookupUrl.h"
  36. #include "utils/JspfLoader.h"
  37. #include "utils/Logger.h"
  38. #include "utils/SpotifyParser.h"
  39. #include "utils/XspfLoader.h"
  40. #include "utils/XspfGenerator.h"
  41. #include "viewpages/SearchViewPage.h"
  42. #include "Pipeline.h"
  43. #include "TomahawkSettings.h"
  44. #include "ViewManager.h"
  45. #include <QMessageBox>
  46. #include <QFileInfo>
  47. GlobalActionManager* GlobalActionManager::s_instance = 0;
  48. using namespace Tomahawk;
  49. using namespace TomahawkUtils;
  50. GlobalActionManager*
  51. GlobalActionManager::instance()
  52. {
  53. if ( !s_instance )
  54. s_instance = new GlobalActionManager;
  55. return s_instance;
  56. }
  57. GlobalActionManager::GlobalActionManager( QObject* parent )
  58. : QObject( parent )
  59. {
  60. }
  61. GlobalActionManager::~GlobalActionManager()
  62. {
  63. }
  64. void
  65. GlobalActionManager::installResolverFromFile( const QString& resolverPath )
  66. {
  67. const QFileInfo resolverAbsoluteFilePath( resolverPath );
  68. TomahawkSettings::instance()->setScriptDefaultPath( resolverAbsoluteFilePath.absolutePath() );
  69. if ( resolverAbsoluteFilePath.baseName() == "spotify_tomahawkresolver" )
  70. {
  71. // HACK if this is a spotify resolver, we treat it specially.
  72. // usually we expect the user to just download the spotify resolver from attica,
  73. // however developers, those who build their own tomahawk, can't do that, or linux
  74. // users can't do that. However, we have an already-existing SpotifyAccount that we
  75. // know exists that we need to use this resolver path.
  76. //
  77. // Hence, we special-case the spotify resolver and directly set the path on it here.
  78. Accounts::SpotifyAccount* acct = 0;
  79. foreach ( Accounts::Account* account, Accounts::AccountManager::instance()->accounts() )
  80. {
  81. if ( Accounts::SpotifyAccount* spotify = qobject_cast< Accounts::SpotifyAccount* >( account ) )
  82. {
  83. acct = spotify;
  84. break;
  85. }
  86. }
  87. if ( acct )
  88. {
  89. acct->setManualResolverPath( resolverPath );
  90. return;
  91. }
  92. }
  93. Accounts::Account* acct =
  94. Accounts::AccountManager::instance()->accountFromPath( resolverPath );
  95. if ( !acct )
  96. {
  97. QFileInfo fi( resolverPath );
  98. JobStatusView::instance()->model()->addJob( new ErrorStatusMessage(
  99. tr( "Resolver installation from file %1 failed." )
  100. .arg( fi.fileName() ) ) );
  101. tDebug() << "Resolver was not installed:" << resolverPath;
  102. return;
  103. }
  104. int result = QMessageBox::question( JobStatusView::instance(),
  105. tr( "Install plug-in" ),
  106. tr( "<b>%1</b> %2<br/>"
  107. "by <b>%3</b><br/><br/>"
  108. "You are attempting to install a %applicationName "
  109. "plug-in from an unknown source. Plug-ins from "
  110. "untrusted sources may put your data at risk.<br/>"
  111. "Do you want to install this plug-in?" )
  112. .arg( acct->accountFriendlyName() )
  113. .arg( acct->version() )
  114. .arg( acct->author() ),
  115. QMessageBox::Yes,
  116. QMessageBox::No );
  117. if ( result != QMessageBox::Yes )
  118. return;
  119. Accounts::AccountManager::instance()->addAccount( acct );
  120. TomahawkSettings::instance()->addAccount( acct->accountId() );
  121. Accounts::AccountManager::instance()->enableAccount( acct );
  122. }
  123. bool
  124. GlobalActionManager::openUrl( const QString& url )
  125. {
  126. // Native Implementations
  127. if ( url.startsWith( "tomahawk://" ) )
  128. return parseTomahawkLink( url );
  129. else if ( url.contains( "open.spotify.com" ) || url.startsWith( "spotify:" ) )
  130. return openSpotifyLink( url );
  131. // Can we parse the Url using a ScriptResolver?
  132. bool canParse = false;
  133. QList< QPointer< ExternalResolver > > possibleResolvers;
  134. foreach ( QPointer<ExternalResolver> resolver, Pipeline::instance()->scriptResolvers() )
  135. {
  136. if ( resolver->canParseUrl( url, ExternalResolver::UrlTypeAny ) )
  137. {
  138. canParse = true;
  139. possibleResolvers << resolver;
  140. }
  141. }
  142. if ( canParse )
  143. {
  144. m_queuedUrl = url;
  145. foreach ( QPointer<ExternalResolver> resolver, possibleResolvers )
  146. {
  147. ScriptCommand_LookupUrl* cmd = new ScriptCommand_LookupUrl( resolver, url );
  148. connect( cmd, SIGNAL( information( QString, QSharedPointer<QObject> ) ), this, SLOT( informationForUrl( QString, QSharedPointer<QObject> ) ) );
  149. cmd->enqueue();
  150. }
  151. return true;
  152. }
  153. return false;
  154. }
  155. void
  156. GlobalActionManager::savePlaylistToFile( const playlist_ptr& playlist, const QString& filename )
  157. {
  158. XSPFGenerator* g = new XSPFGenerator( playlist, this );
  159. g->setProperty( "filename", filename );
  160. connect( g, SIGNAL( generated( QByteArray ) ), this, SLOT( xspfCreated( QByteArray ) ) );
  161. }
  162. void
  163. GlobalActionManager::xspfCreated( const QByteArray& xspf )
  164. {
  165. QString filename = sender()->property( "filename" ).toString();
  166. QFile f( filename );
  167. if ( !f.open( QIODevice::WriteOnly ) )
  168. {
  169. qWarning() << "Failed to open file to save XSPF:" << filename;
  170. return;
  171. }
  172. f.write( xspf );
  173. f.close();
  174. sender()->deleteLater();
  175. }
  176. bool
  177. GlobalActionManager::parseTomahawkLink( const QString& urlIn )
  178. {
  179. QString url = urlIn;
  180. if ( urlIn.startsWith( "http://toma.hk" ) )
  181. url.replace( "http://toma.hk/", "tomahawk://" );
  182. if ( url.contains( "tomahawk://" ) )
  183. {
  184. QString cmd = url.mid( 11 );
  185. cmd.replace( "%2B", "%20" );
  186. cmd.replace( "+", "%20" ); // QUrl doesn't parse '+' into " "
  187. tLog() << "Parsing tomahawk link command" << cmd;
  188. QString cmdType = cmd.split( "/" ).first();
  189. QUrl u = QUrl::fromEncoded( cmd.toUtf8() );
  190. // for backwards compatibility
  191. if ( cmdType == "load" )
  192. {
  193. if ( urlHasQueryItem( u, "xspf" ) )
  194. {
  195. QUrl xspf = QUrl::fromUserInput( urlQueryItemValue( u, "xspf" ) );
  196. XSPFLoader* l = new XSPFLoader( true, true, this );
  197. tDebug() << "Loading spiff:" << xspf.toString();
  198. l->load( xspf );
  199. connect( l, SIGNAL( ok( Tomahawk::playlist_ptr ) ), ViewManager::instance(), SLOT( show( Tomahawk::playlist_ptr ) ) );
  200. return true;
  201. }
  202. else if ( urlHasQueryItem( u, "jspf" ) )
  203. {
  204. QUrl jspf = QUrl::fromUserInput( urlQueryItemValue( u, "jspf" ) );
  205. JSPFLoader* l = new JSPFLoader( true, this );
  206. tDebug() << "Loading jspiff:" << jspf.toString();
  207. l->load( jspf );
  208. connect( l, SIGNAL( ok( Tomahawk::playlist_ptr ) ), ViewManager::instance(), SLOT( show( Tomahawk::playlist_ptr ) ) );
  209. return true;
  210. }
  211. }
  212. if ( cmdType == "playlist" )
  213. {
  214. return handlePlaylistCommand( u );
  215. }
  216. else if ( cmdType == "collection" )
  217. {
  218. return handleCollectionCommand( u );
  219. }
  220. else if ( cmdType == "queue" )
  221. {
  222. return handleQueueCommand( u );
  223. }
  224. else if ( cmdType == "station" )
  225. {
  226. return handleStationCommand( u );
  227. }
  228. else if ( cmdType == "autoplaylist" )
  229. {
  230. return handleAutoPlaylistCommand( u );
  231. }
  232. else if ( cmdType == "search" )
  233. {
  234. return handleSearchCommand( u );
  235. }
  236. else if ( cmdType == "play" )
  237. {
  238. return handlePlayCommand( u );
  239. }
  240. else if ( cmdType == "bookmark" )
  241. {
  242. return handlePlayCommand( u );
  243. }
  244. else if ( cmdType == "open" )
  245. {
  246. return handleOpenCommand( u );
  247. }
  248. else if ( cmdType == "view" )
  249. {
  250. return handleViewCommand( u );
  251. }
  252. else if ( cmdType == "import" )
  253. {
  254. return handleImportCommand( u );
  255. }
  256. else if ( cmdType == "love" )
  257. {
  258. return handleLoveCommand( u );
  259. }
  260. else
  261. {
  262. tLog() << "Tomahawk link not supported, command not known!" << cmdType << u.path();
  263. return false;
  264. }
  265. }
  266. else
  267. {
  268. tLog() << "Not a tomahawk:// link!";
  269. return false;
  270. }
  271. }
  272. bool
  273. GlobalActionManager::handlePlaylistCommand( const QUrl& url )
  274. {
  275. QStringList parts = url.path().split( "/" ).mid( 1 ); // get the rest of the command
  276. if ( parts.isEmpty() )
  277. {
  278. tLog() << "No specific playlist command:" << url.toString();
  279. return false;
  280. }
  281. if ( parts[ 0 ] == "import" )
  282. {
  283. if ( !urlHasQueryItem( url, "xspf" ) && !urlHasQueryItem( url, "jspf" ) )
  284. {
  285. tDebug() << "No xspf or jspf to load...";
  286. return false;
  287. }
  288. if ( urlHasQueryItem( url, "xspf" ) )
  289. {
  290. createPlaylistFromUrl( "xspf", urlQueryItemValue( url, "xspf" ), urlHasQueryItem( url, "title" ) ? urlQueryItemValue( url, "title" ) : QString() );
  291. return true;
  292. }
  293. else if ( urlHasQueryItem( url, "jspf" ) )
  294. {
  295. createPlaylistFromUrl( "jspf", urlQueryItemValue( url, "jspf" ), urlHasQueryItem( url, "title" ) ? urlQueryItemValue( url, "title" ) : QString() );
  296. return true;
  297. }
  298. }
  299. else if ( parts [ 0 ] == "new" )
  300. {
  301. if ( !urlHasQueryItem( url, "title" ) )
  302. {
  303. tLog() << "New playlist command needs a title...";
  304. return false;
  305. }
  306. playlist_ptr pl = Playlist::create( SourceList::instance()->getLocal(), uuid(), urlQueryItemValue( url, "title" ), QString(), QString(), false );
  307. ViewManager::instance()->show( pl );
  308. }
  309. else if ( parts[ 0 ] == "add" )
  310. {
  311. if ( !urlHasQueryItem( url, "playlistid" ) || !urlHasQueryItem( url, "title" ) || !urlHasQueryItem( url, "artist" ) )
  312. {
  313. tLog() << "Add to playlist command needs playlistid, track, and artist..." << url.toString();
  314. return false;
  315. }
  316. // TODO implement. Let the user select what playlist to add to
  317. return false;
  318. }
  319. return false;
  320. }
  321. bool
  322. GlobalActionManager::handleImportCommand( const QUrl& url )
  323. {
  324. QStringList parts = url.path().split( "/" ).mid( 1 ); // get the rest of the command
  325. if ( parts.size() < 1 )
  326. return false;
  327. if ( parts[ 0 ] == "playlist" )
  328. {
  329. if ( urlHasQueryItem( url, "xspf" ) )
  330. {
  331. createPlaylistFromUrl( "xspf", urlQueryItemValue( url, "xspf" ), urlHasQueryItem( url, "title" ) ? urlQueryItemValue( url, "title" ) : QString() );
  332. return true;
  333. }
  334. else if ( urlHasQueryItem( url, "jspf" ) )
  335. {
  336. createPlaylistFromUrl( "jspf", urlQueryItemValue( url, "jspf" ), urlHasQueryItem( url, "title" ) ? urlQueryItemValue( url, "title" ) : QString() );
  337. return true;
  338. }
  339. }
  340. return false;
  341. }
  342. void
  343. GlobalActionManager::createPlaylistFromUrl( const QString& type, const QString &url, const QString& title )
  344. {
  345. if ( type == "xspf" )
  346. {
  347. QUrl xspf = QUrl::fromUserInput( url );
  348. XSPFLoader* l= new XSPFLoader( true, true, this );
  349. l->setOverrideTitle( title );
  350. l->load( xspf );
  351. connect( l, SIGNAL( ok( Tomahawk::playlist_ptr ) ), this, SLOT( playlistCreatedToShow( Tomahawk::playlist_ptr) ) );
  352. }
  353. else if ( type == "jspf" )
  354. {
  355. QUrl jspf = QUrl::fromUserInput( url );
  356. JSPFLoader* l= new JSPFLoader( true, this );
  357. l->setOverrideTitle( title );
  358. l->load( jspf );
  359. connect( l, SIGNAL( ok( Tomahawk::playlist_ptr ) ), this, SLOT( playlistCreatedToShow( Tomahawk::playlist_ptr) ) );
  360. }
  361. }
  362. void
  363. GlobalActionManager::playlistCreatedToShow( const playlist_ptr& pl )
  364. {
  365. connect( pl.data(), SIGNAL( revisionLoaded( Tomahawk::PlaylistRevision ) ), this, SLOT( playlistReadyToShow() ) );
  366. pl->setProperty( "sharedptr", QVariant::fromValue<Tomahawk::playlist_ptr>( pl ) );
  367. }
  368. void
  369. GlobalActionManager::playlistReadyToShow()
  370. {
  371. playlist_ptr pl = sender()->property( "sharedptr" ).value<Tomahawk::playlist_ptr>();
  372. if ( !pl.isNull() )
  373. ViewManager::instance()->show( pl );
  374. disconnect( sender(), SIGNAL( revisionLoaded( Tomahawk::PlaylistRevision ) ), this, SLOT( playlistReadyToShow() ) );
  375. }
  376. bool
  377. GlobalActionManager::handleCollectionCommand( const QUrl& url )
  378. {
  379. QStringList parts = url.path().split( "/" ).mid( 1 ); // get the rest of the command
  380. if ( parts.isEmpty() )
  381. {
  382. tLog() << "No specific collection command:" << url.toString();
  383. return false;
  384. }
  385. if ( parts[ 0 ] == "add" )
  386. {
  387. // TODO implement
  388. }
  389. return false;
  390. }
  391. bool
  392. GlobalActionManager::handleOpenCommand( const QUrl& url )
  393. {
  394. QStringList parts = url.path().split( "/" ).mid( 1 );
  395. if ( parts.isEmpty() )
  396. {
  397. tLog() << "No specific type to open:" << url.toString();
  398. return false;
  399. }
  400. // TODO user configurable in the UI
  401. return doQueueAdd( parts, urlQueryItems( url ) );
  402. }
  403. bool
  404. GlobalActionManager::handleLoveCommand( const QUrl& url )
  405. {
  406. QStringList parts = url.path().split( "/" ).mid( 1 ); // get the rest of the command
  407. if ( parts.isEmpty() )
  408. {
  409. tLog() << "No specific love command:" << url.toString();
  410. return false;
  411. }
  412. QPair< QString, QString > pair;
  413. QString title, artist, album;
  414. foreach ( pair, urlQueryItems( url ) )
  415. {
  416. if ( pair.first == "title" )
  417. title = pair.second;
  418. else if ( pair.first == "artist" )
  419. artist = pair.second;
  420. else if ( pair.first == "album" )
  421. album = pair.second;
  422. }
  423. track_ptr t = Track::get( artist, title, album );
  424. if ( t.isNull() )
  425. return false;
  426. t->setLoved( true );
  427. return true;
  428. }
  429. void
  430. GlobalActionManager::handleOpenTrack( const query_ptr& q )
  431. {
  432. ViewManager::instance()->queue()->view()->trackView()->model()->appendQuery( q );
  433. ViewManager::instance()->showQueuePage();
  434. if ( !AudioEngine::instance()->isPlaying() && !AudioEngine::instance()->isPaused() )
  435. {
  436. connect( q.data(), SIGNAL( resolvingFinished( bool ) ), this, SLOT( waitingForResolved( bool ) ) );
  437. m_waitingToPlay = q;
  438. }
  439. }
  440. void
  441. GlobalActionManager::handleOpenTracks( const QList< query_ptr >& queries )
  442. {
  443. if ( queries.isEmpty() )
  444. return;
  445. ViewManager::instance()->queue()->view()->trackView()->model()->appendQueries( queries );
  446. ViewManager::instance()->showQueuePage();
  447. if ( !AudioEngine::instance()->isPlaying() && !AudioEngine::instance()->isPaused() )
  448. {
  449. connect( queries.first().data(), SIGNAL( resolvingFinished( bool ) ), this, SLOT( waitingForResolved( bool ) ) );
  450. m_waitingToPlay = queries.first();
  451. }
  452. }
  453. void
  454. GlobalActionManager::handlePlayTrack( const query_ptr& qry )
  455. {
  456. playNow( qry );
  457. }
  458. void
  459. GlobalActionManager::informationForUrl(const QString& url, const QSharedPointer<QObject>& information)
  460. {
  461. tLog( LOGVERBOSE ) << Q_FUNC_INFO << "Got Information for URL:" << url;
  462. if ( m_queuedUrl != url )
  463. {
  464. // This url is not anymore active, result was too late.
  465. return;
  466. }
  467. if ( information.isNull() )
  468. {
  469. // No information was transmitted, nothing to do.
  470. tLog( LOGVERBOSE ) << Q_FUNC_INFO << "Empty information received.";
  471. return;
  472. }
  473. // If we reach this point, we found information that can be parsed.
  474. // So invalidate queued Url
  475. m_queuedUrl = "";
  476. // Try to interpret as Artist
  477. Tomahawk::artist_ptr artist = information.objectCast<Tomahawk::Artist>();
  478. if ( !artist.isNull() )
  479. {
  480. // The Url describes an artist
  481. ViewManager::instance()->show( artist );
  482. return;
  483. }
  484. // Try to interpret as Album
  485. Tomahawk::album_ptr album = information.objectCast<Tomahawk::Album>();
  486. if ( !album.isNull() )
  487. {
  488. // The Url describes an album
  489. ViewManager::instance()->show( album );
  490. return;
  491. }
  492. Tomahawk::playlisttemplate_ptr pltemplate = information.objectCast<Tomahawk::PlaylistTemplate>();
  493. if ( !pltemplate.isNull() )
  494. {
  495. ViewManager::instance()->show( pltemplate->get() );
  496. return;
  497. }
  498. // Try to interpret as Track/Query
  499. Tomahawk::query_ptr query = information.objectCast<Tomahawk::Query>();
  500. if ( !query.isNull() )
  501. {
  502. // The Url describes a track
  503. ViewManager::instance()->show( query );
  504. return;
  505. }
  506. // Try to interpret as Playlist
  507. Tomahawk::playlist_ptr playlist = information.objectCast<Tomahawk::Playlist>();
  508. if ( !playlist.isNull() )
  509. {
  510. // The url describes a playlist
  511. ViewManager::instance()->show( playlist );
  512. return;
  513. }
  514. // Could not cast to a known type.
  515. tLog() << Q_FUNC_INFO << "Can't load parsed information for " << url;
  516. }
  517. bool
  518. GlobalActionManager::handleQueueCommand( const QUrl& url )
  519. {
  520. QStringList parts = url.path().split( "/" ).mid( 1 ); // get the rest of the command
  521. if ( parts.isEmpty() )
  522. {
  523. tLog() << "No specific queue command:" << url.toString();
  524. return false;
  525. }
  526. if ( parts[ 0 ] == "add" )
  527. {
  528. doQueueAdd( parts.mid( 1 ), urlQueryItems( url ) );
  529. }
  530. else
  531. {
  532. tLog() << "Only queue/add/track is support at the moment, got:" << parts;
  533. return false;
  534. }
  535. return false;
  536. }
  537. bool
  538. GlobalActionManager::doQueueAdd( const QStringList& parts, const QList< QPair< QString, QString > >& queryItems )
  539. {
  540. if ( parts.size() && parts[ 0 ] == "track" )
  541. {
  542. if ( queueSpotify( parts, queryItems ) )
  543. return true;
  544. QPair< QString, QString > pair;
  545. QString title, artist, album, urlStr;
  546. foreach ( pair, queryItems )
  547. {
  548. pair.second = pair.second.replace( "+", " " ); // QUrl::queryItems doesn't decode + to a space :(
  549. if ( pair.first == "title" )
  550. title = pair.second;
  551. else if ( pair.first == "artist" )
  552. artist = pair.second;
  553. else if ( pair.first == "album" )
  554. album = pair.second;
  555. else if ( pair.first == "url" )
  556. urlStr = pair.second;
  557. }
  558. if ( !title.isEmpty() || !artist.isEmpty() || !album.isEmpty() )
  559. {
  560. // an individual; query to add to queue
  561. query_ptr q = Query::get( artist, title, album, uuid(), false );
  562. if ( q.isNull() )
  563. return false;
  564. if ( !urlStr.isEmpty() )
  565. {
  566. q->setResultHint( urlStr );
  567. q->setSaveHTTPResultHint( true );
  568. }
  569. Pipeline::instance()->resolve( q, true );
  570. handleOpenTrack( q );
  571. return true;
  572. }
  573. else
  574. { // a list of urls to add to the queue
  575. foreach ( pair, queryItems )
  576. {
  577. if ( pair.first != "url" )
  578. continue;
  579. QUrl track = QUrl::fromUserInput( pair.second );
  580. //FIXME: isLocalFile is Qt 4.8
  581. if ( track.toString().startsWith( "file://" ) )
  582. {
  583. // it's local, so we see if it's in the DB and load it if so
  584. // TODO
  585. }
  586. else
  587. { // give it a web result hint
  588. QFileInfo info( track.path() );
  589. QString artistText = track.host();
  590. if ( artistText.isEmpty() )
  591. artistText = info.absolutePath();
  592. if ( artistText.isEmpty() )
  593. artistText = track.toString();
  594. query_ptr q = Query::get( artistText, info.baseName(), QString(), uuid(), false );
  595. if ( q.isNull() )
  596. continue;
  597. q->setResultHint( track.toString() );
  598. q->setSaveHTTPResultHint( true );
  599. Pipeline::instance()->resolve( q );
  600. ViewManager::instance()->queue()->view()->trackView()->model()->appendQuery( q );
  601. ViewManager::instance()->showQueuePage();
  602. }
  603. return true;
  604. }
  605. }
  606. }
  607. else if ( parts.size() && parts[ 0 ] == "playlist" )
  608. {
  609. QString xspfUrl, jspfUrl;
  610. for ( int i = 0; i < queryItems.size(); i++ )
  611. {
  612. const QPair< QString, QString > queryItem = queryItems.at( i );
  613. if ( queryItem.first == "xspf" )
  614. {
  615. xspfUrl = queryItem.second;
  616. break;
  617. }
  618. else if ( queryItem.first == "jspf" )
  619. {
  620. jspfUrl = queryItem.second;
  621. break;
  622. }
  623. }
  624. if ( !xspfUrl.isEmpty() )
  625. {
  626. XSPFLoader* loader = new XSPFLoader( false, false, this );
  627. connect( loader, SIGNAL( tracks( QList<Tomahawk::query_ptr> ) ), this, SLOT( handleOpenTracks( QList< Tomahawk::query_ptr > ) ) );
  628. loader->load( QUrl( xspfUrl ) );
  629. loader->setAutoDelete( true );
  630. return true;
  631. }
  632. else if ( !jspfUrl.isEmpty() )
  633. {
  634. JSPFLoader* loader = new JSPFLoader( false, this );
  635. connect( loader, SIGNAL( tracks( QList<Tomahawk::query_ptr> ) ), this, SLOT( handleOpenTracks( QList< Tomahawk::query_ptr > ) ) );
  636. loader->load( QUrl( jspfUrl ) );
  637. loader->setAutoDelete( true );
  638. return true;
  639. }
  640. }
  641. return false;
  642. }
  643. bool
  644. GlobalActionManager::queueSpotify( const QStringList& , const QList< QPair< QString, QString > >& queryItems )
  645. {
  646. QString url;
  647. QPair< QString, QString > pair;
  648. foreach ( pair, queryItems )
  649. {
  650. if ( pair.first == "spotifyURL" )
  651. url = pair.second;
  652. else if ( pair.first == "spotifyURI" )
  653. url = pair.second;
  654. }
  655. if ( url.isEmpty() )
  656. return false;
  657. openSpotifyLink( url );
  658. return true;
  659. }
  660. bool
  661. GlobalActionManager::handleSearchCommand( const QUrl& url )
  662. {
  663. // open the super collection and set this as the search filter
  664. QString queryStr;
  665. if ( urlHasQueryItem( url, "query" ) )
  666. queryStr = urlQueryItemValue( url, "query" );
  667. else
  668. {
  669. QStringList query;
  670. if ( urlHasQueryItem( url, "artist" ) )
  671. query << urlQueryItemValue( url, "artist" );
  672. if ( urlHasQueryItem( url, "album" ) )
  673. query << urlQueryItemValue( url, "album" );
  674. if ( urlHasQueryItem( url, "title" ) )
  675. query << urlQueryItemValue( url, "title" );
  676. queryStr = query.join( " " );
  677. }
  678. if ( queryStr.trimmed().isEmpty() )
  679. return false;
  680. ViewManager::instance()->show( new SearchWidget( queryStr.trimmed() ) );
  681. return true;
  682. }
  683. bool
  684. GlobalActionManager::handleViewCommand( const QUrl& url )
  685. {
  686. QStringList parts = url.path().split( "/" ).mid( 1 ); // get the rest of the command
  687. if ( parts.isEmpty() )
  688. {
  689. tLog() << "No specific view command:" << url.toString();
  690. return false;
  691. }
  692. if ( parts[ 0 ] == "artist" )
  693. {
  694. const QString artist = urlQueryItemValue( url, "name" );
  695. if ( artist.isEmpty() )
  696. {
  697. tLog() << "No artist supplied for view/artist command.";
  698. return false;
  699. }
  700. artist_ptr artistPtr = Artist::get( artist );
  701. if ( !artistPtr.isNull() )
  702. ViewManager::instance()->show( artistPtr );
  703. return true;
  704. }
  705. else if ( parts[ 0 ] == "album" )
  706. {
  707. const QString artist = urlQueryItemValue( url, "artist" );
  708. const QString album = urlQueryItemValue( url, "name" );
  709. if ( artist.isEmpty() || album.isEmpty() )
  710. {
  711. tLog() << "No artist or album supplied for view/album command:" << url;
  712. return false;
  713. }
  714. album_ptr albumPtr = Album::get( Artist::get( artist, false ), album, false );
  715. if ( !albumPtr.isNull() )
  716. ViewManager::instance()->show( albumPtr );
  717. return true;
  718. }
  719. else if ( parts[ 0 ] == "track" )
  720. {
  721. const QString artist = urlQueryItemValue( url, "artist" );
  722. const QString album = urlQueryItemValue( url, "album" );
  723. const QString track = urlQueryItemValue( url, "track" );
  724. if ( artist.isEmpty() || track.isEmpty() )
  725. {
  726. tLog() << "No artist or track supplied for view/track command:" << url;
  727. return false;
  728. }
  729. query_ptr queryPtr = Query::get( artist, track, album );
  730. if ( !queryPtr.isNull() )
  731. ViewManager::instance()->show( queryPtr );
  732. return true;
  733. }
  734. return false;
  735. }
  736. bool
  737. GlobalActionManager::handleAutoPlaylistCommand( const QUrl& url )
  738. {
  739. return !loadDynamicPlaylist( url, false ).isNull();
  740. }
  741. Tomahawk::dynplaylist_ptr
  742. GlobalActionManager::loadDynamicPlaylist( const QUrl& url, bool station )
  743. {
  744. QStringList parts = url.path().split( "/" ).mid( 1 ); // get the rest of the command
  745. if ( parts.isEmpty() )
  746. {
  747. tLog() << "No specific station command:" << url.toString();
  748. return Tomahawk::dynplaylist_ptr();
  749. }
  750. if ( parts[ 0 ] == "create" )
  751. {
  752. if ( !urlHasQueryItem( url, "title" ) || !urlHasQueryItem( url, "type" ) )
  753. {
  754. tLog() << "Station create command needs title and type..." << url.toString();
  755. return Tomahawk::dynplaylist_ptr();
  756. }
  757. QString title = urlQueryItemValue( url, "title" );
  758. QString type = urlQueryItemValue( url, "type" );
  759. GeneratorMode m = Static;
  760. if ( station )
  761. m = OnDemand;
  762. dynplaylist_ptr pl = DynamicPlaylist::create( SourceList::instance()->getLocal(), uuid(), title, QString(), QString(), m, false, type );
  763. pl->setMode( m );
  764. QList< dyncontrol_ptr > controls;
  765. QPair< QString, QString > param;
  766. foreach ( param, urlQueryItems( url ) )
  767. {
  768. if ( param.first == "artist" )
  769. {
  770. dyncontrol_ptr c = pl->generator()->createControl( "Artist" );
  771. c->setInput( param.second );
  772. // c->setMatch( QString::number( (int)Echonest::DynamicPlaylist::ArtistRadioType ) );
  773. // controls << c;
  774. }
  775. else if ( param.first == "artist_limitto" )
  776. {
  777. dyncontrol_ptr c = pl->generator()->createControl( "Artist" );
  778. c->setInput( param.second );
  779. // c->setMatch( QString::number( (int)Echonest::DynamicPlaylist::ArtistType ) );
  780. // controls << c;
  781. }
  782. else if ( param.first == "description" )
  783. {
  784. dyncontrol_ptr c = pl->generator()->createControl( "Artist Description" );
  785. c->setInput( param.second );
  786. // c->setMatch( QString::number( (int)Echonest::DynamicPlaylist::ArtistDescriptionType ) );
  787. // controls << c;
  788. }
  789. else if ( param.first == "variety" )
  790. {
  791. dyncontrol_ptr c = pl->generator()->createControl( "Variety" );
  792. c->setInput( param.second );
  793. // c->setMatch( QString::number( (int)Echonest::DynamicPlaylist::Variety ) );
  794. // controls << c;
  795. }
  796. else if ( param.first.startsWith( "tempo" ) )
  797. {
  798. dyncontrol_ptr c = pl->generator()->createControl( "Tempo" );
  799. int extra = param.first.endsWith( "_max" ) ? -1 : 0;
  800. c->setInput( param.second );
  801. // c->setMatch( QString::number( (int)Echonest::DynamicPlaylist::MinTempo + extra ) );
  802. // controls << c;
  803. }
  804. else if ( param.first.startsWith( "duration" ) )
  805. {
  806. dyncontrol_ptr c = pl->generator()->createControl( "Duration" );
  807. int extra = param.first.endsWith( "_max" ) ? -1 : 0;
  808. c->setInput( param.second );
  809. // c->setMatch( QString::number( (int)Echonest::DynamicPlaylist::MinDuration + extra ) );
  810. // controls << c;
  811. }
  812. else if ( param.first.startsWith( "loudness" ) )
  813. {
  814. dyncontrol_ptr c = pl->generator()->createControl( "Loudness" );
  815. int extra = param.first.endsWith( "_max" ) ? -1 : 0;
  816. c->setInput( param.second );
  817. // c->setMatch( QString::number( (int)Echonest::DynamicPlaylist::MinLoudness + extra ) );
  818. // controls << c;
  819. }
  820. else if ( param.first.startsWith( "danceability" ) )
  821. {
  822. dyncontrol_ptr c = pl->generator()->createControl( "Danceability" );
  823. int extra = param.first.endsWith( "_max" ) ? 1 : 0;
  824. c->setInput( param.second );
  825. // c->setMatch( QString::number( (int)Echonest::DynamicPlaylist::MinDanceability + extra ) );
  826. // controls << c;
  827. }
  828. else if ( param.first.startsWith( "energy" ) )
  829. {
  830. dyncontrol_ptr c = pl->generator()->createControl( "Energy" );
  831. int extra = param.first.endsWith( "_max" ) ? 1 : 0;
  832. c->setInput( param.second );
  833. // c->setMatch( QString::number( (int)Echonest::DynamicPlaylist::MinEnergy + extra ) );
  834. // controls << c;
  835. }
  836. else if ( param.first.startsWith( "artist_familiarity" ) )
  837. {
  838. dyncontrol_ptr c = pl->generator()->createControl( "Artist Familiarity" );
  839. int extra = param.first.endsWith( "_max" ) ? -1 : 0;
  840. c->setInput( param.second );
  841. // c->setMatch( QString::number( (int)Echonest::DynamicPlaylist::ArtistMinFamiliarity + extra ) );
  842. // controls << c;
  843. }
  844. else if ( param.first.startsWith( "artist_hotttnesss" ) )
  845. {
  846. dyncontrol_ptr c = pl->generator()->createControl( "Artist Hotttnesss" );
  847. int extra = param.first.endsWith( "_max" ) ? -1 : 0;
  848. c->setInput( param.second );
  849. // c->setMatch( QString::number( (int)Echonest::DynamicPlaylist::ArtistMinHotttnesss + extra ) );
  850. // controls << c;
  851. }
  852. else if ( param.first.startsWith( "song_hotttnesss" ) )
  853. {
  854. dyncontrol_ptr c = pl->generator()->createControl( "Song Hotttnesss" );
  855. int extra = param.first.endsWith( "_max" ) ? -1 : 0;
  856. c->setInput( param.second );
  857. // c->setMatch( QString::number( (int)Echonest::DynamicPlaylist::SongMinHotttnesss + extra ) );
  858. // controls << c;
  859. }
  860. else if ( param.first.startsWith( "longitude" ) )
  861. {
  862. dyncontrol_ptr c = pl->generator()->createControl( "Longitude" );
  863. int extra = param.first.endsWith( "_max" ) ? 1 : 0;
  864. c->setInput( param.second );
  865. // c->setMatch( QString::number( (int)Echonest::DynamicPlaylist::ArtistMinLongitude + extra ) );
  866. // controls << c;
  867. }
  868. else if ( param.first.startsWith( "latitude" ) )
  869. {
  870. dyncontrol_ptr c = pl->generator()->createControl( "Latitude" );
  871. int extra = param.first.endsWith( "_max" ) ? 1 : 0;
  872. c->setInput( param.second );
  873. // c->setMatch( QString::number( (int)Echonest::DynamicPlaylist::ArtistMinLatitude + extra ) );
  874. // controls << c;
  875. }
  876. else if ( param.first == "key" )
  877. {
  878. dyncontrol_ptr c = pl->generator()->createControl( "Key" );
  879. c->setInput( param.second );
  880. // c->setMatch( QString::number( (int)Echonest::DynamicPlaylist::Key ) );
  881. // controls << c;
  882. }
  883. else if ( param.first == "mode" )
  884. {
  885. dyncontrol_ptr c = pl->generator()->createControl( "Mode" );
  886. c->setInput( param.second );
  887. // c->setMatch( QString::number( (int)Echonest::DynamicPlaylist::Mode ) );
  888. // controls << c;
  889. }
  890. else if ( param.first == "mood" )
  891. {
  892. dyncontrol_ptr c = pl->generator()->createControl( "Mood" );
  893. c->setInput( param.second );
  894. // c->setMatch( QString::number( (int)Echonest::DynamicPlaylist::Mood ) );
  895. // controls << c;
  896. }
  897. else if ( param.first == "style" )
  898. {
  899. dyncontrol_ptr c = pl->generator()->createControl( "Style" );
  900. c->setInput( param.second );
  901. // c->setMatch( QString::number( (int)Echonest::DynamicPlaylist::Style ) );
  902. // controls << c;
  903. }
  904. else if ( param.first == "song" )
  905. {
  906. dyncontrol_ptr c = pl->generator()->createControl( "Song" );
  907. c->setInput( param.second );
  908. // c->setMatch( QString::number( (int)Echonest::DynamicPlaylist::SongRadioType ) );
  909. // controls << c;
  910. }
  911. }
  912. if ( m == OnDemand )
  913. pl->createNewRevision( uuid(), pl->currentrevision(), type, controls );
  914. else
  915. pl->createNewRevision( uuid(), pl->currentrevision(), type, controls, pl->entries() );
  916. ViewManager::instance()->show( pl );
  917. return pl;
  918. }
  919. return Tomahawk::dynplaylist_ptr();
  920. }
  921. bool
  922. GlobalActionManager::handleStationCommand( const QUrl& url )
  923. {
  924. return !loadDynamicPlaylist( url, true ).isNull();
  925. }
  926. bool
  927. GlobalActionManager::handlePlayCommand( const QUrl& url )
  928. {
  929. QStringList parts = url.path().split( "/" ).mid( 1 ); // get the rest of the command
  930. if ( parts.isEmpty() )
  931. {
  932. tLog() << "No specific play command:" << url.toString();
  933. return false;
  934. }
  935. if ( parts[ 0 ] == "track" )
  936. {
  937. if ( playSpotify( url ) )
  938. return true;
  939. QPair< QString, QString > pair;
  940. QString title, artist, album, urlStr;
  941. foreach ( pair, urlQueryItems( url ) )
  942. {
  943. if ( pair.first == "title" )
  944. title = pair.second;
  945. else if ( pair.first == "artist" )
  946. artist = pair.second;
  947. else if ( pair.first == "album" )
  948. album = pair.second;
  949. else if ( pair.first == "url" )
  950. urlStr = pair.second;
  951. }
  952. query_ptr q = Query::get( artist, title, album );
  953. if ( q.isNull() )
  954. return false;
  955. if ( !urlStr.isEmpty() )
  956. {
  957. q->setResultHint( urlStr );
  958. q->setSaveHTTPResultHint( true );
  959. }
  960. playNow( q );
  961. return true;
  962. }
  963. return false;
  964. }
  965. bool
  966. GlobalActionManager::playSpotify( const QUrl& url )
  967. {
  968. if ( !urlHasQueryItem( url, "spotifyURI" ) && !urlHasQueryItem( url, "spotifyURL" ) )
  969. return false;
  970. QString spotifyUrl = urlHasQueryItem( url, "spotifyURI" ) ? urlQueryItemValue( url, "spotifyURI" ) : urlQueryItemValue( url, "spotifyURL" );
  971. SpotifyParser* p = new SpotifyParser( spotifyUrl, false, this );
  972. connect( p, SIGNAL( track( Tomahawk::query_ptr ) ), this, SLOT( playOrQueueNow( Tomahawk::query_ptr ) ) );
  973. return true;
  974. }
  975. void
  976. GlobalActionManager::playNow( const query_ptr& q )
  977. {
  978. Pipeline::instance()->resolve( q, true );
  979. m_waitingToPlay = q;
  980. q->setProperty( "playNow", true );
  981. connect( q.data(), SIGNAL( resolvingFinished( bool ) ), this, SLOT( waitingForResolved( bool ) ) );
  982. }
  983. void
  984. GlobalActionManager::playOrQueueNow( const query_ptr& q )
  985. {
  986. Pipeline::instance()->resolve( q, true );
  987. m_waitingToPlay = q;
  988. connect( q.data(), SIGNAL( resolvingFinished( bool ) ), this, SLOT( waitingForResolved( bool ) ) );
  989. }
  990. void
  991. GlobalActionManager::showPlaylist()
  992. {
  993. if ( m_toShow.isNull() )
  994. return;
  995. ViewManager::instance()->show( m_toShow );
  996. m_toShow.clear();
  997. }
  998. void
  999. GlobalActionManager::waitingForResolved( bool /* success */ )
  1000. {
  1001. if ( m_waitingToPlay.data() != sender() )
  1002. {
  1003. m_waitingToPlay.clear();
  1004. return;
  1005. }
  1006. if ( !m_waitingToPlay.isNull() && m_waitingToPlay->playable() )
  1007. {
  1008. // play it!
  1009. // AudioEngine::instance()->playItem( AudioEngine::instance()->playlist(), m_waitingToPlay->results().first() );
  1010. if ( sender() && sender()->property( "playNow" ).toBool() )
  1011. {
  1012. if ( !AudioEngine::instance()->playlist().isNull() )
  1013. AudioEngine::instance()->playItem( AudioEngine::instance()->playlist(), m_waitingToPlay->results().first() );
  1014. else
  1015. {
  1016. ViewManager::instance()->queue()->view()->trackView()->model()->appendQuery( m_waitingToPlay );
  1017. AudioEngine::instance()->play();
  1018. }
  1019. }
  1020. else
  1021. AudioEngine::instance()->play();
  1022. m_waitingToPlay.clear();
  1023. }
  1024. }
  1025. /// SPOTIFY URL HANDLING
  1026. bool
  1027. GlobalActionManager::openSpotifyLink( const QString& link )
  1028. {
  1029. SpotifyParser* spot = new SpotifyParser( link, false, this );
  1030. connect( spot, SIGNAL( track( Tomahawk::query_ptr ) ), this, SLOT( handleOpenTrack( Tomahawk::query_ptr ) ) );
  1031. return true;
  1032. }