File indexing completed on 2025-02-23 04:27:36

0001 /****************************************************************************************
0002  * Copyright (c) 2006 Ian Monroe <ian@monroe.nu>                                        *
0003  * Copyright (c) 2007 Maximilian Kossick <maximilian.kossick@googlemail.com>            *
0004  *                                                                                      *
0005  * This program is free software; you can redistribute it and/or modify it under        *
0006  * the terms of the GNU General Public License as published by the Free Software        *
0007  * Foundation; either version 2 of the License, or (at your option) any later           *
0008  * version.                                                                             *
0009  *                                                                                      *
0010  * This program is distributed in the hope that it will be useful, but WITHOUT ANY      *
0011  * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A      *
0012  * PARTICULAR PURPOSE. See the GNU General Public License for more details.             *
0013  *                                                                                      *
0014  * You should have received a copy of the GNU General Public License along with         *
0015  * this program.  If not, see <http://www.gnu.org/licenses/>.                           *
0016  ****************************************************************************************/
0017 
0018 #define DEBUG_PREFIX "DaapReader"
0019 
0020 #include "Reader.h"
0021 
0022 #include "authentication/contentfetcher.h"
0023 #include "../DaapCollection.h"
0024 #include "../DaapMeta.h"
0025 #include "core/support/Debug.h"
0026 
0027 
0028 #include <QByteArray>
0029 #include <QDateTime>
0030 #include <QDataStream>
0031 #include <QVariant>
0032 
0033 #include <ThreadWeaver/ThreadWeaver>
0034 #include <ThreadWeaver/Queue>
0035 
0036 using namespace Daap;
0037 using namespace Meta;
0038 
0039 //#define DEBUGTAG( VAR ) debug() << tag << " has value " << VAR;
0040 #define DEBUGTAG( VAR )
0041 
0042 Reader::Reader( Collections::DaapCollection* mc, const QString& host, quint16 port, const QString& password, QObject* parent, const char* name)
0043     : QObject( parent )
0044     , m_memColl( mc )
0045     , m_host( host )
0046     , m_port( port )
0047     , m_sessionId( -1 )
0048     , m_password( password )
0049 {
0050     setObjectName( name );
0051     debug() << "Host: " << host << " port: " << port;
0052 
0053     // these content codes are needed to learn all others
0054     m_codes["mccr"] = Code( "dmap.contentcodesresponse", CONTAINER );
0055     m_codes["mstt"] = Code( "dmap.status", LONG );
0056     m_codes["mdcl"] = Code( "dmap.dictionary", CONTAINER );
0057     // mcnm is actually an int, but string makes parsing easier
0058     m_codes["mcnm"] = Code( "dmap.contentcodesnumber", STRING );
0059     m_codes["mcna"] = Code( "dmap.contentcodesname", STRING );
0060     m_codes["mcty"] = Code( "dmap.contentcodestype", SHORT );
0061 
0062     // stupid, stupid. The reflection just isn't good enough
0063     // to connect to an iPhoto server.
0064     m_codes["ppro"] = Code( "dpap.protocolversion", LONG );
0065     m_codes["avdb"] = Code( "daap.serverdatabases", CONTAINER );
0066     m_codes["adbs"] = Code( "daap.databasesongs", CONTAINER );
0067     m_codes["pret"] = Code( "dpap.unknown", CONTAINER );
0068 }
0069 
0070 Reader::~Reader()
0071 {  }
0072 
0073 void
0074 Reader::logoutRequest()
0075 {
0076     DEBUG_BLOCK
0077     ContentFetcher* http = new ContentFetcher( m_host, m_port, m_password, this, "readerLogoutHttp" );
0078     connect( http, &ContentFetcher::httpError, this, &Reader::fetchingError );
0079     connect( http, &ContentFetcher::finished, this, &Reader::logoutRequestFinished );
0080     http->getDaap( "/logout?" + m_loginString );
0081 }
0082 
0083 void
0084 Reader::logoutRequestFinished()
0085 {
0086     DEBUG_BLOCK
0087     sender()->deleteLater();
0088     deleteLater();
0089 }
0090 
0091 void
0092 Reader::loginRequest()
0093 {
0094     DEBUG_BLOCK
0095     ContentFetcher* http = new ContentFetcher( m_host, m_port, m_password, this, "readerHttp");
0096     connect( http, &ContentFetcher::httpError, this, &Reader::fetchingError );
0097     connect( http, &ContentFetcher::finished, this, &Reader::contentCodesReceived );
0098     http->getDaap( "/content-codes" );
0099 }
0100 
0101 void
0102 Reader::contentCodesReceived()
0103 {
0104     DEBUG_BLOCK
0105     ContentFetcher* http = (ContentFetcher*) sender();
0106     disconnect( http, &ContentFetcher::finished, this, &Reader::contentCodesReceived );
0107 
0108     QDataStream raw( http->results() );
0109     Map contentCodes = parse( raw );
0110     QList<QVariant> root = contentCodes["mccr"].toList();
0111     if( root.isEmpty() )
0112         return; //error
0113     root = root[0].toMap().value( "mdcl" ).toList();
0114     foreach( const QVariant &v, root )
0115     {
0116         Map entry = v.toMap();
0117         QString code = entry.value( "mcnm" ).toList().value( 0 ).toString();
0118         QString name = entry.value( "mcna" ).toList().value( 0 ).toString();
0119         ContentTypes type = ContentTypes( entry.value( "mcty" ).toList().value( 0 ).toInt() );
0120         if( !m_codes.contains( code ) && !code.isEmpty() && type > 0 )
0121         {
0122             m_codes[code] = Code( name, type );
0123             debug() << "Added DAAP code" << code << ":" << name << "with type" << type;
0124         }
0125     }
0126 
0127     connect( http, &ContentFetcher::loginRequired,
0128              this, &Reader::loginHeaderReceived );
0129     http->getDaap( "/login" );
0130 }
0131 
0132 void
0133 Reader::loginHeaderReceived()
0134 {
0135     DEBUG_BLOCK
0136     ContentFetcher* http = (ContentFetcher*) sender();
0137     disconnect( http, &ContentFetcher::loginRequired,
0138                 this, &Reader::loginHeaderReceived );
0139 
0140     Q_EMIT passwordRequired();
0141     http->deleteLater();
0142 
0143 //     connect( http, &ContentFetcher::finished, this, &Reader::loginFinished );
0144 }
0145 
0146 
0147 void
0148 Reader::loginFinished()
0149 {
0150     DEBUG_BLOCK
0151     ContentFetcher* http = (ContentFetcher*) sender();
0152     disconnect( http, &ContentFetcher::finished, this, &Reader::loginFinished );
0153 
0154     QDataStream raw( http->results() );
0155     Map loginResults = parse( raw );
0156     QVariantList list = loginResults.value( "mlog" ).toList();
0157     debug() << "list size is " << list.size();
0158     QVariantList innerList = list.value( 0 ).toMap().value( "mlid" ).toList();
0159     debug() << "innerList size is " << innerList.size();
0160     if( innerList.isEmpty() )
0161     {
0162         http->deleteLater();
0163         return;
0164     }
0165     m_sessionId = innerList.value( 0 ).toInt();
0166     m_loginString = "session-id=" + QString::number( m_sessionId );
0167     connect( http, &ContentFetcher::finished, this, &Reader::updateFinished );
0168     http->getDaap( "/update?" + m_loginString );
0169 }
0170 
0171 void
0172 Reader::updateFinished()
0173 {
0174     DEBUG_BLOCK
0175     ContentFetcher* http = (ContentFetcher*) sender();
0176     disconnect( http, &ContentFetcher::finished, this, &Reader::updateFinished );
0177 
0178     QDataStream raw( http->results() );
0179     Map updateResults = parse( raw );
0180     if( updateResults["mupd"].toList().isEmpty() )
0181         return; //error
0182     if( updateResults["mupd"].toList()[0].toMap()["musr"].toList().isEmpty() )
0183         return; //error
0184     m_loginString = m_loginString + "&revision-number="  +
0185             QString::number( updateResults["mupd"].toList()[0].toMap()["musr"].toList()[0].toInt() );
0186 
0187     connect( http, &ContentFetcher::finished, this, &Reader::databaseIdFinished );
0188     http->getDaap( "/databases?" + m_loginString );
0189 }
0190 
0191 void
0192 Reader::databaseIdFinished()
0193 {
0194     ContentFetcher* http = (ContentFetcher*) sender();
0195     disconnect( http, &ContentFetcher::finished, this, &Reader::databaseIdFinished );
0196 
0197     QDataStream raw( http->results() );
0198     Map dbIdResults = parse( raw );
0199     m_databaseId = QString::number( dbIdResults["avdb"].toList()[0].toMap()["mlcl"].toList()[0].toMap()["mlit"].toList()[0].toMap()["miid"].toList()[0].toInt() );
0200     connect( http, &ContentFetcher::finished, this, &Reader::songListFinished );
0201     http->getDaap( QStringLiteral("/databases/%1/items?type=music&meta=dmap.itemid,dmap.itemname,daap.songformat,daap.songartist,daap.songalbum,daap.songtime,daap.songtracknumber,daap.songcomment,daap.songyear,daap.songgenre&%2")
0202                 .arg( m_databaseId, m_loginString ) );
0203 
0204 }
0205 
0206 void
0207 Reader::songListFinished()
0208 {
0209     DEBUG_BLOCK
0210     ContentFetcher* http = (ContentFetcher*) sender();
0211     disconnect( http, &ContentFetcher::finished, this, &Reader::songListFinished );
0212 
0213     QByteArray result = http->results();
0214     http->deleteLater();
0215 
0216     ThreadWeaver::Queue::instance()->enqueue( QSharedPointer<ThreadWeaver::Job>(new WorkerThread( result, this, m_memColl )) );
0217 }
0218 
0219 bool
0220 Reader::parseSongList( const QByteArray &data, bool set_collection )
0221 {
0222     // The original implementation used parse(), which uses addElement() and
0223     // makes heavy usage of QMaps and QList which hurts performance very badly.
0224     // Therefore this function parses the daap responses directly into the
0225     // DaapCollection which is 27 times faster here and saves a slight bit of
0226     // heap space.
0227     // parse() and addElement() create a more qt like structure though and might be
0228     // kept for other daap tasks.
0229 
0230     DEBUG_BLOCK
0231     QDataStream raw( data );
0232 
0233     // Cache for music data
0234     QString itemId;
0235     QString format;
0236     QString title;
0237     QString artist;
0238     QString composer;
0239     QString comment;
0240     QString album;
0241     QString genre;
0242     int year = 0;
0243     qint32 trackNumber=0;
0244     qint32 songTime=0;
0245 
0246     while( !raw.atEnd() )
0247     {
0248         char rawTag[5];
0249         quint32 tagLength = getTagAndLength( raw, rawTag );
0250 
0251         if( tagLength == 0 )
0252             continue;
0253 
0254         QVariant tagData = readTagData( raw, rawTag, tagLength );
0255 
0256         if( !tagData.isValid() )
0257             continue;
0258 
0259         QString tag = QString( rawTag );
0260 
0261         if( m_codes[tag].type == CONTAINER )
0262         {
0263              parseSongList( tagData.toByteArray() );
0264              continue;
0265         }
0266 
0267         if( tag == "astn" )
0268             trackNumber = tagData.toInt();
0269         else if( tag == "asyr" )
0270             year = tagData.toInt();
0271         else if( tag == "miid" )
0272             itemId = tagData.toString();
0273         else if(tag == "astm" )
0274             songTime = tagData.toInt();
0275         else if( tag== "asfm" )
0276             format = tagData.toString();
0277         else if( tag == "minm" )
0278             title = tagData.toString();
0279         else if( tag == "asal" )
0280             album = tagData.toString();
0281         else if( tag == "asar" )
0282             artist = tagData.toString();
0283         else if( tag == "ascp" )
0284             composer = tagData.toString();
0285         else if( tag == "ascm" )
0286             comment = tagData.toString();
0287         else if( tag == "asgn" )
0288             genre = tagData.toString();
0289     }
0290 
0291     if( !itemId.isEmpty() )
0292         addTrack( itemId, title, artist, composer, comment, album, genre, year, format, trackNumber, songTime );
0293 
0294     if( set_collection )
0295     {
0296         m_memColl->memoryCollection()->acquireWriteLock();
0297         m_memColl->memoryCollection()->setTrackMap( m_trackMap );
0298         m_memColl->memoryCollection()->setArtistMap( m_artistMap );
0299         m_memColl->memoryCollection()->setAlbumMap( m_albumMap );
0300         m_memColl->memoryCollection()->setGenreMap( m_genreMap );
0301         m_memColl->memoryCollection()->setComposerMap( m_composerMap );
0302         m_memColl->memoryCollection()->setYearMap( m_yearMap );
0303         m_memColl->memoryCollection()->releaseLock();
0304         m_trackMap.clear();
0305         m_artistMap.clear();
0306         m_albumMap.clear();
0307         m_genreMap.clear();
0308         m_composerMap.clear();
0309         m_yearMap.clear();
0310     }
0311     return true;
0312 }
0313 
0314 void
0315 Reader::addTrack( const QString& itemId, const QString& title, const QString& artist, const QString& composer,
0316                   const QString& comment, const QString& album, const QString& genre, int year, const QString& format,
0317                   qint32 trackNumber, qint32 songTime )
0318 {
0319     DaapTrackPtr track( new DaapTrack( m_memColl, m_host, m_port, m_databaseId, itemId, format ) );
0320     track->setTitle( title );
0321     track->setLength( songTime );
0322     track->setTrackNumber( trackNumber );
0323     track->setComment( comment );
0324     track->setComposer( composer );
0325 
0326     DaapArtistPtr artistPtr;
0327     if ( m_artistMap.contains( artist ) )
0328         artistPtr = DaapArtistPtr::staticCast( m_artistMap.value( artist ) );
0329     else
0330     {
0331         artistPtr = DaapArtistPtr( new DaapArtist( artist ) );
0332         m_artistMap.insert( artist, ArtistPtr::staticCast( artistPtr ) );
0333     }
0334     artistPtr->addTrack( track );
0335     track->setArtist( artistPtr );
0336 
0337     DaapAlbumPtr albumPtr;
0338     if ( m_albumMap.contains( album, artist ) )
0339         albumPtr = DaapAlbumPtr::staticCast( m_albumMap.value( album, artist ) );
0340     else
0341     {
0342         albumPtr = DaapAlbumPtr( new DaapAlbum( album ) );
0343         albumPtr->setAlbumArtist( artistPtr );
0344         m_albumMap.insert( AlbumPtr::staticCast( albumPtr ) );
0345     }
0346     albumPtr->addTrack( track );
0347     track->setAlbum( albumPtr );
0348 
0349     DaapComposerPtr composerPtr;
0350     if ( m_composerMap.contains( composer ) )
0351         composerPtr = DaapComposerPtr::staticCast( m_composerMap.value( composer ) );
0352     else
0353     {
0354         composerPtr = DaapComposerPtr( new DaapComposer ( composer ) );
0355         m_composerMap.insert( composer, ComposerPtr::staticCast( composerPtr ) );
0356     }
0357     composerPtr->addTrack( track );
0358     track->setComposer ( composerPtr );
0359 
0360     DaapYearPtr yearPtr;
0361     if ( m_yearMap.contains( year ) )
0362         yearPtr = DaapYearPtr::staticCast( m_yearMap.value( year ) );
0363     else
0364     {
0365         yearPtr = DaapYearPtr( new DaapYear( QString::number(year) ) );
0366         m_yearMap.insert( year, YearPtr::staticCast( yearPtr ) );
0367     }
0368     yearPtr->addTrack( track );
0369     track->setYear( yearPtr );
0370 
0371     DaapGenrePtr genrePtr;
0372     if ( m_genreMap.contains( genre ) )
0373         genrePtr = DaapGenrePtr::staticCast( m_genreMap.value( genre ) );
0374     else
0375     {
0376         genrePtr = DaapGenrePtr( new DaapGenre( genre ) );
0377         m_genreMap.insert( genre, GenrePtr::staticCast( genrePtr ) );
0378     }
0379     genrePtr->addTrack( track );
0380     track->setGenre( genrePtr );
0381     m_trackMap.insert( track->uidUrl(), TrackPtr::staticCast( track ) );
0382 }
0383 
0384 
0385 quint32
0386 Reader::getTagAndLength( QDataStream &raw, char tag[5] )
0387 {
0388     tag[4] = 0;
0389     raw.readRawData(tag, 4);
0390     quint32 tagLength = 0;
0391     raw >> tagLength;
0392     return tagLength;
0393 }
0394 
0395 QVariant
0396 Reader::readTagData( QDataStream &raw, char *tag, quint32 tagLength)
0397 {
0398     /**
0399     * Consume tagLength bytes of data from the stream and convert it to the
0400     * proper type, while making sure that datalength/datatype mismatches are handled properly
0401     */
0402 
0403     QVariant ret = QVariant();
0404 
0405     if ( tagLength == 0 )
0406         return ret;
0407 
0408 #define READ_DATA(var) \
0409     DEBUGTAG( var ) \
0410     if( sizeof(var) != tagLength ) { \
0411         warning() << "Bad tag data length:" << tag << ":" << tagLength; \
0412         raw.skipRawData(tagLength); \
0413         break; \
0414     } else { \
0415         raw >> var ; \
0416         ret = QVariant(var); \
0417     }
0418     switch( m_codes[tag].type )
0419     {
0420         case CHAR:
0421         {
0422             qint8 charData;
0423             READ_DATA( charData )
0424             break;
0425         }
0426         case SHORT:
0427         {
0428             qint16 shortData;
0429             READ_DATA( shortData )
0430             break;
0431         }
0432         case LONG:
0433         {
0434             qint32 longData;
0435             READ_DATA( longData );
0436             break;
0437         }
0438         case LONGLONG:
0439         {
0440             qint64 longlongData;
0441             READ_DATA( longlongData );
0442             break;
0443         }
0444         case STRING:
0445         {
0446             QByteArray stringData( tagLength, ' ' );
0447             raw.readRawData( stringData.data(), tagLength );
0448             ret = QVariant(QString::fromUtf8( stringData, tagLength ));
0449             DEBUGTAG( QString::fromUtf8( stringData, tagLength ) )
0450             break;
0451         }
0452         case DATE:
0453         {
0454             qint64 dateData;
0455             READ_DATA( dateData )
0456             QDateTime date;
0457             date.setSecsSinceEpoch( dateData );
0458             ret = QVariant( date );
0459             break;
0460         }
0461         case DVERSION:
0462         {
0463             qint32 verData;
0464             READ_DATA( verData )
0465             QString version( "%1.%2.%3" );
0466             version = version.arg( verData >> 16, (verData >> 8) & 0xFF, verData & 0xFF);
0467             ret = QVariant( version );
0468             break;
0469         }
0470         case CONTAINER:
0471         {
0472             QByteArray containerData( tagLength, ' ' );
0473             raw.readRawData( containerData.data(), tagLength );
0474             ret = QVariant( containerData );
0475             break;
0476         }
0477         default:
0478             warning() << "Tag" << tag << "has unhandled type.";
0479             raw.skipRawData(tagLength);
0480             break;
0481     }
0482 #undef READ_DATA
0483     return ret;
0484 }
0485 
0486 Map
0487 Reader::parse( QDataStream &raw )
0488 {
0489     DEBUG_BLOCK
0490     /**
0491      * http://daap.sourceforge.net/docs/index.html
0492      * 0-3     Content code    OSType (unsigned long), description of the contents of this chunk
0493      * 4-7     Length  Length of the contents of this chunk (not the whole chunk)
0494      * 8-      Data    The data contained within the chunk
0495      **/
0496     Map childMap;
0497     while( !raw.atEnd() )
0498     {
0499         char tag[5];
0500         quint32 tagLength = getTagAndLength( raw, tag );
0501         if( tagLength == 0 )
0502             continue;
0503 
0504         QVariant tagData = readTagData(raw, tag, tagLength);
0505         if( !tagData.isValid() )
0506             continue;
0507 
0508         if( m_codes[tag].type == CONTAINER )
0509         {
0510             QDataStream substream( tagData.toByteArray() );
0511             addElement( childMap, tag, QVariant( parse( substream ) ) );
0512         }
0513         else
0514             addElement( childMap, tag, tagData );
0515     }
0516     return childMap;
0517 }
0518 
0519 void
0520 Reader::addElement( Map &parentMap, char* tag, const QVariant &element )
0521 {
0522     QList<QVariant> list;
0523     Map::Iterator it = parentMap.find( tag );
0524     if ( it == parentMap.end() ) {
0525         list.append( element );
0526         parentMap.insert( tag, QVariant( list ) );
0527     } else {
0528         list = it.value().toList();
0529         list.append( element );
0530         it.value() = QVariant( list );
0531     }
0532 }
0533 
0534 void
0535 Reader::fetchingError( const QString& error )
0536 {
0537     DEBUG_BLOCK
0538     sender()->deleteLater();
0539     Q_EMIT httpError( error );
0540 }
0541 
0542 WorkerThread::WorkerThread( const QByteArray &data, Reader *reader, Collections::DaapCollection *coll )
0543     : QObject()
0544     , ThreadWeaver::Job()
0545     , m_success( false )
0546     , m_data( data )
0547     , m_reader( reader )
0548 {
0549     connect( this, &WorkerThread::done, coll, &Collections::DaapCollection::loadedDataFromServer );
0550     connect( this, &WorkerThread::failed, coll, &Collections::DaapCollection::parsingFailed );
0551     connect( this, &WorkerThread::done, this, &Reader::deleteLater );
0552 }
0553 
0554 WorkerThread::~WorkerThread()
0555 {
0556     //nothing to do
0557 }
0558 
0559 bool
0560 WorkerThread::success() const
0561 {
0562     return m_success;
0563 }
0564 
0565 void
0566 WorkerThread::run(ThreadWeaver::JobPointer self, ThreadWeaver::Thread *thread)
0567 {
0568     Q_UNUSED(self);
0569     Q_UNUSED(thread);
0570     m_success = m_reader->parseSongList( m_data, true );
0571 }
0572 
0573 void
0574 WorkerThread::defaultBegin(const ThreadWeaver::JobPointer& self, ThreadWeaver::Thread *thread)
0575 {
0576     Q_EMIT started(self);
0577     ThreadWeaver::Job::defaultBegin(self, thread);
0578 }
0579 
0580 void
0581 WorkerThread::defaultEnd(const ThreadWeaver::JobPointer& self, ThreadWeaver::Thread *thread)
0582 {
0583     ThreadWeaver::Job::defaultEnd(self, thread);
0584     if (!self->success()) {
0585         Q_EMIT failed(self);
0586     }
0587     Q_EMIT done(self);
0588 }