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 }