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

0001 /****************************************************************************************
0002  * Copyright (c) 2006 Ian Monroe <ian@monroe.nu>                                        *
0003  * Copyright (c) 2006 Seb Ruiz <ruiz@kde.org>                                           *
0004  * Copyright (c) 2007 Maximilian Kossick <maximilian.kossick@googlemail.com>            *
0005  *                                                                                      *
0006  * This program is free software; you can redistribute it and/or modify it under        *
0007  * the terms of the GNU General Public License as published by the Free Software        *
0008  * Foundation; either version 2 of the License, or (at your option) any later           *
0009  * version.                                                                             *
0010  *                                                                                      *
0011  * This program is distributed in the hope that it will be useful, but WITHOUT ANY      *
0012  * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A      *
0013  * PARTICULAR PURPOSE. See the GNU General Public License for more details.             *
0014  *                                                                                      *
0015  * You should have received a copy of the GNU General Public License along with         *
0016  * this program.  If not, see <http://www.gnu.org/licenses/>.                           *
0017  ****************************************************************************************/
0018 
0019 #define DEBUG_PREFIX "DaapCollection"
0020 
0021 #include "DaapCollection.h"
0022 
0023 #include "amarokconfig.h"
0024 #include "core/logger/Logger.h"
0025 #include "core/support/Components.h"
0026 #include "core/support/Debug.h"
0027 #include "DaapMeta.h"
0028 #include "MemoryQueryMaker.h"
0029 #include "daapreader/Reader.h"
0030 
0031 #include <QStringList>
0032 #include <QTimer>
0033 
0034 #include <KLocalizedString>
0035 
0036 #include <DNSSD/RemoteService>
0037 #include <DNSSD/ServiceBase>
0038 #include <DNSSD/ServiceBrowser>
0039 
0040 using namespace Collections;
0041 
0042 DaapCollectionFactory::DaapCollectionFactory()
0043     : Collections::CollectionFactory()
0044     , m_browser( nullptr )
0045 {
0046 }
0047 
0048 DaapCollectionFactory::~DaapCollectionFactory()
0049 {
0050     delete m_browser;
0051 }
0052 
0053 void
0054 DaapCollectionFactory::init()
0055 {
0056     DEBUG_BLOCK
0057     switch( KDNSSD::ServiceBrowser::isAvailable() )
0058     {
0059     case KDNSSD::ServiceBrowser::Working:
0060         //don't block Amarok's startup by connecting to DAAP servers
0061         QTimer::singleShot( 1000, this, &DaapCollectionFactory::connectToManualServers );
0062         m_browser = new KDNSSD::ServiceBrowser("_daap._tcp");
0063         m_browser->setObjectName("daapServiceBrowser");
0064         connect( m_browser, &KDNSSD::ServiceBrowser::serviceAdded,
0065                  this, &DaapCollectionFactory::foundDaap );
0066         connect( m_browser, &KDNSSD::ServiceBrowser::serviceRemoved,
0067                  this, &DaapCollectionFactory::serverOffline );
0068         m_browser->startBrowse();
0069         break;
0070 
0071     case KDNSSD::ServiceBrowser::Stopped:
0072         debug() << "The Zeroconf daemon is not running";
0073         break;
0074 
0075     case KDNSSD::ServiceBrowser::Unsupported:
0076         debug() << "Zeroconf support is not available";
0077         break;
0078 
0079     default:
0080         debug() << "Unknown error with Zeroconf";
0081     }
0082     m_initialized = true;
0083 }
0084 
0085 void
0086 DaapCollectionFactory::connectToManualServers()
0087 {
0088     DEBUG_BLOCK
0089     QStringList sl = AmarokConfig::manuallyAddedServers();
0090     foreach( const QString &server, sl )
0091     {
0092         debug() << "Adding server " << server;
0093         QStringList current = server.split( QLatin1Char(':'), Qt::KeepEmptyParts );
0094         //handle invalid urls gracefully
0095         if( current.count() < 2 )
0096             continue;
0097             
0098         QString host = current.first();
0099         quint16 port = current.last().toUShort();
0100         Amarok::Logger::longMessage(
0101                     i18n( "Loading remote collection from host %1", host),
0102                     Amarok::Logger::Information );
0103 
0104         int lookup_id = QHostInfo::lookupHost( host, this, &DaapCollectionFactory::resolvedManualServerIp );
0105         m_lookupHash.insert( lookup_id, port );
0106     }
0107 }
0108 
0109 void
0110 DaapCollectionFactory::serverOffline( KDNSSD::RemoteService::Ptr service )
0111 {
0112     DEBUG_BLOCK
0113     QString key =  serverKey( service->hostName(), service->port() );
0114     if( m_collectionMap.contains( key ) )
0115     {
0116         auto coll = m_collectionMap[ key ];
0117         if( coll )
0118             coll->serverOffline();  //collection will be deleted by collectionmanager
0119         else
0120             warning() << "collection already null";
0121         
0122         m_collectionMap.remove( key );
0123 
0124     }
0125     else
0126         warning() << "removing non-existent service";
0127 }
0128 
0129 void
0130 DaapCollectionFactory::foundDaap( KDNSSD::RemoteService::Ptr service )
0131 {
0132     DEBUG_BLOCK
0133 
0134     connect( service.data(), &KDNSSD::RemoteService::resolved, this, &DaapCollectionFactory::resolvedDaap );
0135     service->resolveAsync();
0136 }
0137 
0138 void
0139 DaapCollectionFactory::resolvedDaap( bool success )
0140 {
0141     const KDNSSD::RemoteService* service =  dynamic_cast<const KDNSSD::RemoteService*>(sender());
0142     if( !success || !service ) return;
0143     debug() << service->serviceName() << ' ' << service->hostName() << ' ' << service->domain() << ' ' << service->type();
0144 
0145     int lookup_id = QHostInfo::lookupHost( service->hostName(), this, &DaapCollectionFactory::resolvedServiceIp );
0146     m_lookupHash.insert( lookup_id, service->port() );
0147 }
0148 
0149 QString
0150 DaapCollectionFactory::serverKey( const QString& host, quint16 port) const
0151 {
0152     return host + QLatin1Char(':') + QString::number( port );
0153 }
0154 
0155 void
0156 DaapCollectionFactory::slotCollectionReady()
0157 {
0158     DEBUG_BLOCK
0159     DaapCollection *collection = dynamic_cast<DaapCollection*>( sender() );
0160     if( collection )
0161     {
0162         disconnect( collection, &DaapCollection::remove, this, &DaapCollectionFactory::slotCollectionDownloadFailed );
0163         Q_EMIT newCollection( collection );
0164     }
0165 }
0166 
0167 void
0168 DaapCollectionFactory::slotCollectionDownloadFailed()
0169 {
0170     DEBUG_BLOCK
0171     DaapCollection *collection = qobject_cast<DaapCollection*>( sender() );
0172     if( !collection )
0173         return;
0174     disconnect( collection, &DaapCollection::collectionReady, this, &DaapCollectionFactory::slotCollectionReady );
0175     for( const auto &it : m_collectionMap )
0176     {
0177         if( it.data() == collection )
0178         {
0179             m_collectionMap.remove( m_collectionMap.key( it ) );
0180             break;
0181         }
0182     }
0183     collection->deleteLater();
0184 }
0185 
0186 void
0187 DaapCollectionFactory::resolvedManualServerIp( const QHostInfo &hostInfo )
0188 {
0189     if ( !m_lookupHash.contains(hostInfo.lookupId()) )
0190         return;
0191 
0192     if ( hostInfo.addresses().isEmpty() )
0193         return;
0194 
0195     QString host = hostInfo.hostName();
0196     QString ip = hostInfo.addresses().at(0).toString();
0197     quint16 port = m_lookupHash.value( hostInfo.lookupId() );
0198 
0199     //adding manual servers to the collectionMap doesn't make sense
0200     DaapCollection *coll = new DaapCollection( host, ip, port );
0201     connect( coll, &DaapCollection::collectionReady, this, &DaapCollectionFactory::slotCollectionReady );
0202     connect( coll, &DaapCollection::remove, this, &DaapCollectionFactory::slotCollectionDownloadFailed );
0203 }
0204 
0205 void
0206 DaapCollectionFactory::resolvedServiceIp( const QHostInfo &hostInfo )
0207 {
0208     DEBUG_BLOCK
0209    // debug() << "got address:" << hostInfo.addresses() << "and lookup hash contains id" << hostInfo.lookupId() << "?" << m_lookupHash.contains(hostInfo.lookupId());
0210     if ( !m_lookupHash.contains(hostInfo.lookupId()) )
0211         return;
0212 
0213     if ( hostInfo.addresses().isEmpty() )
0214         return;
0215 
0216     QString host = hostInfo.hostName();
0217     QString ip = hostInfo.addresses().at(0).toString();
0218     quint16 port = m_lookupHash.value( hostInfo.lookupId() );
0219 
0220    // debug() << "already added server?" << m_collectionMap.contains(serverKey( host, port ));
0221     if( m_collectionMap.contains(serverKey( host, port )) ) //same server from multiple interfaces
0222         return;
0223 
0224    // debug() << "creating daap collection with" << host << ip << port;
0225     QPointer<DaapCollection> coll( new DaapCollection( host, ip, port ) );
0226     connect( coll, &DaapCollection::collectionReady, this, &DaapCollectionFactory::slotCollectionReady );
0227     connect( coll, &DaapCollection::remove, this, &DaapCollectionFactory::slotCollectionDownloadFailed );
0228     m_collectionMap.insert( serverKey( host, port ), coll.data() );
0229 }
0230 
0231 //DaapCollection
0232 
0233 DaapCollection::DaapCollection( const QString &host, const QString &ip, quint16 port )
0234     : Collection()
0235     , m_host( host )
0236     , m_port( port )
0237     , m_ip( ip )
0238     , m_reader( nullptr )
0239     , m_mc( new MemoryCollection() )
0240 {
0241     debug() << "Host: " << host << " port: " << port;
0242     m_reader = new Daap::Reader( this, host, port, QString(), this, "DaapReader" );
0243     connect( m_reader, &Daap::Reader::passwordRequired,this, &DaapCollection::passwordRequired );
0244     connect( m_reader, &Daap::Reader::httpError, this, &DaapCollection::httpError );
0245     m_reader->loginRequest();
0246 }
0247 
0248 DaapCollection::~DaapCollection()
0249 {
0250 }
0251 
0252 QueryMaker*
0253 DaapCollection::queryMaker()
0254 {
0255     return new MemoryQueryMaker( m_mc.toWeakRef(), collectionId() );
0256 }
0257 
0258 QString
0259 DaapCollection::collectionId() const
0260 {
0261     return QString( QStringLiteral("daap://") + m_ip + QLatin1Char(':') ) + QString::number( m_port );
0262 }
0263 
0264 QString
0265 DaapCollection::prettyName() const
0266 {
0267     QString host = m_host;
0268     // No need to be overly verbose
0269     if( host.endsWith( ".local" ) )
0270         host = host.remove( QRegExp(".local$") );
0271     return i18n("Music share at %1", host);
0272 }
0273 
0274 void
0275 DaapCollection::passwordRequired()
0276 {
0277     //get password
0278     QString password;
0279     delete m_reader;
0280     m_reader = new Daap::Reader( this, m_host, m_port, password, this, "DaapReader" );
0281     connect( m_reader, &Daap::Reader::passwordRequired, this, &DaapCollection::passwordRequired );
0282     connect( m_reader, &Daap::Reader::httpError, this, &DaapCollection::httpError );
0283     m_reader->loginRequest();
0284 }
0285 
0286 void
0287 DaapCollection::httpError( const QString &error )
0288 {
0289     DEBUG_BLOCK
0290     debug() << "Http error in DaapReader: " << error;
0291     Q_EMIT remove();
0292 }
0293 
0294 void
0295 DaapCollection::serverOffline()
0296 {
0297     Q_EMIT remove();
0298 }
0299 
0300 void
0301 DaapCollection::loadedDataFromServer()
0302 {
0303     DEBUG_BLOCK
0304     Q_EMIT collectionReady();
0305 }
0306 
0307 void
0308 DaapCollection::parsingFailed()
0309 {
0310     DEBUG_BLOCK
0311     Q_EMIT remove();
0312 }