diff --git a/CMakeLists.txt b/CMakeLists.txt index 5c8aa8a99..635f6716f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -23,7 +23,7 @@ ENDIF() SET( TOMAHAWK_VERSION_MAJOR 0 ) SET( TOMAHAWK_VERSION_MINOR 7 ) -SET( TOMAHAWK_VERSION_PATCH 99 ) +SET( TOMAHAWK_VERSION_PATCH 999 ) #SET( TOMAHAWK_VERSION_RC 0 ) SET( TOMAHAWK_TRANSLATION_LANGUAGES ar bg bn_IN ca cs de en el es fi fr hi_IN hu gl it ja lt pl pt_BR ru sv tr zh_CN zh_TW ) diff --git a/src/accounts/xmpp/sip/TomahawkXmppMessage.cpp b/src/accounts/xmpp/sip/TomahawkXmppMessage.cpp index ef2951812..df15f9b77 100644 --- a/src/accounts/xmpp/sip/TomahawkXmppMessage.cpp +++ b/src/accounts/xmpp/sip/TomahawkXmppMessage.cpp @@ -2,6 +2,7 @@ * * Copyright 2010-2011, Christian Muehlhaeuser * Copyright 2010-2011, Jeff Mitchell + * Copyright 2013, Uwe L. Korn * * Tomahawk is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -21,60 +22,41 @@ #include "utils/Logger.h" - -class TomahawkXmppMessagePrivate +TomahawkXmppMessage::TomahawkXmppMessage() + : m_sipInfos() { -public: - QString ip; - int port; - QString uniqname; - QString key; - bool visible; -}; - -TomahawkXmppMessage::TomahawkXmppMessage(const QString &ip, unsigned int port, const QString &uniqname, const QString &key) : d_ptr(new TomahawkXmppMessagePrivate) -{ - Q_D(TomahawkXmppMessage); - d->ip = ip; - d->port = port; - d->uniqname = uniqname; - d->key = key; - d->visible = true; } -TomahawkXmppMessage::TomahawkXmppMessage() : d_ptr(new TomahawkXmppMessagePrivate) +TomahawkXmppMessage::TomahawkXmppMessage( const QList &sipInfos ) + : m_sipInfos( sipInfos ) { - Q_D(TomahawkXmppMessage); - d->visible = false; - d->port = -1; } - TomahawkXmppMessage::~TomahawkXmppMessage() { } -const QString TomahawkXmppMessage::ip() const +const QList +TomahawkXmppMessage::sipInfos() const { - return d_func()->ip; + return m_sipInfos; } -unsigned int TomahawkXmppMessage::port() const + +const QString +TomahawkXmppMessage::key() const { - return d_func()->port; + if ( m_sipInfos.isEmpty() ) + return QString(); + else + return m_sipInfos.first().key(); } -const QString TomahawkXmppMessage::uniqname() const +const QString +TomahawkXmppMessage::uniqname() const { - return d_func()->uniqname; -} - -const QString TomahawkXmppMessage::key() const -{ - return d_func()->key; -} - -bool TomahawkXmppMessage::visible() const -{ - return d_func()->visible; + if ( m_sipInfos.isEmpty() ) + return QString(); + else + return m_sipInfos.first().nodeId(); } diff --git a/src/accounts/xmpp/sip/TomahawkXmppMessage.h b/src/accounts/xmpp/sip/TomahawkXmppMessage.h index d26ac30ae..63d5bc09f 100644 --- a/src/accounts/xmpp/sip/TomahawkXmppMessage.h +++ b/src/accounts/xmpp/sip/TomahawkXmppMessage.h @@ -2,6 +2,7 @@ * * Copyright 2010-2011, Christian Muehlhaeuser * Copyright 2010-2011, Jeff Mitchell + * Copyright 2013, Uwe L. Korn * * Tomahawk is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -22,30 +23,37 @@ #include +#include "sip/SipInfo.h" + #define TOMAHAWK_SIP_MESSAGE_NS QLatin1String("http://www.tomhawk-player.org/sip/transports") #include "accounts/AccountDllMacro.h" -class TomahawkXmppMessagePrivate; class ACCOUNTDLLEXPORT TomahawkXmppMessage : public Jreen::Payload { J_PAYLOAD(TomahawkXmppMessage) - Q_DECLARE_PRIVATE(TomahawkXmppMessage) public: - // sets visible to true - TomahawkXmppMessage(const QString &ip, unsigned int port, const QString &uniqname, const QString &key); - - // sets visible to false as we dont have any extra information TomahawkXmppMessage(); + TomahawkXmppMessage(const QList& sipInfos); ~TomahawkXmppMessage(); - const QString ip() const; - unsigned int port() const; - const QString uniqname() const; + /** + * The SipInfo objects that are wrapped in this XmppMessage + */ + const QList sipInfos() const; + + /** + * The name of the peer contained in this message + */ const QString key() const; - bool visible() const; + + /** + * The name of the peer contained in this message + */ + const QString uniqname() const; + private: - QScopedPointer d_ptr; + QList m_sipInfos; }; #endif // ENTITYTIME_H diff --git a/src/accounts/xmpp/sip/TomahawkXmppMessageFactory.cpp b/src/accounts/xmpp/sip/TomahawkXmppMessageFactory.cpp index 5c6a3f81f..f5c8f925e 100644 --- a/src/accounts/xmpp/sip/TomahawkXmppMessageFactory.cpp +++ b/src/accounts/xmpp/sip/TomahawkXmppMessageFactory.cpp @@ -2,6 +2,7 @@ * * Copyright 2010-2011, Christian Muehlhaeuser * Copyright 2010-2011, Jeff Mitchell + * Copyright 2013, Uwe L. Korn * * Tomahawk is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -19,6 +20,7 @@ #include "TomahawkXmppMessageFactory.h" +#include "network/Servent.h" #include "utils/Logger.h" #include @@ -29,6 +31,7 @@ using namespace Jreen; TomahawkXmppMessageFactory::TomahawkXmppMessageFactory() + : m_sipInfos() { m_depth = 0; m_state = AtNowhere; @@ -54,31 +57,35 @@ void TomahawkXmppMessageFactory::handleStartElement(const QStringRef &name, cons const QXmlStreamAttributes &attributes) { m_depth++; - if (m_depth == 1) { + if ( m_depth == 1 ) + { m_state = AtNowhere; - m_ip = QString(); - m_port = -1; m_uniqname = QString(); m_key = QString(); - m_visible = false; - } else if (m_depth == 2) { - if (name == QLatin1String("transport")) + m_sipInfos = QList(); + } + else if ( m_depth == 2 ) + { + if ( name == QLatin1String( "transport" ) ) { -// qDebug() << "Found Transport"; m_state = AtTransport; - - m_uniqname = attributes.value(QLatin1String("uniqname")).toString(); - m_key = attributes.value(QLatin1String("pwd")).toString(); + m_uniqname = attributes.value( QLatin1String( "uniqname" ) ).toString(); + m_key = attributes.value( QLatin1String( "pwd" ) ).toString(); } - } else if(m_depth == 3) { - if (name == QLatin1String("candidate")) + } + else if(m_depth == 3) + { + if ( name == QLatin1String( "candidate" ) ) { m_state = AtCandidate; -// qDebug() << "Found candidate"; - m_ip = attributes.value(QLatin1String("ip")).toString(); - m_port = attributes.value(QLatin1String("port")).toString().toInt(); - - m_visible = true; + SipInfo info = SipInfo(); + info.setVisible( true ); + info.setHost( attributes.value( QLatin1String( "ip" ) ).toString() ); + info.setPort( attributes.value( QLatin1String( "port" ) ).toString().toInt() ); + info.setKey( m_key ); + info.setNodeId( m_uniqname ); + Q_ASSERT( info.isValid() ); + m_sipInfos.append( info ); } } Q_UNUSED(uri); @@ -87,8 +94,22 @@ void TomahawkXmppMessageFactory::handleStartElement(const QStringRef &name, cons void TomahawkXmppMessageFactory::handleEndElement(const QStringRef &name, const QStringRef &uri) { - if (m_depth == 3) + if ( m_depth == 3 ) + m_state = AtTransport; + else if ( m_depth == 2 ) + { m_state = AtNowhere; + // Check that we have at least one SipInfo so that we provide some information about invisible peers. + if ( m_sipInfos.isEmpty() ) + { + SipInfo info = SipInfo(); + info.setVisible( false ); + info.setKey( m_key ); + info.setNodeId( m_uniqname ); + Q_ASSERT( info.isValid() ); + m_sipInfos.append( info ); + } + } Q_UNUSED(name); Q_UNUSED(uri); m_depth--; @@ -111,38 +132,69 @@ void TomahawkXmppMessageFactory::serialize(Payload *extension, QXmlStreamWriter { TomahawkXmppMessage *sipMessage = se_cast(extension); - writer->writeStartElement(QLatin1String("tomahawk")); - writer->writeDefaultNamespace(TOMAHAWK_SIP_MESSAGE_NS); + writer->writeStartElement( QLatin1String( "tomahawk" ) ); + writer->writeDefaultNamespace( TOMAHAWK_SIP_MESSAGE_NS ); - if(sipMessage->visible()) + // Get a copy of the list, so that we can modify it here. + QList sipInfos = QList( sipMessage->sipInfos() ); + SipInfo lastInfo; + foreach ( SipInfo info, sipInfos ) + { + if ( info.isVisible() ) { - // add transport tag - writer->writeStartElement(QLatin1String("transport")); - writer->writeAttribute(QLatin1String("pwd"), sipMessage->key()); - writer->writeAttribute(QLatin1String("uniqname"), sipMessage->uniqname()); + QHostAddress ha = QHostAddress( info.host() ); + if ( ( Servent::isValidExternalIP( ha ) && ha.protocol() == QAbstractSocket::IPv4Protocol ) || ( ha.protocol() == QAbstractSocket::UnknownNetworkLayerProtocol ) || ( ha.isNull() && !info.host().isEmpty() ) ) + { + // For comapability reasons, this shall be put as the last candidate (this is the IP/host that would have been sent in previous versions) + lastInfo = info; + sipInfos.removeOne( info ); + break; + } + } + } - writer->writeEmptyElement(QLatin1String("candidate")); - writer->writeAttribute(QLatin1String("component"), "1"); - writer->writeAttribute(QLatin1String("id"), "el0747fg11"); // FIXME - writer->writeAttribute(QLatin1String("ip"), sipMessage->ip()); - writer->writeAttribute(QLatin1String("network"), "1"); - writer->writeAttribute(QLatin1String("port"), QVariant(sipMessage->port()).toString()); - writer->writeAttribute(QLatin1String("priority"), "1"); //TODO - writer->writeAttribute(QLatin1String("protocol"), "tcp"); - writer->writeAttribute(QLatin1String("type"), "host"); //FIXME: correct?! - writer->writeEndElement(); - } - else - { - writer->writeEmptyElement(QLatin1String("transport")); - } - writer->writeEndElement(); + writer->writeStartElement( QLatin1String( "transport" ) ); + writer->writeAttribute( QLatin1String( "pwd" ), sipMessage->key() ); + writer->writeAttribute( QLatin1String( "uniqname" ), sipMessage->uniqname() ); + + foreach ( SipInfo info, sipInfos ) + { + if ( info.isVisible() ) + serializeSipInfo( info, writer ); + } + + if ( lastInfo.isValid() ) + { + Q_ASSERT( lastInfo.isVisible() ); + tLog( LOGVERBOSE ) << Q_FUNC_INFO << "Using " << lastInfo.host() << ":" << lastInfo.port() << " as the host which all older clients will only detect"; + serializeSipInfo( lastInfo, writer ); + } + + // + writer->writeEndElement(); + // + writer->writeEndElement(); } -Payload::Ptr TomahawkXmppMessageFactory::createPayload() +Payload::Ptr +TomahawkXmppMessageFactory::createPayload() { - if(m_visible) - return Payload::Ptr(new TomahawkXmppMessage(m_ip, m_port, m_uniqname, m_key)); - else - return Payload::Ptr(new TomahawkXmppMessage()); + return Payload::Ptr( new TomahawkXmppMessage( m_sipInfos ) ); +} + +void +TomahawkXmppMessageFactory::serializeSipInfo(SipInfo &info, QXmlStreamWriter *writer) +{ + if ( info.isVisible() ) + { + writer->writeEmptyElement( QLatin1String( "candidate" ) ); + writer->writeAttribute( QLatin1String( "component" ), "1" ); + writer->writeAttribute( QLatin1String( "id" ), "el0747fg11" ); // FIXME + writer->writeAttribute( QLatin1String( "ip" ), info.host() ); + writer->writeAttribute( QLatin1String( "network" ), "1" ); + writer->writeAttribute( QLatin1String( "port" ), QVariant( info.port() ).toString() ); + writer->writeAttribute( QLatin1String( "priority" ), "1" ); //TODO + writer->writeAttribute( QLatin1String( "protocol" ), "tcp" ); + writer->writeAttribute( QLatin1String( "type" ), "host" ); //FIXME: correct?! + } } diff --git a/src/accounts/xmpp/sip/TomahawkXmppMessageFactory.h b/src/accounts/xmpp/sip/TomahawkXmppMessageFactory.h index f1910cb40..f2d2a9de3 100644 --- a/src/accounts/xmpp/sip/TomahawkXmppMessageFactory.h +++ b/src/accounts/xmpp/sip/TomahawkXmppMessageFactory.h @@ -2,6 +2,7 @@ * * Copyright 2010-2011, Christian Muehlhaeuser * Copyright 2010-2011, Jeff Mitchell + * Copyright 2013, Uwe L. Korn * * Tomahawk is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -39,13 +40,29 @@ public: void serialize(Jreen::Payload *extension, QXmlStreamWriter *writer); Jreen::Payload::Ptr createPayload(); private: + void serializeSipInfo(SipInfo& info, QXmlStreamWriter *writer); + enum State { AtNowhere, AtTransport, AtCandidate } m_state; + + /** + * All the provided Sip informations + */ + QList m_sipInfos; + + /** + * The current parsing depth + */ int m_depth; - QString m_ip; - int m_port; + + /** + * The unique name of the peer + */ QString m_uniqname; + + /** + * The authentication key of the peer + */ QString m_key; - bool m_visible; }; #endif // ENTITYTIMEFACTORY_P_H diff --git a/src/accounts/xmpp/sip/XmppSip.cpp b/src/accounts/xmpp/sip/XmppSip.cpp index cd878d392..530f65ed6 100644 --- a/src/accounts/xmpp/sip/XmppSip.cpp +++ b/src/accounts/xmpp/sip/XmppSip.cpp @@ -4,6 +4,7 @@ * Copyright 2010-2011, Christian Muehlhaeuser * Copyright 2011, Leo Franchi * Copyright 2010-2012, Jeff Mitchell + * Copyright 2013, Uwe L. Korn * * Tomahawk is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -435,22 +436,15 @@ XmppSipPlugin::errorMessage( Jreen::Client::DisconnectReason reason ) void -XmppSipPlugin::sendSipInfo( const Tomahawk::peerinfo_ptr& receiver, const SipInfo& info ) +XmppSipPlugin::sendSipInfos( const Tomahawk::peerinfo_ptr& receiver, const QList& info ) { tDebug( LOGVERBOSE ) << Q_FUNC_INFO << receiver << info; if ( !m_client ) return; - TomahawkXmppMessage *sipMessage; - if ( info.isVisible() ) - { - sipMessage = new TomahawkXmppMessage( info.host(), info.port(), info.nodeId(), info.key() ); - } - else - sipMessage = new TomahawkXmppMessage(); - - qDebug() << "Send sip messsage to" << receiver; + TomahawkXmppMessage* sipMessage = new TomahawkXmppMessage( info ); + tDebug( LOGVERBOSE ) << Q_FUNC_INFO << "Send sip messsage to" << receiver; Jreen::IQ iq( Jreen::IQ::Set, receiver->id() ); iq.addExtension( sipMessage ); Jreen::IQReply *reply = m_client->send( iq ); @@ -687,6 +681,7 @@ XmppSipPlugin::onNewMessage( const Jreen::Message& message ) return; } + // FIXME: We do not sent SipInfo in JSON via XMPP, why do we receive it here? SipInfo info = SipInfo::fromJson( msg ); if ( !info.isValid() ) { @@ -885,12 +880,22 @@ XmppSipPlugin::onNewIq( const Jreen::IQ& iq ) Jreen::SoftwareVersion::Ptr softwareVersion = iq.payload(); if ( softwareVersion ) { + QMutexLocker locker( &peerQueueMutex ); QString versionString = QString( "%1 %2 %3" ).arg( softwareVersion->name(), softwareVersion->os(), softwareVersion->version() ); qDebug() << Q_FUNC_INFO << "Received software version for" << iq.from().full() << ":" << versionString; Tomahawk::peerinfo_ptr peerInfo = PeerInfo::get( this, iq.from().full() ); if ( !peerInfo.isNull() ) { peerInfo->setVersionString( versionString ); + if ( sipinfosQueue.contains( iq.from().full() ) ) + { + peerInfo->setSipInfos( sipinfosQueue.value( iq.from().full() ) ); + sipinfosQueue.remove( iq.from().full() ); + } + if ( peersWaitingForVersionString.contains( iq.from().full() ) ) + { + peersWaitingForVersionString.remove( iq.from().full() ); + } } } } @@ -911,31 +916,37 @@ XmppSipPlugin::onNewIq( const Jreen::IQ& iq ) TomahawkXmppMessage::Ptr sipMessage = iq.payload< TomahawkXmppMessage >(); if ( sipMessage ) { + QMutexLocker locker( &peerQueueMutex ); iq.accept(); + tLog( LOGVERBOSE ) << Q_FUNC_INFO << "Received Sip Information from:" << iq.from().full(); - qDebug() << Q_FUNC_INFO << "Got SipMessage ..." - << "ip" << sipMessage->ip() << "port" << sipMessage->port() << "nodeId" << sipMessage->uniqname() << "key" << sipMessage->key() << "visible" << sipMessage->visible(); - - SipInfo info; - info.setVisible( sipMessage->visible() ); - if ( sipMessage->visible() ) + // Check that all received SipInfos are valid. + foreach ( SipInfo info, sipMessage->sipInfos() ) { - info.setHost( sipMessage->ip() ); - info.setPort( sipMessage->port() ); - info.setNodeId( sipMessage->uniqname() ); - info.setKey( sipMessage->key() ); + Q_ASSERT( info.isValid() ); } - Q_ASSERT( info.isValid() ); - - qDebug() << Q_FUNC_INFO << "From:" << iq.from().full() << ":" << info; + // Get the peer information for the sender. Tomahawk::peerinfo_ptr peerInfo = PeerInfo::get( this, iq.from().full() ); if ( peerInfo.isNull() ) { tDebug() << Q_FUNC_INFO << "no valid peerInfo for" << iq.from().full(); return; } - peerInfo->setSipInfo( info ); + if ( peerInfo->versionString().isEmpty() ) + { + // If we do not have a version string, this peerInfo is still queued. So we queue its SipInfo until we have a valid version string. + sipinfosQueue[iq.from().full()] = sipMessage->sipInfos(); + } + else + { + peerInfo->setSipInfos( sipMessage->sipInfos() ); + } + // If we stored a reference for this peer in our sip-waiting-queue, remove it. + if ( peersWaitingForSip.contains( iq.from().full() ) ) + { + peersWaitingForSip.remove( iq.from().full() ); + } } } } @@ -977,7 +988,21 @@ XmppSipPlugin::handlePeerStatus( const Jreen::JID& jid, Jreen::Presence::Type pr Tomahawk::peerinfo_ptr peerInfo = PeerInfo::get( this, fulljid ); if ( !peerInfo.isNull() ) { + QMutexLocker locker( &peerQueueMutex ); peerInfo->setStatus( PeerInfo::Offline ); + // If we stored a reference for this peer in our sip-waiting-queue, remove it. + if ( peersWaitingForSip.contains( fulljid ) ) + { + peersWaitingForSip.remove( fulljid ); + } + if ( peersWaitingForVersionString.contains( fulljid ) ) + { + peersWaitingForVersionString.remove( fulljid ); + } + if ( sipinfosQueue.contains( fulljid ) ) + { + sipinfosQueue.remove( fulljid ); + } } return; @@ -989,12 +1014,15 @@ XmppSipPlugin::handlePeerStatus( const Jreen::JID& jid, Jreen::Presence::Type pr { qDebug() << Q_FUNC_INFO << "* Peer goes online:" << fulljid; + QMutexLocker locker( &peerQueueMutex ); m_peers[ jid ] = presenceType; Tomahawk::peerinfo_ptr peerInfo = PeerInfo::get( this, fulljid, PeerInfo::AutoCreate ); peerInfo->setContactId( jid.bare() ); peerInfo->setStatus( PeerInfo::Online ); peerInfo->setFriendlyName( m_jidsNames.value( jid.bare() ) ); + peersWaitingForSip[fulljid] = peerInfo; + peersWaitingForVersionString[fulljid] = peerInfo; #ifndef ENABLE_HEADLESS if ( !m_avatarManager->avatar( jid.bare() ).isNull() ) diff --git a/src/accounts/xmpp/sip/XmppSip.h b/src/accounts/xmpp/sip/XmppSip.h index fabcbf438..0a70b734f 100644 --- a/src/accounts/xmpp/sip/XmppSip.h +++ b/src/accounts/xmpp/sip/XmppSip.h @@ -4,6 +4,7 @@ * Copyright 2010-2011, Christian Muehlhaeuser * Copyright 2011, Leo Franchi * Copyright 2010-2011, Jeff Mitchell + * Copyright 2013, Uwe L. Korn * * Tomahawk is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -90,7 +91,7 @@ public slots: virtual void configurationChanged(); virtual void addContact( const QString& peerId, const QString& msg = QString() ); - virtual void sendSipInfo( const Tomahawk::peerinfo_ptr& receiver, const SipInfo& info ); + virtual void sendSipInfos( const Tomahawk::peerinfo_ptr& receiver, const QList& info ); void showAddFriendDialog(); void publishTune( const QUrl& url, const Tomahawk::InfoSystem::InfoStringHash& trackInfo ); @@ -153,6 +154,10 @@ private: enum IqContext { NoContext, RequestDisco, RequestedDisco, SipMessageSent, RequestedVCard, RequestVersion, RequestedVersion }; AvatarManager* m_avatarManager; Jreen::PubSub::Manager* m_pubSubManager; + QMap< QString, Tomahawk::peerinfo_ptr > peersWaitingForSip; + QMap< QString, Tomahawk::peerinfo_ptr > peersWaitingForVersionString; + QMap< QString, QList< SipInfo > > sipinfosQueue; + QMutex peerQueueMutex; }; #endif diff --git a/src/accounts/zeroconf/TomahawkZeroconf.h b/src/accounts/zeroconf/TomahawkZeroconf.h index c1d362fb4..87d45845a 100644 --- a/src/accounts/zeroconf/TomahawkZeroconf.h +++ b/src/accounts/zeroconf/TomahawkZeroconf.h @@ -1,6 +1,7 @@ /* === This file is part of Tomahawk Player - === * * Copyright 2010-2011, Christian Muehlhaeuser + * Copyright 2013, Uwe L. Korn * * Tomahawk is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -29,6 +30,7 @@ #include #include #include +#include #include #include #include @@ -131,6 +133,10 @@ private slots: m_sock.readDatagram( datagram.data(), datagram.size(), &sender, &senderPort ); qDebug() << "DATAGRAM RCVD" << QString::fromLatin1( datagram ) << sender; + // Ignore our own requests + if ( QNetworkInterface::allAddresses().contains( sender ) ) + return; + // only process msgs originating on the LAN: if ( datagram.startsWith( "TOMAHAWKADVERT:" ) && Servent::isIPWhitelisted( sender ) ) diff --git a/src/accounts/zeroconf/Zeroconf.cpp b/src/accounts/zeroconf/Zeroconf.cpp index 7a73d8d1c..b1924f8bc 100644 --- a/src/accounts/zeroconf/Zeroconf.cpp +++ b/src/accounts/zeroconf/Zeroconf.cpp @@ -161,7 +161,9 @@ ZeroconfPlugin::lanHostFound( const QString& host, int port, const QString& name sipInfo.setVisible( true ); Tomahawk::peerinfo_ptr peerInfo = Tomahawk::PeerInfo::get( this, host, Tomahawk::PeerInfo::AutoCreate ); - peerInfo->setSipInfo( sipInfo ); + QList sipInfos = QList(); + sipInfos.append( sipInfo ); + peerInfo->setSipInfos( sipInfos ); peerInfo->setContactId( host ); peerInfo->setFriendlyName( name ); peerInfo->setType( PeerInfo::Local ); diff --git a/src/accounts/zeroconf/Zeroconf.h b/src/accounts/zeroconf/Zeroconf.h index d6803f8f4..2de0e9b44 100644 --- a/src/accounts/zeroconf/Zeroconf.h +++ b/src/accounts/zeroconf/Zeroconf.h @@ -65,7 +65,7 @@ public slots: void advertise(); - void sendSipInfo( const Tomahawk::peerinfo_ptr&, const SipInfo& ) {} + virtual void sendSipInfos( const Tomahawk::peerinfo_ptr& receiver, const QList& info ) {} void broadcastMsg( const QString & ) {} void addContact( const QString &, const QString& ) {} diff --git a/src/libtomahawk/CMakeLists.txt b/src/libtomahawk/CMakeLists.txt index d7f7daef7..22f985fc4 100644 --- a/src/libtomahawk/CMakeLists.txt +++ b/src/libtomahawk/CMakeLists.txt @@ -306,6 +306,8 @@ list(APPEND libSources network/Servent.cpp network/Connection.cpp network/ControlConnection.cpp + network/QTcpSocketExtra.cpp + network/ConnectionManager.cpp playlist/PlaylistUpdaterInterface.cpp playlist/dynamic/DynamicPlaylist.cpp diff --git a/src/libtomahawk/Source.cpp b/src/libtomahawk/Source.cpp index 509068e87..ab397a769 100644 --- a/src/libtomahawk/Source.cpp +++ b/src/libtomahawk/Source.cpp @@ -30,6 +30,7 @@ #include "database/DatabaseCommand_LoadAllSources.h" #include "database/DatabaseCommand_SourceOffline.h" #include "database/DatabaseCommand_UpdateSearchIndex.h" +#include "database/DatabaseImpl.h" #include "database/Database.h" #include @@ -81,10 +82,30 @@ Source::~Source() } -void +bool Source::setControlConnection( ControlConnection* cc ) { - m_cc = cc; + QMutexLocker locker( &m_setControlConnectionMutex ); + if ( !m_cc.isNull() && m_cc->isReady() && m_cc->isRunning() ) + { + const QString& nodeid = Database::instance()->impl()->dbid(); + if ( cc->id() < nodeid && m_cc->outbound() ) + { + m_cc = cc; + // This ControlConnection is not needed anymore, get rid of it! + m_cc->deleteLater(); + return true; + } + else + { + return false; + } + } + else + { + m_cc = cc; + return true; + } } @@ -456,6 +477,12 @@ Source::playlistInterface() return m_playlistInterface; } +QSharedPointer +Source::acquireLock() +{ + return QSharedPointer( new QMutexLocker( &m_mutex ) ); +} + void Source::onPlaybackStarted( const Tomahawk::track_ptr& track, unsigned int duration ) diff --git a/src/libtomahawk/Source.h b/src/libtomahawk/Source.h index 842665cf2..aaa6faf2f 100644 --- a/src/libtomahawk/Source.h +++ b/src/libtomahawk/Source.h @@ -2,6 +2,7 @@ * * Copyright 2010-2011, Christian Muehlhaeuser * Copyright 2010-2012, Jeff Mitchell + * Copyright 2013, Uwe L. Korn * * Tomahawk is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -25,6 +26,7 @@ #include #include "Typedefs.h" +#include "network/ControlConnection.h" #include "network/DbSyncConnection.h" #include "collection/Collection.h" #include "Query.h" @@ -32,7 +34,6 @@ #include "DllMacro.h" -class ControlConnection; class DatabaseCommand_DeleteFiles; class DatabaseCommand_LoadAllSources; class DatabaseCommand_LogPlayback; @@ -85,8 +86,8 @@ public: void removeCollection( const Tomahawk::collection_ptr& c ); int id() const { return m_id; } - ControlConnection* controlConnection() const { return m_cc; } - void setControlConnection( ControlConnection* cc ); + ControlConnection* controlConnection() const { return m_cc.data(); } + bool setControlConnection( ControlConnection* cc ); const QSet< Tomahawk::peerinfo_ptr > peerInfos() const; @@ -101,6 +102,8 @@ public: Tomahawk::playlistinterface_ptr playlistInterface(); + QSharedPointer acquireLock(); + signals: void syncedWithDatabase(); void synced(); @@ -168,11 +171,13 @@ private: DBSyncConnection::State m_state; QTimer m_currentTrackTimer; - ControlConnection* m_cc; + QPointer m_cc; QList< QSharedPointer > m_cmds; int m_commandCount; QString m_lastCmdGuid; mutable QMutex m_cmdMutex; + QMutex m_setControlConnectionMutex; + QMutex m_mutex; Tomahawk::playlistinterface_ptr m_playlistInterface; }; diff --git a/src/libtomahawk/network/Connection.cpp b/src/libtomahawk/network/Connection.cpp index 511bcaa3c..8b1244608 100644 --- a/src/libtomahawk/network/Connection.cpp +++ b/src/libtomahawk/network/Connection.cpp @@ -2,6 +2,7 @@ * * Copyright 2010-2011, Christian Muehlhaeuser * Copyright 2010-2011, Jeff Mitchell + * Copyright 2013, Uwe L. Korn * * Tomahawk is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -216,14 +217,19 @@ void Connection::checkACLResult( const QString &nodeid, const QString &username, ACLRegistry::ACL peerStatus ) { QString bareName = name().contains( '/' ) ? name().left( name().indexOf( "/" ) ) : name(); - if ( nodeid != property( "nodeid" ).toString() || username != bareName ) + if ( nodeid != property( "nodeid" ).toString() ) { - tDebug( LOGVERBOSE ) << Q_FUNC_INFO << "nodeid not ours, or username not our barename"; + tDebug( LOGVERBOSE ) << Q_FUNC_INFO << QString( "nodeid (%1) not ours (%2) for user %3" ).arg( nodeid ).arg( property( "nodeid" ).toString() ).arg( username ); + return; + } + if ( username != bareName ) + { + tDebug( LOGVERBOSE ) << Q_FUNC_INFO << "username not our barename"; return; } disconnect( ACLRegistry::instance(), SIGNAL( aclResult( QString, QString, ACLRegistry::ACL ) ) ); - tDebug( LOGVERBOSE ) << Q_FUNC_INFO << "ACL status is" << peerStatus; + tDebug( LOGVERBOSE ) << Q_FUNC_INFO << QString( "ACL status for user %1 is" ).arg( username ) << peerStatus; if ( peerStatus == ACLRegistry::Stream ) { QTimer::singleShot( 0, this, SLOT( doSetup() ) ); @@ -306,15 +312,20 @@ Connection::doSetup() void Connection::socketDisconnected() { + qint64 bytesAvailable = 0; + if ( !m_sock.isNull() ) + { + bytesAvailable = m_sock->bytesAvailable(); + } tDebug( LOGVERBOSE ) << "SOCKET DISCONNECTED" << this->name() << id() << "shutdown will happen after incoming queue empties." - << "bytesavail:" << m_sock->bytesAvailable() + << "bytesavail:" << bytesAvailable << "bytesRecvd" << bytesReceived(); m_peer_disconnected = true; emit socketClosed(); - if ( m_msgprocessor_in.length() == 0 && m_sock->bytesAvailable() == 0 ) + if ( m_msgprocessor_in.length() == 0 && bytesAvailable == 0 ) { handleIncomingQueueEmpty(); actualShutdown(); diff --git a/src/libtomahawk/network/Connection.h b/src/libtomahawk/network/Connection.h index ba8d92e28..769e05a58 100644 --- a/src/libtomahawk/network/Connection.h +++ b/src/libtomahawk/network/Connection.h @@ -60,28 +60,28 @@ public: void setFirstMessage( const QVariant& m ); void setFirstMessage( msg_ptr m ); - msg_ptr firstMessage() const { return m_firstmsg; }; + msg_ptr firstMessage() const { return m_firstmsg; } - const QPointer& socket() { return m_sock; }; + const QPointer& socket() { return m_sock; } - void setOutbound( bool o ) { m_outbound = o; }; + void setOutbound( bool o ) { m_outbound = o; } bool outbound() const { return m_outbound; } - Servent* servent() { return m_servent; }; + Servent* servent() { return m_servent; } // get public port of remote peer: - int peerPort() { return m_peerport; }; - void setPeerPort( int p ) { m_peerport = p; }; + int peerPort() { return m_peerport; } + void setPeerPort( int p ) { m_peerport = p; } void markAsFailed(); - void setName( const QString& n ) { m_name = n; }; - QString name() const { return m_name; }; + void setName( const QString& n ) { m_name = n; } + QString name() const { return m_name; } - void setOnceOnly( bool b ) { m_onceonly = b; }; - bool onceOnly() const { return m_onceonly; }; + void setOnceOnly( bool b ) { m_onceonly = b; } + bool onceOnly() const { return m_onceonly; } - bool isReady() const { return m_ready; } ; + bool isReady() const { return m_ready; } bool isRunning() const { return m_sock != 0; } qint64 bytesSent() const { return m_tx_bytes; } diff --git a/src/libtomahawk/network/ConnectionManager.cpp b/src/libtomahawk/network/ConnectionManager.cpp new file mode 100644 index 000000000..234e41869 --- /dev/null +++ b/src/libtomahawk/network/ConnectionManager.cpp @@ -0,0 +1,261 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2013, Uwe L. Korn + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + +#include "ConnectionManager.h" +#include "ControlConnection.h" +#include "Servent.h" + +#include "database/Database.h" +#include "database/DatabaseImpl.h" +#include "sip/SipPlugin.h" +#include "utils/Logger.h" + +#include +#include + +ConnectionManager::ConnectionManager( const QString &nodeid ) + : m_nodeid( nodeid ) +{ + // TODO sth? +} + +void +ConnectionManager::handleSipInfo( const Tomahawk::peerinfo_ptr &peerInfo ) +{ + // Start handling in a separate thread so that we do not block the event loop. + QtConcurrent::run( this, &ConnectionManager::handleSipInfoPrivate, peerInfo ); +} + +void +ConnectionManager::handleSipInfoPrivate( const Tomahawk::peerinfo_ptr &peerInfo ) +{ + m_mutex.lock(); + // Respect different behaviour before 0.7.999 + peerInfoDebug( peerInfo ) << Q_FUNC_INFO << "Trying to connect to client with version " << peerInfo->versionString().split(' ').last() << TomahawkUtils::compareVersionStrings( peerInfo->versionString().split(' ').last(), "0.7.99" ); + if ( !peerInfo->versionString().isEmpty() && TomahawkUtils::compareVersionStrings( peerInfo->versionString().split(' ').last(), "0.7.999" ) < 0) + { + peerInfoDebug( peerInfo ) << Q_FUNC_INFO << "Using old-style (<0.7.999) connection order."; + SipInfo we = Servent::getSipInfoForOldVersions( Servent::instance()->getLocalSipInfos( QString( "default" ), QString( "default" ) ) ); + SipInfo they = peerInfo->sipInfos().first(); + if ( they.isVisible() ) + { + if ( !we.isVisible() || we.host() < they.host() || (we.host() == they.host() && we.port() < they.port())) + { + tLog( LOGVERBOSE ) << Q_FUNC_INFO << "Initiate connection to" << peerInfo->id() << "at" << they.host() << "peer of:" << peerInfo->sipPlugin()->account()->accountFriendlyName(); + connectToPeer( peerInfo, false ); + return; + // We connected to the peer, so we are done here. + } + } + m_mutex.unlock(); + return; + } + foreach ( SipInfo info, peerInfo->sipInfos() ) + { + if (info.isVisible()) + { + // There is at least one SipInfo that may be visible. Try connecting. + // Duplicate Connections are checked by connectToPeer, so we do not need to take care of this + tLog( LOGVERBOSE ) << Q_FUNC_INFO << "Initiate connection to" << peerInfo->id() << "at" << info.host() << "peer of:" << peerInfo->sipPlugin()->account()->accountFriendlyName(); + connectToPeer( peerInfo, false ); + // We connected to the peer, so we are done here. + return; + } + } + m_mutex.unlock(); +} + +void +ConnectionManager::connectToPeer( const Tomahawk::peerinfo_ptr &peerInfo, bool lock ) +{ + // Lock, so that we will not attempt to do two parallell connects. + if (lock) + { + m_mutex.lock(); + } + // Check that we are not already connected to this peer + ControlConnection* cconn = Servent::instance()->lookupControlConnection( peerInfo->nodeId() ); + if ( cconn != NULL || !m_controlConnection.isNull() ) + { + // We are already connected to this peer, so just add some more details. + peerInfoDebug( peerInfo ) << "Existing connection found, not connecting."; + cconn->addPeerInfo( peerInfo ); + if ( cconn != NULL ) + { + m_controlConnection = QPointer(cconn); + } + m_mutex.unlock(); + return; + // TODO: Keep the peerInfo in mind for reconnecting + // FIXME: Do we need this for reconnecting if the connection drops? + } + + // If we are not connected, try to connect + m_currentPeerInfo = peerInfo; + peerInfoDebug( peerInfo ) << "No existing connection found, trying to connect."; + m_sipCandidates.append( peerInfo->sipInfos() ); + + QVariantMap m; + m["conntype"] = "accept-offer"; + m["key"] = peerInfo->key(); + m["nodeid"] = Database::instance()->impl()->dbid(); + + m_controlConnection = QPointer( new ControlConnection( Servent::instance() ) ); + m_controlConnection->addPeerInfo( peerInfo ); + m_controlConnection->setFirstMessage( m ); + + if ( peerInfo->id().length() ) + m_controlConnection->setName( peerInfo->contactId() ); + if ( peerInfo->nodeId().length() ) + m_controlConnection->setId( peerInfo->nodeId() ); + + m_controlConnection->setProperty( "nodeid", peerInfo->nodeId() ); + + Servent::instance()->registerControlConnection( m_controlConnection.data() ); + tryConnect(); +} + +void ConnectionManager::tryConnect() +{ + // ATTENTION: mutex should be already locked by the calling function. + Q_ASSERT( !m_controlConnection.isNull() ); + + if ( m_sipCandidates.isEmpty() ) + { + // No more possibilities to connect. + peerInfoDebug( m_currentPeerInfo ) << Q_FUNC_INFO << "No more possible SIP endpoints for " << m_controlConnection->name() << " skipping."; + + // Clean up. + m_currentPeerInfo.clear(); + delete m_controlConnection.data(); + m_mutex.unlock(); + return; + } + + // Use first available SIP endpoint and remove it from the list + SipInfo info = m_sipCandidates.takeFirst(); + if ( !info.isVisible() ) + { + peerInfoDebug( m_currentPeerInfo ) << Q_FUNC_INFO << "Try next SipInfo, we can't connect to this one"; + tryConnect(); + return; + } + + peerInfoDebug( m_currentPeerInfo ) << Q_FUNC_INFO << "Connecting to " << info.host() << ":" << info.port(); + Q_ASSERT( info.port() > 0 ); + + // Check that we are not connecting to ourselves + foreach( QHostAddress ha, Servent::instance()->addresses() ) + { + if ( info.host() == ha.toString() && info.port() == Servent::instance()->port() ) + { + peerInfoDebug( m_currentPeerInfo ) << Q_FUNC_INFO << "Tomahawk won't try to connect to" << info.host() << ":" << info.port() << ": same ip:port as ourselves."; + tryConnect(); + return; + } + } + if ( info.host() == Servent::instance()->additionalAddress() && info.port() == Servent::instance()->additionalPort() ) + { + peerInfoDebug( m_currentPeerInfo ) << Q_FUNC_INFO << "Tomahawk won't try to connect to" << info.host() << ":" << info.port() << ": same ip:port as ourselves."; + tryConnect(); + return; + } + + // We should have already setup a first message in connectToPeer + Q_ASSERT( !m_controlConnection->firstMessage().isNull() ); + + QTcpSocketExtra* sock = new QTcpSocketExtra(); + sock->setConnectTimeout( CONNECT_TIMEOUT ); + sock->_disowned = false; + sock->_conn = m_controlConnection.data(); + sock->_outbound = true; + + connect( sock, SIGNAL( connected() ), this, SLOT( socketConnected() ) ); + connect( sock, SIGNAL( error( QAbstractSocket::SocketError ) ), this, SLOT( socketError( QAbstractSocket::SocketError ) ) ); + + peerInfoDebug( m_currentPeerInfo ) << Q_FUNC_INFO << "Connecting socket to " << info.host() << ":" << info.port(); + sock->connectToHost( info.host(), info.port(), QTcpSocket::ReadWrite ); + sock->moveToThread( thread() ); +} + +void +ConnectionManager::socketError( QAbstractSocket::SocketError error ) +{ + Q_UNUSED( error ); + Q_ASSERT( !m_controlConnection.isNull() ); + + QTcpSocketExtra* sock = (QTcpSocketExtra*)sender(); + peerInfoDebug( m_currentPeerInfo ) << Q_FUNC_INFO << "Connecting to " << sock->peerAddress().toString() << " failed: " << sock->errorString(); + sock->deleteLater(); + + // Try to connect with the next available SipInfo. + tryConnect(); +} + + +void +ConnectionManager::socketConnected() +{ + QTcpSocketExtra* sock = (QTcpSocketExtra*)sender(); + + peerInfoDebug( m_currentPeerInfo ) << Q_FUNC_INFO << "Connected to hostaddr: " << sock->peerAddress() << ", hostname:" << sock->peerName(); + + Q_ASSERT( !sock->_conn.isNull() ); + + handoverSocket( sock ); + m_currentPeerInfo.clear(); + m_mutex.unlock(); +} + +void +ConnectionManager::handoverSocket( QTcpSocketExtra* sock ) +{ + Q_ASSERT( !m_controlConnection.isNull() ); + Q_ASSERT( sock ); + Q_ASSERT( m_controlConnection->socket().isNull() ); + Q_ASSERT( sock->isValid() ); + + disconnect( sock, SIGNAL( disconnected() ), sock, SLOT( deleteLater() ) ); + disconnect( sock, SIGNAL( error( QAbstractSocket::SocketError ) ), this, SLOT( socketError( QAbstractSocket::SocketError ) ) ); + + sock->_disowned = true; + m_controlConnection->setOutbound( sock->_outbound ); + m_controlConnection->setPeerPort( sock->peerPort() ); + + m_controlConnection->start( sock ); + m_currentPeerInfo.clear(); + m_mutex.unlock(); +} + + +static QMutex* nodeMapMutex = new QMutex(); +static QMap< QString, QSharedPointer< ConnectionManager > > connectionManagers; + +QSharedPointer +ConnectionManager::getManagerForNodeId( const QString &nodeid ) +{ + QMutexLocker locker( nodeMapMutex ); + if ( connectionManagers.contains( nodeid ) && !connectionManagers.value( nodeid ).isNull() ) { + return connectionManagers.value( nodeid ); + } + + // There exists no connection for this nodeid + QSharedPointer< ConnectionManager > manager( new ConnectionManager( nodeid ) ); + connectionManagers[nodeid] = manager; + return manager; +} diff --git a/src/libtomahawk/network/ConnectionManager.h b/src/libtomahawk/network/ConnectionManager.h new file mode 100644 index 000000000..4f405ebbf --- /dev/null +++ b/src/libtomahawk/network/ConnectionManager.h @@ -0,0 +1,68 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2013, Uwe L. Korn + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + +#ifndef CONNECTIONMANAGER_H +#define CONNECTIONMANAGER_H + +#include "DllMacro.h" + +#include "sip/PeerInfo.h" + +#include +#include +#include + +class QTcpSocketExtra; + +class DLLEXPORT ConnectionManager : public QObject +{ + Q_OBJECT + +public: + static QSharedPointer getManagerForNodeId( const QString& nodeid ); + ConnectionManager( const QString& nodeid ); + + void handleSipInfo( const Tomahawk::peerinfo_ptr& peerInfo ); + +private slots: + void socketConnected(); + void socketError( QAbstractSocket::SocketError error ); + +private: + void connectToPeer(const Tomahawk::peerinfo_ptr& peerInfo , bool lock); + void handleSipInfoPrivate( const Tomahawk::peerinfo_ptr& peerInfo ); + + /** + * Transfers ownership of socket to the ControlConnection and inits the ControlConnection + */ + void handoverSocket( QTcpSocketExtra* sock ); + + /** + * Attempt to connect to the peer using the current stored information. + */ + void tryConnect(); + + // We just keep this for debug purposes and only during connection attempts. + Tomahawk::peerinfo_ptr m_currentPeerInfo; + QString m_nodeid; + QPointer m_controlConnection; + QList m_sipCandidates; + QMutex m_mutex; +}; + +#endif // CONNECTIONMANAGER_H diff --git a/src/libtomahawk/network/ControlConnection.cpp b/src/libtomahawk/network/ControlConnection.cpp index 58c4ba7f3..10596fbe4 100644 --- a/src/libtomahawk/network/ControlConnection.cpp +++ b/src/libtomahawk/network/ControlConnection.cpp @@ -100,20 +100,29 @@ ControlConnection::setup() // setup source and remote collection for this peer m_source = SourceList::instance()->get( id(), friendlyName, true ); - m_source->setControlConnection( this ); + QSharedPointer locker = m_source->acquireLock(); + if ( m_source->setControlConnection( this ) ) + { + // We are the new ControlConnection for this source - // delay setting up collection/etc until source is synced. - // we need it DB synced so it has an ID + exists in DB. - connect( m_source.data(), SIGNAL( syncedWithDatabase() ), - SLOT( registerSource() ), Qt::QueuedConnection ); + // delay setting up collection/etc until source is synced. + // we need it DB synced so it has an ID + exists in DB. + connect( m_source.data(), SIGNAL( syncedWithDatabase() ), + SLOT( registerSource() ), Qt::QueuedConnection ); - m_source->setOnline(); + m_source->setOnline(); - m_pingtimer = new QTimer; - m_pingtimer->setInterval( 5000 ); - connect( m_pingtimer, SIGNAL( timeout() ), SLOT( onPingTimer() ) ); - m_pingtimer->start(); - m_pingtimer_mark.start(); + m_pingtimer = new QTimer; + m_pingtimer->setInterval( 5000 ); + connect( m_pingtimer, SIGNAL( timeout() ), SLOT( onPingTimer() ) ); + m_pingtimer->start(); + m_pingtimer_mark.start(); + } + else + { + // There is already another ControlConnection in use, we are useless. + deleteLater(); + } } @@ -121,13 +130,18 @@ ControlConnection::setup() void ControlConnection::registerSource() { - qDebug() << Q_FUNC_INFO << m_source->id(); - Source* source = (Source*) sender(); - Q_UNUSED( source ) - Q_ASSERT( source == m_source.data() ); + QSharedPointer locker = m_source->acquireLock(); + // Only continue if we are still the ControlConnection associated with this source. + if ( m_source->controlConnection() == this ) + { + qDebug() << Q_FUNC_INFO << m_source->id(); + Source* source = (Source*) sender(); + Q_UNUSED( source ) + Q_ASSERT( source == m_source.data() ); - m_registered = true; - setupDbSyncConnection(); + m_registered = true; + setupDbSyncConnection(); + } } @@ -221,6 +235,7 @@ ControlConnection::handleMsg( msg_ptr msg ) if ( !msg->is( Msg::JSON ) ) { Q_ASSERT( msg->is( Msg::JSON ) ); + tLog( LOGVERBOSE ) << Q_FUNC_INFO << "Received message was not in JSON format"; markAsFailed(); return; } diff --git a/src/libtomahawk/network/QTcpSocketExtra.cpp b/src/libtomahawk/network/QTcpSocketExtra.cpp new file mode 100644 index 000000000..4c1a51e5f --- /dev/null +++ b/src/libtomahawk/network/QTcpSocketExtra.cpp @@ -0,0 +1,65 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * Copyright 2010-2012, Jeff Mitchell + * Copyright 2013, Teo Mrnjavac + * Copyright 2013, Uwe L. Korn + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + +#include "QTcpSocketExtra.h" + +#include "utils/Logger.h" + +void +QTcpSocketExtra::connectToHost( const QHostAddress& host, quint16 port, OpenMode openMode ) +{ + if ( m_connectTimer->isActive() == true ) + { + tLog() << Q_FUNC_INFO << "Connection already establishing."; + return; + } + + QTcpSocket::connectToHost( host, port, openMode); + if ( m_connectTimeout > 0 ) + m_connectTimer->start( m_connectTimeout ); +} + +void +QTcpSocketExtra::connectToHost(const QString& host, quint16 port, OpenMode openMode) +{ + if ( m_connectTimer->isActive() == true ) + { + tLog() << Q_FUNC_INFO << "Connection already establishing."; + return; + } + + QTcpSocket::connectToHost( host, port, openMode); + if ( m_connectTimeout > 0 ) + m_connectTimer->start( m_connectTimeout ); +} + +void +QTcpSocketExtra::connectTimeout() +{ + m_connectTimer->stop(); + if ( state() != ConnectedState ) + { + // We did not manage to connect in the given timespan, so abort the attempt... + abort(); + // .. and notify error handlers. + emit error( SocketTimeoutError ); + } +} diff --git a/src/libtomahawk/network/QTcpSocketExtra.h b/src/libtomahawk/network/QTcpSocketExtra.h new file mode 100644 index 000000000..0ca56da1d --- /dev/null +++ b/src/libtomahawk/network/QTcpSocketExtra.h @@ -0,0 +1,93 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * Copyright 2010-2012, Jeff Mitchell + * Copyright 2013, Teo Mrnjavac + * Copyright 2013, Uwe L. Korn + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + +#ifndef QTCPSOCKETEXTRA_H +#define QTCPSOCKETEXTRA_H + +// time before new connection terminates if no auth received +#define AUTH_TIMEOUT 180000 + +#include +#include +#include +#include + +#include "Msg.h" +#include "DllMacro.h" + +class Connection; + +// this is used to hold a bit of state, so when a connected signal is emitted +// from a socket, we can associate it with a Connection object etc. +// In addition, functionality to limit the connection timeout is implemented. +class DLLEXPORT QTcpSocketExtra : public QTcpSocket +{ +Q_OBJECT + +public: + QTcpSocketExtra() : QTcpSocket(), m_connectTimeout( -1 ) + { + QTimer::singleShot( AUTH_TIMEOUT, this, SLOT( authTimeout() ) ) ; + m_connectTimer = new QTimer( this ); + connect( m_connectTimer, SIGNAL( timeout() ), this, SLOT( connectTimeout() ) ); + } + + void connectToHost(const QString& host, quint16 port, OpenMode openMode = ReadWrite ); + void connectToHost( const QHostAddress& host, quint16 port, OpenMode openMode = ReadWrite ); + + QPointer _conn; + bool _outbound; + bool _disowned; + msg_ptr _msg; + + /** + * Set a time limit for establishing a connection. + */ + void setConnectTimeout( qint32 timeout ) { m_connectTimeout = timeout; } + + /** + * Get the current timeout for establishing a connection. + */ + qint32 connectTimeout() const { return m_connectTimeout; } + +private slots: + void connectTimeout(); + void authTimeout() + { + if( _disowned ) + return; + + qDebug() << "Connection timed out before providing a valid offer-key"; + this->disconnectFromHost(); + } +private: + /** + * How long we will wait for a connection to establish + */ + qint32 m_connectTimeout; + + /** + * Timer to measure the connection initialisation + */ + QTimer* m_connectTimer; +}; + +#endif // QTCPSOCKETEXTRA_H diff --git a/src/libtomahawk/network/Servent.cpp b/src/libtomahawk/network/Servent.cpp index e22263976..3284e88e1 100644 --- a/src/libtomahawk/network/Servent.cpp +++ b/src/libtomahawk/network/Servent.cpp @@ -3,6 +3,7 @@ * Copyright 2010-2011, Christian Muehlhaeuser * Copyright 2010-2012, Jeff Mitchell * Copyright 2013, Teo Mrnjavac + * Copyright 2013, Uwe L. Korn * * Tomahawk is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -27,6 +28,7 @@ #include "ControlConnection.h" #include "database/Database.h" #include "database/DatabaseImpl.h" +#include "network/ConnectionManager.h" #include "StreamConnection.h" #include "SourceList.h" #include "sip/SipInfo.h" @@ -34,6 +36,7 @@ #include "sip/SipPlugin.h" #include "PortFwdThread.h" #include "TomahawkSettings.h" +#include "utils/Closure.h" #include "utils/TomahawkUtils.h" #include "utils/Logger.h" #include "accounts/AccountManager.h" @@ -50,6 +53,14 @@ #include + +typedef QPair< QList< SipInfo >, Connection* > sipConnectionPair; +Q_DECLARE_METATYPE( sipConnectionPair ) +Q_DECLARE_METATYPE( QList< SipInfo > ) +Q_DECLARE_METATYPE( Connection* ) +Q_DECLARE_METATYPE( QTcpSocketExtra* ) +Q_DECLARE_METATYPE( Tomahawk::peerinfo_ptr ) + using namespace Tomahawk; Servent* Servent::s_instance = 0; @@ -70,7 +81,6 @@ Servent::Servent( QObject* parent ) { s_instance = this; - m_lanHack = qApp->arguments().contains( "--lanhack" ); m_noAuth = qApp->arguments().contains( "--noauth" ); setProxy( QNetworkProxy::NoProxy ); @@ -113,6 +123,7 @@ Servent::~Servent() bool Servent::startListening( QHostAddress ha, bool upnp, int port ) { + m_externalAddresses = QList(); m_port = port; int defPort = TomahawkSettings::instance()->defaultPort(); @@ -125,8 +136,8 @@ Servent::startListening( QHostAddress ha, bool upnp, int port ) { if ( !listen( ha, defPort ) ) { - tLog() << "Failed to listen on both port" << m_port << "and port" << defPort; - tLog() << "Error string is:" << errorString(); + tLog() << Q_FUNC_INFO << "Failed to listen on both port" << m_port << "and port" << defPort; + tLog() << Q_FUNC_INFO << "Error string is:" << errorString(); return false; } else @@ -134,38 +145,67 @@ Servent::startListening( QHostAddress ha, bool upnp, int port ) } } - TomahawkSettings::ExternalAddressMode mode = TomahawkSettings::instance()->externalAddressMode(); + if ( ha == QHostAddress::AnyIPv6 ) + { + // We are listening on all available addresses, so we should send a SipInfo for all of them. + foreach ( QHostAddress addr, QNetworkInterface::allAddresses() ) + { + if ( addr.toString() == "127.0.0.1" ) + continue; // IPv4 localhost + if ( addr.toString() == "::1" ) + continue; // IPv6 localhost + if ( addr.toString() == "::7F00:1" ) + continue; // IPv4 localhost as IPv6 address + if ( addr.isInSubnet( QHostAddress::parseSubnet( "fe80::/10" ) ) ) + continue; // Skip link local addresses + tLog( LOGVERBOSE ) << Q_FUNC_INFO << "Listening to " << addr.toString(); + m_externalAddresses.append( addr ); + } - tLog() << "Servent listening on port" << m_port << "- servent thread:" << thread() + } + else if ( ( ha.toString() != "127.0.0.1" ) && ( ha.toString() != "::1" ) && ( ha.toString() != "::7F00:1" ) ) + { + // We listen only to one specific Address, only announce this. + m_externalAddresses.append( ha ); + } + // If we only accept connections via localhost, we'll announce nothing. + + TomahawkSettings::ExternalAddressMode mode = TomahawkSettings::instance()->externalAddressMode(); + tLog( LOGVERBOSE ) << Q_FUNC_INFO << "Servent listening on port" << m_port << "- servent thread:" << thread() << "- address mode:" << (int)( mode ); - // --lanhack means to advertise your LAN IP as if it were externally visible switch ( mode ) { case TomahawkSettings::Static: m_externalHostname = TomahawkSettings::instance()->externalHostname(); m_externalPort = TomahawkSettings::instance()->externalPort(); m_ready = true; + // All setup is made, were done. emit ready(); break; case TomahawkSettings::Lan: - setInternalAddress(); + // Nothing has to be done here. + m_ready = true; + emit ready(); break; case TomahawkSettings::Upnp: - if ( !upnp ) + if ( upnp ) { - setInternalAddress(); - break; + // upnp could be turned of on the cli with --noupnp + tLog( LOGVERBOSE ) << Q_FUNC_INFO << "External address mode set to upnp..."; + m_portfwd = QPointer< PortFwdThread >( new PortFwdThread( m_port ) ); + Q_ASSERT( m_portfwd ); + connect( m_portfwd.data(), SIGNAL( externalAddressDetected( QHostAddress, unsigned int ) ), + SLOT( setExternalAddress( QHostAddress, unsigned int ) ) ); + m_portfwd.data()->start(); + } + else + { + m_ready = true; + emit ready(); } - // TODO check if we have a public/internet IP on this machine directly - tLog() << "External address mode set to upnp..."; - m_portfwd = QPointer< PortFwdThread >( new PortFwdThread( m_port ) ); - Q_ASSERT( m_portfwd ); - connect( m_portfwd.data(), SIGNAL( externalAddressDetected( QHostAddress, unsigned int ) ), - SLOT( setExternalAddress( QHostAddress, unsigned int ) ) ); - m_portfwd.data()->start(); break; } @@ -173,6 +213,25 @@ Servent::startListening( QHostAddress ha, bool upnp, int port ) } +void +Servent::setExternalAddress( QHostAddress ha, unsigned int port ) +{ + if ( isValidExternalIP( ha ) ) + { + m_externalHostname = ha.toString(); + m_externalPort = port; + } + + if ( m_externalPort == 0 || !isValidExternalIP( ha ) ) + tLog() << Q_FUNC_INFO << "UPnP failed, no further external address could be acquired!"; + else + tLog( LOGVERBOSE ) << Q_FUNC_INFO << "UPnP setup successful"; + + m_ready = true; + emit ready(); +} + + QString Servent::createConnectionKey( const QString& name, const QString &nodeid, const QString &key, bool onceOnly ) { @@ -190,12 +249,56 @@ Servent::createConnectionKey( const QString& name, const QString &nodeid, const return _key; } - bool -Servent::isValidExternalIP( const QHostAddress& addr ) const +Servent::isValidExternalIP( const QHostAddress& addr ) { QString ip = addr.toString(); - if ( !m_lanHack && ( ip.startsWith( "10." ) || ip.startsWith( "172.16." ) || ip.startsWith( "192.168." ) ) ) + if (addr.protocol() == QAbstractSocket::IPv4Protocol) + { + // private network + if ( addr.isInSubnet(QHostAddress::parseSubnet("10.0.0.0/8")) ) + return false; + // localhost + if ( addr.isInSubnet(QHostAddress::parseSubnet("127.0.0.0/8")) ) + return false; + // private network + if ( addr.isInSubnet(QHostAddress::parseSubnet("169.254.0.0/16")) ) + return false; + // private network + if ( addr.isInSubnet(QHostAddress::parseSubnet("172.16.0.0/12")) ) + return false; + // private network + if ( addr.isInSubnet(QHostAddress::parseSubnet("192.168.0.0/16")) ) + return false; + // multicast + if ( addr.isInSubnet(QHostAddress::parseSubnet("224.0.0.0/4")) ) + return false; + } + else if (addr.protocol() == QAbstractSocket::IPv4Protocol) + { + // "unspecified address" + if ( addr.isInSubnet(QHostAddress::parseSubnet("::/128")) ) + return false; + // link-local + if ( addr.isInSubnet(QHostAddress::parseSubnet("fe80::/10")) ) + return false; + // unique local addresses + if ( addr.isInSubnet(QHostAddress::parseSubnet("fc00::/7")) ) + return false; + // benchmarking only + if ( addr.isInSubnet(QHostAddress::parseSubnet("2001:2::/48")) ) + return false; + // non-routed IPv6 addresses used for Cryptographic Hash Identifiers + if ( addr.isInSubnet(QHostAddress::parseSubnet("2001:10::/28")) ) + return false; + // documentation prefix + if ( addr.isInSubnet(QHostAddress::parseSubnet("2001:db8::/32")) ) + return false; + // multicast + if ( addr.isInSubnet(QHostAddress::parseSubnet("ff00::0/8 ")) ) + return false; + } + else { return false; } @@ -203,60 +306,35 @@ Servent::isValidExternalIP( const QHostAddress& addr ) const return !addr.isNull(); } - -void -Servent::setInternalAddress() -{ - foreach ( QHostAddress ha, QNetworkInterface::allAddresses() ) - { - if ( ha.toString() == "127.0.0.1" ) - continue; - if ( ha.toString().contains( ":" ) ) - continue; //ipv6 - - if ( m_lanHack && isValidExternalIP( ha ) ) - { - tLog() << "LANHACK: set external address to lan address" << ha.toString(); - setExternalAddress( ha, m_port ); - } - else - { - m_ready = true; - emit ready(); - } - break; - } -} - - -void -Servent::setExternalAddress( QHostAddress ha, unsigned int port ) -{ - if ( isValidExternalIP( ha ) ) - { - m_externalAddress = ha; - m_externalPort = port; - } - - if ( m_externalPort == 0 || !isValidExternalIP( ha ) ) - { - tLog() << "UPnP failed, LAN and outbound connections only!"; - setInternalAddress(); - return; - } - - tLog() << "UPnP setup successful"; - m_ready = true; - emit ready(); -} - - void Servent::registerOffer( const QString& key, Connection* conn ) { m_offers[key] = QPointer(conn); } +void +Servent::registerLazyOffer(const QString &key, const peerinfo_ptr &peerInfo, const QString &nodeid, const int timeout ) +{ + m_lazyoffers[key] = QPair< peerinfo_ptr, QString >( peerInfo, nodeid ); + QTimer* timer = new QTimer( this ); + timer->setInterval( timeout ); + timer->setSingleShot( true ); + NewClosure( timer, SIGNAL( timeout() ), this, SLOT( deleteLazyOffer( const QString& ) ), key ); + timer->start(); +} + +void +Servent::deleteLazyOffer( const QString& key ) +{ + m_lazyoffers.remove( key ); + + // Cleanup. + QTimer* timer = (QTimer*)sender(); + if ( timer ) + { + timer->deleteLater(); + } +} void Servent::registerControlConnection( ControlConnection* conn ) @@ -292,6 +370,73 @@ Servent::lookupControlConnection( const SipInfo& sipInfo ) return NULL; } +ControlConnection* +Servent::lookupControlConnection( const QString& nodeid ) +{ + foreach ( ControlConnection* c, m_controlconnections ) + { + if ( c->id() == nodeid ) + return c; + } + return NULL; +} + + +QList +Servent::getLocalSipInfos( const QString& nodeid, const QString& key ) +{ + QList sipInfos = QList(); + foreach ( QHostAddress ha, m_externalAddresses ) + { + SipInfo info = SipInfo(); + info.setHost( ha.toString() ); + info.setPort( m_port ); + info.setKey( key ); + info.setVisible( true ); + info.setNodeId( nodeid ); + sipInfos.append( info ); + } + if ( m_externalHostname.length() > 0) + { + SipInfo info = SipInfo(); + info.setHost( m_externalHostname ); + info.setPort( m_externalPort ); + info.setKey( key ); + info.setVisible( true ); + info.setNodeId( nodeid ); + sipInfos.append( info ); + } + + if ( sipInfos.isEmpty() ) + { + // We are not visible via any IP, send a dummy SipInfo + SipInfo info = SipInfo(); + info.setVisible( false ); + info.setKey( key ); + info.setNodeId( nodeid ); + tDebug( LOGVERBOSE ) << Q_FUNC_INFO << "Only accepting connections, no usable IP to listen to found."; + } + + return sipInfos; +} + +SipInfo +Servent::getSipInfoForOldVersions( const QList& sipInfos ) +{ + SipInfo info = SipInfo(); + info.setVisible( false ); + foreach ( SipInfo _info, sipInfos ) + { + QHostAddress ha = QHostAddress( _info.host() ); + if ( ( Servent::isValidExternalIP( ha ) && ha.protocol() == QAbstractSocket::IPv4Protocol ) || ( ha.protocol() == QAbstractSocket::UnknownNetworkLayerProtocol ) || ( ha.isNull() && !_info.host().isEmpty() )) + { + info = _info; + break; + } + } + + return info; +} void Servent::registerPeer( const Tomahawk::peerinfo_ptr& peerInfo ) @@ -312,9 +457,9 @@ Servent::registerPeer( const Tomahawk::peerinfo_ptr& peerInfo ) if ( peerInfo->type() == Tomahawk::PeerInfo::Local ) { peerInfoDebug(peerInfo) << "we need to establish the connection now... thinking"; - if ( !connectedToSession( peerInfo->sipInfo().nodeId() ) ) + if ( !connectedToSession( peerInfo->nodeId() ) ) { - connectToPeer( peerInfo ); + ConnectionManager::getManagerForNodeId( peerInfo->nodeId() )->handleSipInfo( peerInfo ); } else { @@ -335,35 +480,23 @@ Servent::registerPeer( const Tomahawk::peerinfo_ptr& peerInfo ) } else { - SipInfo info; - QString peerId = peerInfo->id(); QString key = uuid(); - ControlConnection* conn = new ControlConnection( this ); - const QString& nodeid = Database::instance()->impl()->dbid(); - conn->setName( peerInfo->contactId() ); - conn->setId( nodeid ); - conn->addPeerInfo( peerInfo ); - if ( visibleExternally() ) + QList sipInfos = getLocalSipInfos( nodeid, key ); + // The offer should be removed after some time or we will build up a heap of unused PeerInfos + registerLazyOffer( key, peerInfo, nodeid, sipInfos.length() * 1.5 * CONNECT_TIMEOUT ); + // SipInfos were single-value before 0.7.999 + if ( !peerInfo->versionString().isEmpty() && TomahawkUtils::compareVersionStrings( peerInfo->versionString().split(' ').last(), "0.7.999" ) < 0) { - registerOffer( key, conn ); - info.setVisible( true ); - info.setHost( externalAddress() ); - info.setPort( externalPort() ); - info.setKey( key ); - info.setNodeId( nodeid ); - - tDebug() << "Asking them (" << peerInfo->id() << ") to connect to us:" << info; + SipInfo info = getSipInfoForOldVersions( sipInfos ); + peerInfo->sendLocalSipInfos( QList() << info ); } else { - info.setVisible( false ); - tDebug() << "We are not visible externally:" << info; + peerInfo->sendLocalSipInfos( sipInfos ); } - peerInfo->sendLocalSipInfo( info ); - handleSipInfo( peerInfo ); connect( peerInfo.data(), SIGNAL( sipInfoChanged() ), SLOT( onSipInfoChanged() ) ); } @@ -384,43 +517,12 @@ Servent::onSipInfoChanged() void Servent::handleSipInfo( const Tomahawk::peerinfo_ptr& peerInfo ) { - tLog() << Q_FUNC_INFO << peerInfo->id() << peerInfo->sipInfo(); - - SipInfo info = peerInfo->sipInfo(); - if ( !info.isValid() ) + // We do not have received the initial SipInfo for this client yet, so wait for it. + // Each client will have at least one non-visible SipInfo + if ( peerInfo->sipInfos().isEmpty() ) return; - /* - If only one party is externally visible, connection is obvious - If both are, peer with lowest IP address initiates the connection. - - This avoids dupe connections. - */ - if ( info.isVisible() ) - { - if ( !visibleExternally() || - externalAddress() < info.host() || - ( externalAddress() == info.host() && externalPort() < info.port() ) ) - { - - tDebug() << "Initiate connection to" << peerInfo->id() << "at" << info.host() << "peer of:" << peerInfo->sipPlugin()->account()->accountFriendlyName(); - connectToPeer( peerInfo ); - } - else - { - tDebug() << Q_FUNC_INFO << "They should be conecting to us..."; - } - } - else - { - tDebug() << Q_FUNC_INFO << "They are not visible, doing nothing atm"; - - if ( !visibleExternally() ) - { - if ( peerInfo->controlConnection() ) - delete peerInfo->controlConnection(); - } - } + ConnectionManager::getManagerForNodeId( peerInfo->nodeId() )->handleSipInfo( peerInfo ); } void @@ -450,6 +552,7 @@ Servent::readyRead() { Q_ASSERT( this->thread() == QThread::currentThread() ); QPointer< QTcpSocketExtra > sock = (QTcpSocketExtra*)sender(); + tLog( LOGVERBOSE ) << Q_FUNC_INFO << "Starting to read from new incoming connection from: " << sock->peerAddress().toString(); if ( sock.isNull() || sock.data()->_disowned ) { @@ -616,10 +719,14 @@ Servent::createParallelConnection( Connection* orig_conn, Connection* new_conn, // if we can connect to them directly: if ( orig_conn && orig_conn->outbound() ) { - connectToPeer( orig_conn->socket()->peerAddress().toString(), - orig_conn->peerPort(), - key, - new_conn ); + SipInfo info = SipInfo(); + info.setVisible( true ); + info.setKey( key ); + info.setNodeId( orig_conn->id() ); + info.setHost( orig_conn->socket()->peerAddress().toString() ); + info.setPort( orig_conn->peerPort() ); + Q_ASSERT( info.isValid() ); + initiateConnection( info, new_conn ); } else // ask them to connect to us: { @@ -631,7 +738,6 @@ Servent::createParallelConnection( Connection* orig_conn, Connection* new_conn, m.insert( "conntype", "request-offer" ); m.insert( "key", tmpkey ); m.insert( "offer", key ); - m.insert( "port", externalPort() ); m.insert( "controlid", Database::instance()->impl()->dbid() ); QJson::Serializer ser; @@ -645,13 +751,13 @@ Servent::socketConnected() { QTcpSocketExtra* sock = (QTcpSocketExtra*)sender(); - tDebug( LOGVERBOSE ) << Q_FUNC_INFO << thread() << "socket:" << sock << ", hostaddr:" << sock->peerAddress() << ", hostname:" << sock->peerName(); + tLog( LOGVERBOSE ) << Q_FUNC_INFO << thread() << "socket:" << sock << ", hostaddr:" << sock->peerAddress() << ", hostname:" << sock->peerName(); if ( sock->_conn.isNull() ) { sock->close(); sock->deleteLater(); - tDebug( LOGVERBOSE ) << Q_FUNC_INFO << "Socket's connection was null, could have timed out or been given an invalid address"; + tLog( LOGVERBOSE ) << Q_FUNC_INFO << "Socket's connection was null, could have timed out or been given an invalid address"; return; } @@ -659,9 +765,9 @@ Servent::socketConnected() handoverSocket( conn, sock ); } - // transfers ownership of socket to the connection and inits the connection -void Servent::handoverSocket( Connection* conn, QTcpSocketExtra* sock ) +void +Servent::handoverSocket( Connection* conn, QTcpSocketExtra* sock ) { Q_ASSERT( conn ); Q_ASSERT( sock ); @@ -670,8 +776,7 @@ void Servent::handoverSocket( Connection* conn, QTcpSocketExtra* sock ) disconnect( sock, SIGNAL( readyRead() ), this, SLOT( readyRead() ) ); disconnect( sock, SIGNAL( disconnected() ), sock, SLOT( deleteLater() ) ); - disconnect( sock, SIGNAL( error( QAbstractSocket::SocketError ) ), - this, SLOT( socketError( QAbstractSocket::SocketError ) ) ); + disconnect( sock, SIGNAL( error( QAbstractSocket::SocketError ) ), this, SLOT( socketError( QAbstractSocket::SocketError ) ) ); sock->_disowned = true; conn->setOutbound( sock->_outbound ); @@ -680,6 +785,69 @@ void Servent::handoverSocket( Connection* conn, QTcpSocketExtra* sock ) conn->start( sock ); } +void +Servent::cleanupSocket( QTcpSocketExtra *sock ) +{ + if ( !sock ) + { + tLog() << "SocketError, sock is null"; + return; + } + + if ( sock->_conn.isNull() ) + { + tLog() << "SocketError, connection is null"; + } + sock->deleteLater(); +} + +void +Servent::initiateConnection( const SipInfo& sipInfo, Connection* conn ) +{ + Q_ASSERT( sipInfo.isValid() ); + Q_ASSERT( sipInfo.isVisible() ); + Q_ASSERT( sipInfo.port() > 0 ); + Q_ASSERT( conn ); + + // Check that we are not connecting to ourselves + foreach( QHostAddress ha, m_externalAddresses ) + { + if ( sipInfo.host() == ha.toString() ) + { + tLog( LOGVERBOSE ) << Q_FUNC_INFO << "Tomahawk won't try to connect to" << sipInfo.host() << ":" << sipInfo.port() << ": same IP as ourselves."; + return; + } + } + if ( sipInfo.host() == m_externalHostname ) + { + tLog( LOGVERBOSE ) << Q_FUNC_INFO << "Tomahawk won't try to connect to" << sipInfo.host() << ":" << sipInfo.port() << ": same IP as ourselves."; + return; + } + + if ( !sipInfo.key().isEmpty() && conn->firstMessage().isNull() ) + { + QVariantMap m; + m["conntype"] = "accept-offer"; + m["key"] = sipInfo.key(); + m["controlid"] = Database::instance()->impl()->dbid(); + conn->setFirstMessage( m ); + } + + QTcpSocketExtra* sock = new QTcpSocketExtra(); + sock->setConnectTimeout( CONNECT_TIMEOUT ); + sock->_disowned = false; + sock->_conn = conn; + sock->_outbound = true; + + connect( sock, SIGNAL( connected() ), SLOT( socketConnected() ) ); + connect( sock, SIGNAL( error( QAbstractSocket::SocketError ) ), this, SLOT( socketError( QAbstractSocket::SocketError ) ) ); + + if ( !conn->peerIpAddress().isNull() ) + sock->connectToHost( conn->peerIpAddress(), sipInfo.port(), QTcpSocket::ReadWrite ); + else + sock->connectToHost( sipInfo.host(), sipInfo.port(), QTcpSocket::ReadWrite ); + sock->moveToThread( thread() ); +} void Servent::socketError( QAbstractSocket::SocketError e ) @@ -711,132 +879,6 @@ Servent::socketError( QAbstractSocket::SocketError e ) } } - -void -Servent::connectToPeer( const peerinfo_ptr& peerInfo ) -{ - Q_ASSERT( this->thread() == QThread::currentThread() ); - - SipInfo sipInfo = peerInfo->sipInfo(); - - peerInfoDebug( peerInfo ) << "connectToPeer: search for already established connections to the same nodeid:" << m_controlconnections.count() << "connections"; - if ( peerInfo->controlConnection() ) - delete peerInfo->controlConnection(); - - bool isDupe = false; - ControlConnection* conn = 0; - // try to find a ControlConnection with the same SipInfo, then we dont need to try to connect again - - foreach ( ControlConnection* c, m_controlconnections ) - { - Q_ASSERT( c ); - - if ( c->id() == sipInfo.nodeId() ) - { - conn = c; - - foreach ( const peerinfo_ptr& currentPeerInfo, c->peerInfos() ) - { - peerInfoDebug( currentPeerInfo ) << "Same object:" << ( peerInfo == currentPeerInfo ) << ( peerInfo.data() == currentPeerInfo.data() ) << ( peerInfo->debugName() == currentPeerInfo->debugName() ); - - if ( peerInfo == currentPeerInfo ) - { - isDupe = true; - peerInfoDebug( currentPeerInfo ) << "Not adding, because it's a dupe: peerInfoCount remains the same" << conn->peerInfos().count(); - break; - } - } - - if ( !c->peerInfos().contains( peerInfo ) ) - { - c->addPeerInfo( peerInfo ); -// peerInfoDebug(peerInfo) << "Adding " << peerInfo->debugName() << ", not a dupe... new peerInfoCount:" << c->peerInfos().count(); -// foreach ( const peerinfo_ptr& kuh, c->peerInfos() ) -// { -// peerInfoDebug(peerInfo) << " ** " << kuh->debugName(); -// } - } - - break; - } - } - - peerInfoDebug( peerInfo ) << "connectToPeer: found a match:" << ( conn ? conn->name() : "false" ) << "dupe:" << isDupe; - - if ( isDupe ) - { - peerInfoDebug( peerInfo ) << "it's a dupe, nothing to do here, returning and stopping processing: peerInfoCount:" << conn->peerInfos().count(); - } - - if ( conn ) - return; - - QVariantMap m; - m["conntype"] = "accept-offer"; - m["key"] = sipInfo.key(); - m["port"] = externalPort(); - m["nodeid"] = Database::instance()->impl()->dbid(); - - peerInfoDebug(peerInfo) << "No match found, creating a new ControlConnection..."; - conn = new ControlConnection( this ); - conn->addPeerInfo( peerInfo ); - conn->setFirstMessage( m ); - - if ( peerInfo->id().length() ) - conn->setName( peerInfo->contactId() ); - if ( sipInfo.nodeId().length() ) - conn->setId( sipInfo.nodeId() ); - - conn->setProperty( "nodeid", sipInfo.nodeId() ); - - registerControlConnection( conn ); - connectToPeer( sipInfo.host(), sipInfo.port(), sipInfo.key(), conn ); -} - - -void -Servent::connectToPeer( const QString& ha, int port, const QString& key, Connection* conn ) -{ - tDebug( LOGVERBOSE ) << "Servent::connectToPeer:" << ha << ":" << port - << thread() << QThread::currentThread(); - - Q_ASSERT( port > 0 ); - Q_ASSERT( conn ); - - if ( ( ha == m_externalAddress.toString() || ha == m_externalHostname ) && - ( port == m_externalPort ) ) - { - tDebug() << "ERROR: Tomahawk won't try to connect to" << ha << ":" << port << ": identified as ourselves."; - return; - } - - if ( key.length() && conn->firstMessage().isNull() ) - { - QVariantMap m; - m["conntype"] = "accept-offer"; - m["key"] = key; - m["port"] = externalPort(); - m["controlid"] = Database::instance()->impl()->dbid(); - conn->setFirstMessage( m ); - } - - QTcpSocketExtra* sock = new QTcpSocketExtra(); - sock->_disowned = false; - sock->_conn = conn; - sock->_outbound = true; - - connect( sock, SIGNAL( connected() ), SLOT( socketConnected() ) ); - connect( sock, SIGNAL( error( QAbstractSocket::SocketError ) ), - SLOT( socketError( QAbstractSocket::SocketError ) ) ); - - if ( !conn->peerIpAddress().isNull() ) - sock->connectToHost( conn->peerIpAddress(), port, QTcpSocket::ReadWrite ); - else - sock->connectToHost( ha, port, QTcpSocket::ReadWrite ); - sock->moveToThread( thread() ); -} - - void Servent::reverseOfferRequest( ControlConnection* orig_conn, const QString& theirdbid, const QString& key, const QString& theirkey ) { @@ -854,13 +896,11 @@ Servent::reverseOfferRequest( ControlConnection* orig_conn, const QString& their QVariantMap m; m["conntype"] = "push-offer"; m["key"] = theirkey; - m["port"] = externalPort(); m["controlid"] = Database::instance()->impl()->dbid(); new_conn->setFirstMessage( m ); createParallelConnection( orig_conn, new_conn, QString() ); } - // return the appropriate connection for a given offer key, or NULL if invalid Connection* Servent::claimOffer( ControlConnection* cc, const QString &nodeid, const QString &key, const QHostAddress peer ) @@ -922,7 +962,20 @@ Servent::claimOffer( ControlConnection* cc, const QString &nodeid, const QString } } - if ( m_offers.contains( key ) ) + if ( m_lazyoffers.contains( key ) ) + { + ControlConnection* conn = new ControlConnection( this ); + conn->setName( m_lazyoffers.value( key ).first->contactId() ); + conn->addPeerInfo( m_lazyoffers.value( key ).first ); + conn->setId( m_lazyoffers.value( key ).second ); + + // Register as non-lazy offer + m_lazyoffers.remove( key ); + registerOffer( key, conn ); + + return conn; + } + else if ( m_offers.contains( key ) ) { QPointer conn = m_offers.value( key ); if ( conn.isNull() ) diff --git a/src/libtomahawk/network/Servent.h b/src/libtomahawk/network/Servent.h index 622c3f84d..d18300dd5 100644 --- a/src/libtomahawk/network/Servent.h +++ b/src/libtomahawk/network/Servent.h @@ -3,6 +3,7 @@ * Copyright 2010-2011, Christian Muehlhaeuser * Copyright 2010-2012, Jeff Mitchell * Copyright 2013, Teo Mrnjavac + * Copyright 2013, Uwe L. Korn * * Tomahawk is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -21,8 +22,8 @@ #ifndef SERVENT_H #define SERVENT_H -// time before new connection terminates if no auth received -#define AUTH_TIMEOUT 180000 +// time before new connection terminate if it could not be established +#define CONNECT_TIMEOUT 10000 #include #include @@ -40,6 +41,7 @@ #include "Typedefs.h" #include "Msg.h" +#include "network/QTcpSocketExtra.h" #include @@ -58,56 +60,30 @@ class SipInfo; typedef boost::function< void( const Tomahawk::result_ptr&, boost::function< void( QSharedPointer< QIODevice >& ) > )> IODeviceFactoryFunc; -// this is used to hold a bit of state, so when a connected signal is emitted -// from a socket, we can associate it with a Connection object etc. -class DLLEXPORT QTcpSocketExtra : public QTcpSocket -{ -Q_OBJECT - -public: - QTcpSocketExtra() : QTcpSocket() - { - QTimer::singleShot( AUTH_TIMEOUT, this, SLOT( authTimeout() ) ) ; - } - - QPointer _conn; - bool _outbound; - bool _disowned; - msg_ptr _msg; - -private slots: - void authTimeout() - { - if( _disowned ) - return; - - qDebug() << "Connection timed out before providing a valid offer-key"; - this->disconnectFromHost(); - } -}; - class DLLEXPORT Servent : public QTcpServer { Q_OBJECT public: static Servent* instance(); + static bool isValidExternalIP( const QHostAddress& addr ); + static SipInfo getSipInfoForOldVersions( const QList &sipInfos ); explicit Servent( QObject* parent = 0 ); virtual ~Servent(); bool startListening( QHostAddress ha, bool upnp, int port ); - int port() const { return m_port; } - // creates new token that allows a controlconnection to be set up QString createConnectionKey( const QString& name = "", const QString &nodeid = "", const QString &key = "", bool onceOnly = true ); void registerOffer( const QString& key, Connection* conn ); + void registerLazyOffer( const QString& key, const Tomahawk::peerinfo_ptr& peerInfo, const QString &nodeid , const int timeout ); void registerControlConnection( ControlConnection* conn ); void unregisterControlConnection( ControlConnection* conn ); ControlConnection* lookupControlConnection( const SipInfo& sipInfo ); + ControlConnection* lookupControlConnection( const QString& nodeid ); // you may call this method as often as you like for the same peerInfo, dupe checking is done inside void registerPeer( const Tomahawk::peerinfo_ptr& peerInfo ); @@ -117,13 +93,30 @@ public slots: void onSipInfoChanged(); public: - void connectToPeer( const Tomahawk::peerinfo_ptr& ha ); - void connectToPeer( const QString& ha, int port, const QString& key, Connection* conn ); - void reverseOfferRequest( ControlConnection* orig_conn, const QString& theirdbid, const QString& key, const QString& theirkey ); + void initiateConnection( const SipInfo& sipInfo, Connection* conn ); + void reverseOfferRequest( ControlConnection* orig_conn, const QString &theirdbid, const QString& key, const QString& theirkey ); - bool visibleExternally() const { return !m_externalHostname.isNull() || (m_externalPort > 0 && !m_externalAddress.isNull()); } - QString externalAddress() const { return !m_externalHostname.isNull() ? m_externalHostname : m_externalAddress.toString(); } - int externalPort() const { return m_externalPort; } + bool visibleExternally() const { return (!m_externalHostname.isNull()) || (m_externalAddresses.length() > 0); } + + /** + * The port this Peer listens directly (per default) + */ + int port() const { return m_port; } + + /** + * The IP addresses this Peer listens directly (per default) + */ + QList< QHostAddress > addresses() const { return m_externalAddresses; } + + /** + * An additional address this peer listens to, e.g. via UPnP. + */ + QString additionalAddress() const { return m_externalHostname; } + + /** + * An additional port this peer listens to, e.g. via UPnP (only in combination with additionalAddress. + */ + int additionalPort() const { return m_externalPort; } static bool isIPWhitelisted( QHostAddress ip ); @@ -138,8 +131,9 @@ public: void localFileIODeviceFactory( const Tomahawk::result_ptr& result, boost::function< void ( QSharedPointer< QIODevice >& ) > callback ); void httpIODeviceFactory( const Tomahawk::result_ptr& result, boost::function< void ( QSharedPointer< QIODevice >& ) > callback ); - bool isReady() const { return m_ready; }; + bool isReady() const { return m_ready; } + QList getLocalSipInfos(const QString& nodeid, const QString &key); signals: void dbSyncTriggered(); void streamStarted( StreamConnection* ); @@ -150,10 +144,8 @@ protected: void incomingConnection( int sd ); public slots: - void setInternalAddress(); void setExternalAddress( QHostAddress ha, unsigned int port ); - void socketError( QAbstractSocket::SocketError ); void createParallelConnection( Connection* orig_conn, Connection* new_conn, const QString& key ); void registerStreamConnection( StreamConnection* ); @@ -163,25 +155,44 @@ public slots: void triggerDBSync(); private slots: + void deleteLazyOffer( const QString& key ); void readyRead(); + void socketError( QAbstractSocket::SocketError e ); Connection* claimOffer( ControlConnection* cc, const QString &nodeid, const QString &key, const QHostAddress peer = QHostAddress::Any ); private: - bool isValidExternalIP( const QHostAddress& addr ) const; void handoverSocket( Connection* conn, QTcpSocketExtra* sock ); + void cleanupSocket( QTcpSocketExtra* sock ); void printCurrentTransfers(); QJson::Parser parser; QList< ControlConnection* > m_controlconnections; // canonical list of authed peers QMap< QString, QPointer< Connection > > m_offers; + QMap< QString, QPair< Tomahawk::peerinfo_ptr, QString > > m_lazyoffers; QStringList m_connectedNodes; - int m_port, m_externalPort; - QHostAddress m_externalAddress; + /** + * The external port used by all address except those obtained via UPnP or the static configuration option + */ + int m_port; + + /** + * Either the static set or the UPnP set external port + */ + int m_externalPort; + + /** + * All available external IPs + */ + QList m_externalAddresses; + + /** + * Either the static set or the UPnP set external host + */ QString m_externalHostname; + bool m_ready; - bool m_lanHack; bool m_noAuth; // currently active file transfers: diff --git a/src/libtomahawk/sip/PeerInfo.cpp b/src/libtomahawk/sip/PeerInfo.cpp index 6630f2501..047e46830 100644 --- a/src/libtomahawk/sip/PeerInfo.cpp +++ b/src/libtomahawk/sip/PeerInfo.cpp @@ -1,6 +1,7 @@ /* === This file is part of Tomahawk Player - === * * Copyright 2012, Dominik Schmidt + * Copyright 2013, Uwe L. Korn * * Tomahawk is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -202,9 +203,9 @@ PeerInfo::sipPlugin() const void -PeerInfo::sendLocalSipInfo( const SipInfo& sipInfo ) +PeerInfo::sendLocalSipInfos( const QList& sipInfos ) { - sipPlugin()->sendSipInfo( weakRef().toStrongRef(), sipInfo ); + sipPlugin()->sendSipInfos( weakRef().toStrongRef(), sipInfos ); } @@ -228,6 +229,23 @@ PeerInfo::contactId() const return m_contactId; } +const QString +PeerInfo::nodeId() const +{ + Q_ASSERT( !m_sipInfos.isEmpty() ); + // All sip infos share the same nodeId + return m_sipInfos.first().nodeId(); +} + +const QString +PeerInfo::key() const +{ + Q_ASSERT( !m_sipInfos.isEmpty() ); + // All sip infos share the same key + return m_sipInfos.first().key(); +} + + void PeerInfo::setStatus( PeerInfo::Status status ) @@ -259,22 +277,19 @@ PeerInfo::status() const void -PeerInfo::setSipInfo( const SipInfo& sipInfo ) +PeerInfo::setSipInfos( const QList& sipInfos ) { - if ( sipInfo == m_sipInfo ) - return; + m_sipInfos = sipInfos; - m_sipInfo = sipInfo; - - tLog() << "id:" << id() << "info changed" << sipInfo; + tLog() << "id:" << id() << "info changed" << sipInfos; emit sipInfoChanged(); } -const SipInfo -PeerInfo::sipInfo() const +const QList +PeerInfo::sipInfos() const { - return m_sipInfo; + return m_sipInfos; } @@ -389,7 +404,6 @@ PeerInfo::setData( const QVariant& data ) m_data = data; } - const QVariant PeerInfo::data() const { diff --git a/src/libtomahawk/sip/PeerInfo.h b/src/libtomahawk/sip/PeerInfo.h index 2b49548b3..74fd52c7c 100644 --- a/src/libtomahawk/sip/PeerInfo.h +++ b/src/libtomahawk/sip/PeerInfo.h @@ -74,7 +74,7 @@ public: const QString id() const; SipPlugin* sipPlugin() const; const QString debugName() const; - void sendLocalSipInfo( const SipInfo& sipInfo ); + void sendLocalSipInfos( const QList& sipInfos ); QWeakPointer< Tomahawk::PeerInfo > weakRef(); void setWeakRef( QWeakPointer< Tomahawk::PeerInfo > weakRef ); @@ -96,8 +96,8 @@ public: void setStatus( Status status ); Status status() const; - void setSipInfo( const SipInfo& sipInfo ); - const SipInfo sipInfo() const; + void setSipInfos( const QList& sipInfos ); + const QList sipInfos() const; void setFriendlyName( const QString& friendlyName ); const QString friendlyName() const; @@ -112,6 +112,16 @@ public: void setData( const QVariant& data ); const QVariant data() const; + /** + * Get the node id of this peer + */ + const QString nodeId() const; + + /** + * Get the authentication key for this host + */ + const QString key() const; + signals: void sipInfoChanged(); @@ -131,7 +141,7 @@ private: QString m_id; QString m_contactId; Status m_status; - SipInfo m_sipInfo; + QList m_sipInfos; QString m_friendlyName; QString m_versionString; QVariant m_data; diff --git a/src/libtomahawk/sip/SipInfo.cpp b/src/libtomahawk/sip/SipInfo.cpp index 016289602..d633cf1ef 100644 --- a/src/libtomahawk/sip/SipInfo.cpp +++ b/src/libtomahawk/sip/SipInfo.cpp @@ -121,7 +121,6 @@ bool SipInfo::isVisible() const { Q_ASSERT( isValid() ); - return d->visible.toBool(); } @@ -137,7 +136,6 @@ const QString SipInfo::host() const { Q_ASSERT( isValid() ); - return d->host; } @@ -153,7 +151,6 @@ int SipInfo::port() const { Q_ASSERT( isValid() ); - return d->port; } @@ -169,7 +166,6 @@ const QString SipInfo::nodeId() const { Q_ASSERT( isValid() ); - return d->nodeId; } @@ -185,7 +181,6 @@ const QString SipInfo::key() const { Q_ASSERT( isValid() ); - return d->key; } diff --git a/src/libtomahawk/sip/SipPlugin.h b/src/libtomahawk/sip/SipPlugin.h index 6503c282e..caa23877e 100644 --- a/src/libtomahawk/sip/SipPlugin.h +++ b/src/libtomahawk/sip/SipPlugin.h @@ -70,7 +70,11 @@ public slots: virtual void configurationChanged() = 0; virtual void addContact( const QString& peerId, const QString& msg = QString() ) = 0; - virtual void sendSipInfo( const Tomahawk::peerinfo_ptr& receiver, const SipInfo& info ) = 0; + + /** + * Send a list of SipInfos to all contacts. + */ + virtual void sendSipInfos( const Tomahawk::peerinfo_ptr& receiver, const QList& infos ) = 0; signals: void peerStatusChanged( const Tomahawk::peerinfo_ptr& ); diff --git a/src/tomahawk/DiagnosticsDialog.cpp b/src/tomahawk/DiagnosticsDialog.cpp index 77efc0a72..4091a7af0 100644 --- a/src/tomahawk/DiagnosticsDialog.cpp +++ b/src/tomahawk/DiagnosticsDialog.cpp @@ -2,6 +2,7 @@ * * Copyright 2011, Dominik Schmidt * Copyright 2011, Jeff Mitchell + * Copyright 2013, Uwe L. Korn * * Tomahawk is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -64,24 +65,26 @@ DiagnosticsDialog::updateLogView() log.append( QString( "TOMAHAWK DIAGNOSTICS LOG -%1 \n\n" ).arg( QDateTime::currentDateTime().toString() ) ); log.append( "TOMAHAWK-VERSION: " TOMAHAWK_VERSION "\n" ); log.append( "PLATFORM: " TOMAHAWK_SYSTEM "\n\n"); - log.append( "NETWORK:\n General:\n" ); + log.append( "NETWORK:\n Listening to:\n" ); if ( Servent::instance()->visibleExternally() ) { - log.append( - QString( - " visible: true\n" - " host: %1\n" - " port: %2\n" - "\n" - ).arg( Servent::instance()->externalAddress() ) - .arg( Servent::instance()->externalPort() ) + foreach ( QHostAddress ha, Servent::instance()->addresses() ) + { + if ( ha.protocol() == QAbstractSocket::IPv6Protocol ) + log.append( QString( " [%1]:%2\n" ).arg( ha.toString() ).arg( Servent::instance()->port() ) ); + else + log.append( QString( " %1:%2\n" ).arg( ha.toString() ).arg( Servent::instance()->port() ) ); + } + if ( !Servent::instance()->additionalAddress().isNull() ) + { + log.append( QString( " [%1]:%2\n" ).arg( Servent::instance()->additionalAddress() ).arg( Servent::instance()->additionalPort() ) ); + } - ); } else { - log.append( " visible: false\n" ); + log.append( " not listening to any interface, outgoing connections only\n" ); } log.append( "\n\nINFOPLUGINS:\n" ); @@ -162,54 +165,17 @@ DiagnosticsDialog::accountLog( Tomahawk::Accounts::Account* account ) foreach( const Tomahawk::peerinfo_ptr& peerInfo, account->sipPlugin()->peersOnline() ) { - QString peerId = peerInfo->id(); - QString versionString = peerInfo->versionString(); - SipInfo sipInfo = peerInfo->sipInfo(); - if ( !sipInfo.isValid() ) + accountInfo.append( QString( " %1: " ).arg( peerInfo->id() ) ); + foreach ( SipInfo info, peerInfo->sipInfos() ) { - accountInfo.append( - QString(" %1: %2 %3" /*"(%4)"*/) - .arg( peerInfo->id() ) - .arg( "sipinfo invalid" ) - .arg( versionString ) - // .arg( connected ? "connected" : "not connected") - ); - } - else if ( sipInfo.isVisible() ) - { - accountInfo.append( - QString(" %1: %2:%3 %4" /*" (%5)"*/) - .arg( peerId ) - .arg( sipInfo.host() ) - .arg( sipInfo.port() ) - .arg( versionString ) - // .arg( connected ? "connected" : "not connected") - ); - } - else - { - accountInfo.append( - QString(" %1: visible: false %2" /*" (%3)"*/) - .arg( peerId ) - .arg( versionString ) - // .arg( connected ? "connected" : "not connected") - ); - } - - if( sipInfo.isValid() ) - { - if( !Servent::instance()->visibleExternally() || - Servent::instance()->externalAddress() < sipInfo.host() || - ( Servent::instance()->externalAddress() == sipInfo.host() && Servent::instance()->externalPort() < sipInfo.port() ) ) - { - accountInfo.append(" (outbound)"); - } + if ( info.isValid() ) + accountInfo.append( QString( "[%1]:%2; " ).arg( info.host() ).arg( info.port() ) ); else - { - accountInfo.append(" (inbound)"); - } + accountInfo.append( "SipInfo invalid; " ); } - accountInfo.append("\n"); + if ( ( ( peerInfo->sipInfos().length() == 1 ) && ( !peerInfo->sipInfos().first().isVisible() ) ) || ( peerInfo->sipInfos().isEmpty() ) ) + accountInfo.append( "(outbound connections only) "); + accountInfo.append( QString( " (%1)\n" ).arg( peerInfo->versionString() ) ); } accountInfo.append( "\n" ); diff --git a/src/tomahawk/Settings_Advanced.ui b/src/tomahawk/Settings_Advanced.ui index 08636c286..5b50eda48 100644 --- a/src/tomahawk/Settings_Advanced.ui +++ b/src/tomahawk/Settings_Advanced.ui @@ -32,7 +32,7 @@ - None (outgoing connections only) + Active (your host needs to be directly reachable) diff --git a/src/tomahawk/TomahawkApp.cpp b/src/tomahawk/TomahawkApp.cpp index 06f4203bd..4f1c6d558 100644 --- a/src/tomahawk/TomahawkApp.cpp +++ b/src/tomahawk/TomahawkApp.cpp @@ -610,7 +610,7 @@ TomahawkApp::initServent() bool upnp = !arguments().contains( "--noupnp" ); int port = TomahawkSettings::instance()->externalPort(); - if ( !Servent::instance()->startListening( QHostAddress( QHostAddress::Any ), upnp, port ) ) + if ( !Servent::instance()->startListening( QHostAddress( QHostAddress::AnyIPv6 ), upnp, port ) ) { tLog() << "Failed to start listening with servent"; exit( 1 );