PageRenderTime 123ms CodeModel.GetById 2ms app.highlight 112ms RepoModel.GetById 1ms app.codeStats 1ms

/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
  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}