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