PageRenderTime 453ms CodeModel.GetById 102ms app.highlight 299ms RepoModel.GetById 42ms app.codeStats 1ms

/src/libtomahawk/DropJob.cpp

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