diff --git a/src/base/bittorrent/peerinfo.cpp b/src/base/bittorrent/peerinfo.cpp index abbe45df5..857424aee 100644 --- a/src/base/bittorrent/peerinfo.cpp +++ b/src/base/bittorrent/peerinfo.cpp @@ -29,6 +29,7 @@ #include "base/net/geoipmanager.h" #include "base/utils/string.h" #include "base/unicodestrings.h" +#include "base/bittorrent/torrenthandle.h" #include "peerinfo.h" namespace libt = libtorrent; @@ -49,9 +50,11 @@ PeerAddress::PeerAddress(QHostAddress ip, ushort port) // PeerInfo -PeerInfo::PeerInfo(const libt::peer_info &nativeInfo) +PeerInfo::PeerInfo(const TorrentHandle *torrent, const libt::peer_info &nativeInfo) : m_nativeInfo(nativeInfo) { + calcRelevance(torrent); + determineFlags(); } bool PeerInfo::fromDHT() const @@ -253,3 +256,155 @@ QString PeerInfo::connectionType() const return connection; } + +void PeerInfo::calcRelevance(const TorrentHandle *torrent) +{ + const QBitArray &allPieces = torrent->pieces(); + const QBitArray &peerPieces = pieces(); + + int localMissing = 0; + int remoteHaves = 0; + + for (int i = 0; i < allPieces.size(); ++i) { + if (!allPieces[i]) { + ++localMissing; + if (peerPieces[i]) + ++remoteHaves; + } + } + + if (localMissing == 0) + m_relevance = 0.0; + else + m_relevance = static_cast(remoteHaves) / localMissing; +} + +qreal PeerInfo::relevance() const +{ + return m_relevance; +} + +void PeerInfo::determineFlags() +{ + if (isInteresting()) { + //d = Your client wants to download, but peer doesn't want to send (interested and choked) + if (isRemoteChocked()) { + m_flags += "d "; + m_flagsDescription += tr("interested(local) and choked(peer)"); + m_flagsDescription += ", "; + } + else { + //D = Currently downloading (interested and not choked) + m_flags += "D "; + m_flagsDescription += tr("interested(local) and unchoked(peer)"); + m_flagsDescription += ", "; + } + } + + if (isRemoteInterested()) { + //u = Peer wants your client to upload, but your client doesn't want to (interested and choked) + if (isChocked()) { + m_flags += "u "; + m_flagsDescription += tr("interested(peer) and choked(local)"); + m_flagsDescription += ", "; + } + else { + //U = Currently uploading (interested and not choked) + m_flags += "U "; + m_flagsDescription += tr("interested(peer) and unchoked(local)"); + m_flagsDescription += ", "; + } + } + + //O = Optimistic unchoke + if (optimisticUnchoke()) { + m_flags += "O "; + m_flagsDescription += tr("optimistic unchoke"); + m_flagsDescription += ", "; + } + + //S = Peer is snubbed + if (isSnubbed()) { + m_flags += "S "; + m_flagsDescription += tr("peer snubbed"); + m_flagsDescription += ", "; + } + + //I = Peer is an incoming connection + if (!isLocalConnection()) { + m_flags += "I "; + m_flagsDescription += tr("incoming connection"); + m_flagsDescription += ", "; + } + + //K = Peer is unchoking your client, but your client is not interested + if (!isRemoteChocked() && !isInteresting()) { + m_flags += "K "; + m_flagsDescription += tr("not interested(local) and unchoked(peer)"); + m_flagsDescription += ", "; + } + + //? = Your client unchoked the peer but the peer is not interested + if (!isChocked() && !isRemoteInterested()) { + m_flags += "? "; + m_flagsDescription += tr("not interested(peer) and unchoked(local)"); + m_flagsDescription += ", "; + } + + //X = Peer was included in peerlists obtained through Peer Exchange (PEX) + if (fromPeX()) { + m_flags += "X "; + m_flagsDescription += tr("peer from PEX"); + m_flagsDescription += ", "; + } + + //H = Peer was obtained through DHT + if (fromDHT()) { + m_flags += "H "; + m_flagsDescription += tr("peer from DHT"); + m_flagsDescription += ", "; + } + + //E = Peer is using Protocol Encryption (all traffic) + if (isRC4Encrypted()) { + m_flags += "E "; + m_flagsDescription += tr("encrypted traffic"); + m_flagsDescription += ", "; + } + + //e = Peer is using Protocol Encryption (handshake) + if (isPlaintextEncrypted()) { + m_flags += "e "; + m_flagsDescription += tr("encrypted handshake"); + m_flagsDescription += ", "; + } + + //P = Peer is using uTorrent uTP + + if (useUTPSocket()) { + m_flags += "P "; + m_flagsDescription += QString::fromUtf8(C_UTP); + m_flagsDescription += ", "; + } + + //L = Peer is local + if (fromLSD()) { + m_flags += "L"; + m_flagsDescription += tr("peer from LSD"); + } + + m_flags = m_flags.trimmed(); + m_flagsDescription = m_flagsDescription.trimmed(); + if (m_flagsDescription.endsWith(',', Qt::CaseInsensitive)) + m_flagsDescription.chop(1); +} + +QString PeerInfo::flags() const +{ + return m_flags; +} + +QString PeerInfo::flagsDescription() const +{ + return m_flagsDescription; +} diff --git a/src/base/bittorrent/peerinfo.h b/src/base/bittorrent/peerinfo.h index 48922ec8e..ae63e2d54 100644 --- a/src/base/bittorrent/peerinfo.h +++ b/src/base/bittorrent/peerinfo.h @@ -33,9 +33,12 @@ #include #include +#include namespace BitTorrent { + class TorrentHandle; + struct PeerAddress { QHostAddress ip; @@ -47,8 +50,10 @@ namespace BitTorrent class PeerInfo { + Q_DECLARE_TR_FUNCTIONS(PeerInfo) + public: - PeerInfo(const libtorrent::peer_info &nativeInfo); + PeerInfo(const TorrentHandle *torrent, const libtorrent::peer_info &nativeInfo); bool fromDHT() const; bool fromPeX() const; @@ -89,12 +94,21 @@ namespace BitTorrent qlonglong totalDownload() const; QBitArray pieces() const; QString connectionType() const; + qreal relevance() const; + QString flags() const; + QString flagsDescription() const; #ifndef DISABLE_COUNTRIES_RESOLUTION QString country() const; #endif private: + void calcRelevance(const TorrentHandle *torrent); + void determineFlags(); + libtorrent::peer_info m_nativeInfo; + qreal m_relevance; + QString m_flags; + QString m_flagsDescription; }; } diff --git a/src/base/bittorrent/torrenthandle.cpp b/src/base/bittorrent/torrenthandle.cpp index b6e6f4e0e..de6ca9c5e 100644 --- a/src/base/bittorrent/torrenthandle.cpp +++ b/src/base/bittorrent/torrenthandle.cpp @@ -960,7 +960,7 @@ QList TorrentHandle::peers() const SAFE_CALL(get_peer_info, nativePeers); foreach (const libt::peer_info &peer, nativePeers) - peers << peer; + peers << PeerInfo(this, peer); return peers; } diff --git a/src/gui/properties/peerlistwidget.cpp b/src/gui/properties/peerlistwidget.cpp index d1c25072f..14729cb38 100644 --- a/src/gui/properties/peerlistwidget.cpp +++ b/src/gui/properties/peerlistwidget.cpp @@ -328,17 +328,15 @@ QStandardItem* PeerListWidget::addPeer(const QString& ip, BitTorrent::TorrentHan } } m_listModel->setData(m_listModel->index(row, PeerListDelegate::CONNECTION), peer.connectionType()); - QString flags, tooltip; - getFlags(peer, flags, tooltip); - m_listModel->setData(m_listModel->index(row, PeerListDelegate::FLAGS), flags); - m_listModel->setData(m_listModel->index(row, PeerListDelegate::FLAGS), tooltip, Qt::ToolTipRole); + m_listModel->setData(m_listModel->index(row, PeerListDelegate::FLAGS), peer.flags()); + m_listModel->setData(m_listModel->index(row, PeerListDelegate::FLAGS), peer.flagsDescription(), Qt::ToolTipRole); m_listModel->setData(m_listModel->index(row, PeerListDelegate::CLIENT), peer.client()); m_listModel->setData(m_listModel->index(row, PeerListDelegate::PROGRESS), peer.progress()); m_listModel->setData(m_listModel->index(row, PeerListDelegate::DOWN_SPEED), peer.payloadDownSpeed()); m_listModel->setData(m_listModel->index(row, PeerListDelegate::UP_SPEED), peer.payloadUpSpeed()); m_listModel->setData(m_listModel->index(row, PeerListDelegate::TOT_DOWN), peer.totalDownload()); m_listModel->setData(m_listModel->index(row, PeerListDelegate::TOT_UP), peer.totalUpload()); - m_listModel->setData(m_listModel->index(row, PeerListDelegate::RELEVANCE), getPeerRelevance(torrent->pieces(), peer.pieces())); + m_listModel->setData(m_listModel->index(row, PeerListDelegate::RELEVANCE), peer.relevance()); return m_listModel->item(row, PeerListDelegate::IP); } @@ -356,18 +354,16 @@ void PeerListWidget::updatePeer(const QString &ip, BitTorrent::TorrentHandle *co } } m_listModel->setData(m_listModel->index(row, PeerListDelegate::CONNECTION), peer.connectionType()); - QString flags, tooltip; - getFlags(peer, flags, tooltip); m_listModel->setData(m_listModel->index(row, PeerListDelegate::PORT), peer.address().port); - m_listModel->setData(m_listModel->index(row, PeerListDelegate::FLAGS), flags); - m_listModel->setData(m_listModel->index(row, PeerListDelegate::FLAGS), tooltip, Qt::ToolTipRole); + m_listModel->setData(m_listModel->index(row, PeerListDelegate::FLAGS), peer.flags()); + m_listModel->setData(m_listModel->index(row, PeerListDelegate::FLAGS), peer.flagsDescription(), Qt::ToolTipRole); m_listModel->setData(m_listModel->index(row, PeerListDelegate::CLIENT), peer.client()); m_listModel->setData(m_listModel->index(row, PeerListDelegate::PROGRESS), peer.progress()); m_listModel->setData(m_listModel->index(row, PeerListDelegate::DOWN_SPEED), peer.payloadDownSpeed()); m_listModel->setData(m_listModel->index(row, PeerListDelegate::UP_SPEED), peer.payloadUpSpeed()); m_listModel->setData(m_listModel->index(row, PeerListDelegate::TOT_DOWN), peer.totalDownload()); m_listModel->setData(m_listModel->index(row, PeerListDelegate::TOT_UP), peer.totalUpload()); - m_listModel->setData(m_listModel->index(row, PeerListDelegate::RELEVANCE), getPeerRelevance(torrent->pieces(), peer.pieces())); + m_listModel->setData(m_listModel->index(row, PeerListDelegate::RELEVANCE), peer.relevance()); } void PeerListWidget::handleResolved(const QString &ip, const QString &hostname) @@ -390,136 +386,3 @@ void PeerListWidget::handleSortColumnChanged(int col) } } -void PeerListWidget::getFlags(const BitTorrent::PeerInfo &peer, QString& flags, QString& tooltip) -{ - if (peer.isInteresting()) { - //d = Your client wants to download, but peer doesn't want to send (interested and choked) - if (peer.isRemoteChocked()) { - flags += "d "; - tooltip += tr("interested(local) and choked(peer)"); - tooltip += ", "; - } - else { - //D = Currently downloading (interested and not choked) - flags += "D "; - tooltip += tr("interested(local) and unchoked(peer)"); - tooltip += ", "; - } - } - - if (peer.isRemoteInterested()) { - //u = Peer wants your client to upload, but your client doesn't want to (interested and choked) - if (peer.isChocked()) { - flags += "u "; - tooltip += tr("interested(peer) and choked(local)"); - tooltip += ", "; - } - else { - //U = Currently uploading (interested and not choked) - flags += "U "; - tooltip += tr("interested(peer) and unchoked(local)"); - tooltip += ", "; - } - } - - //O = Optimistic unchoke - if (peer.optimisticUnchoke()) { - flags += "O "; - tooltip += tr("optimistic unchoke"); - tooltip += ", "; - } - - //S = Peer is snubbed - if (peer.isSnubbed()) { - flags += "S "; - tooltip += tr("peer snubbed"); - tooltip += ", "; - } - - //I = Peer is an incoming connection - if (!peer.isLocalConnection()) { - flags += "I "; - tooltip += tr("incoming connection"); - tooltip += ", "; - } - - //K = Peer is unchoking your client, but your client is not interested - if (!peer.isRemoteChocked() && !peer.isInteresting()) { - flags += "K "; - tooltip += tr("not interested(local) and unchoked(peer)"); - tooltip += ", "; - } - - //? = Your client unchoked the peer but the peer is not interested - if (!peer.isChocked() && !peer.isRemoteInterested()) { - flags += "? "; - tooltip += tr("not interested(peer) and unchoked(local)"); - tooltip += ", "; - } - - //X = Peer was included in peerlists obtained through Peer Exchange (PEX) - if (peer.fromPeX()) { - flags += "X "; - tooltip += tr("peer from PEX"); - tooltip += ", "; - } - - //H = Peer was obtained through DHT - if (peer.fromDHT()) { - flags += "H "; - tooltip += tr("peer from DHT"); - tooltip += ", "; - } - - //E = Peer is using Protocol Encryption (all traffic) - if (peer.isRC4Encrypted()) { - flags += "E "; - tooltip += tr("encrypted traffic"); - tooltip += ", "; - } - - //e = Peer is using Protocol Encryption (handshake) - if (peer.isPlaintextEncrypted()) { - flags += "e "; - tooltip += tr("encrypted handshake"); - tooltip += ", "; - } - - //P = Peer is using uTorrent uTP - - if (peer.useUTPSocket()) { - flags += "P "; - tooltip += QString::fromUtf8(C_UTP); - tooltip += ", "; - } - - //L = Peer is local - if (peer.fromLSD()) { - flags += "L"; - tooltip += tr("peer from LSD"); - } - - flags = flags.trimmed(); - tooltip = tooltip.trimmed(); - if (tooltip.endsWith(',', Qt::CaseInsensitive)) - tooltip.chop(1); -} - -qreal PeerListWidget::getPeerRelevance(const QBitArray &allPieces, const QBitArray &peerPieces) -{ - int localMissing = 0; - int remoteHaves = 0; - - for (int i = 0; i < allPieces.size(); ++i) { - if (!allPieces[i]) { - ++localMissing; - if (peerPieces[i]) - ++remoteHaves; - } - } - - if (localMissing == 0) - return 0.0; - - return static_cast(remoteHaves) / localMissing; -} diff --git a/src/gui/properties/peerlistwidget.h b/src/gui/properties/peerlistwidget.h index 6f597ad24..c7ec81ad7 100644 --- a/src/gui/properties/peerlistwidget.h +++ b/src/gui/properties/peerlistwidget.h @@ -83,10 +83,6 @@ private slots: void copySelectedPeers(); void handleSortColumnChanged(int col); -private: - static void getFlags(const BitTorrent::PeerInfo &peer, QString &flags, QString &tooltip); - qreal getPeerRelevance(const QBitArray &allPieces, const QBitArray &peerPieces); - private: QStandardItemModel *m_listModel; PeerListDelegate *m_listDelegate; diff --git a/src/webui/btjson.cpp b/src/webui/btjson.cpp index 51e779f37..0702f39d5 100644 --- a/src/webui/btjson.cpp +++ b/src/webui/btjson.cpp @@ -36,7 +36,9 @@ #include "base/bittorrent/sessionstatus.h" #include "base/bittorrent/torrenthandle.h" #include "base/bittorrent/trackerentry.h" +#include "base/bittorrent/peerinfo.h" #include "base/torrentfilter.h" +#include "base/net/geoipmanager.h" #include "jsonutils.h" #include @@ -105,6 +107,22 @@ static const char KEY_TORRENT_SUPER_SEEDING[] = "super_seeding"; static const char KEY_TORRENT_FORCE_START[] = "force_start"; static const char KEY_TORRENT_SAVE_PATH[] = "save_path"; +// Peer keys +static const char KEY_PEER_IP[] = "ip"; +static const char KEY_PEER_PORT[] = "port"; +static const char KEY_PEER_COUNTRY_CODE[] = "country_code"; +static const char KEY_PEER_COUNTRY[] = "country"; +static const char KEY_PEER_CLIENT[] = "client"; +static const char KEY_PEER_PROGRESS[] = "progress"; +static const char KEY_PEER_DOWN_SPEED[] = "dl_speed"; +static const char KEY_PEER_UP_SPEED[] = "up_speed"; +static const char KEY_PEER_TOT_DOWN[] = "downloaded"; +static const char KEY_PEER_TOT_UP[] = "uploaded"; +static const char KEY_PEER_CONNECTION_TYPE[] = "connection"; +static const char KEY_PEER_FLAGS[] = "flags"; +static const char KEY_PEER_FLAGS_DESCRIPTION[] = "flags_desc"; +static const char KEY_PEER_RELEVANCE[] = "relevance"; + // Tracker keys static const char KEY_TRACKER_URL[] = "url"; static const char KEY_TRACKER_STATUS[] = "status"; @@ -171,6 +189,9 @@ static const char KEY_SYNC_MAINDATA_QUEUEING[] = "queueing"; static const char KEY_SYNC_MAINDATA_USE_ALT_SPEED_LIMITS[] = "use_alt_speed_limits"; static const char KEY_SYNC_MAINDATA_REFRESH_INTERVAL[] = "refresh_interval"; +// Sync torrent peers keys +static const char KEY_SYNC_TORRENT_PEERS_SHOW_FLAGS[] = "show_flags"; + static const char KEY_FULL_UPDATE[] = "full_update"; static const char KEY_RESPONSE_ID[] = "rid"; static const char KEY_SUFFIX_REMOVED[] = "_removed"; @@ -354,6 +375,54 @@ QByteArray btjson::getSyncMainData(int acceptedResponseId, QVariantMap &lastData return json::toJson(generateSyncData(acceptedResponseId, data, lastAcceptedData, lastData)); } +QByteArray btjson::getSyncTorrentPeersData(int acceptedResponseId, QString hash, QVariantMap &lastData, QVariantMap &lastAcceptedData) +{ + BitTorrent::TorrentHandle *const torrent = BitTorrent::Session::instance()->findTorrent(hash); + if (!torrent) { + qWarning() << Q_FUNC_INFO << "Invalid torrent " << qPrintable(hash); + return QByteArray(); + } + + QVariantMap data; + QVariantHash peers; + QList peersList = torrent->peers(); +#ifndef DISABLE_COUNTRIES_RESOLUTION + bool resolvePeerCountries = Preferences::instance()->resolvePeerCountries(); +#else + bool resolvePeerCountries = false; +#endif + + data[KEY_SYNC_TORRENT_PEERS_SHOW_FLAGS] = resolvePeerCountries; + + foreach (const BitTorrent::PeerInfo &pi, peersList) { + if (pi.address().ip.isNull()) continue; + QVariantMap peer; +#ifndef DISABLE_COUNTRIES_RESOLUTION + if (resolvePeerCountries) { + peer[KEY_PEER_COUNTRY_CODE] = pi.country().toLower(); + peer[KEY_PEER_COUNTRY] = Net::GeoIPManager::CountryName(pi.country()); + } +#endif + peer[KEY_PEER_IP] = pi.address().ip.toString(); + peer[KEY_PEER_PORT] = pi.address().port; + peer[KEY_PEER_CLIENT] = pi.client(); + peer[KEY_PEER_PROGRESS] = pi.progress(); + peer[KEY_PEER_DOWN_SPEED] = pi.payloadDownSpeed(); + peer[KEY_PEER_UP_SPEED] = pi.payloadUpSpeed(); + peer[KEY_PEER_TOT_DOWN] = pi.totalDownload(); + peer[KEY_PEER_TOT_UP] = pi.totalUpload(); + peer[KEY_PEER_CONNECTION_TYPE] = pi.connectionType(); + peer[KEY_PEER_FLAGS] = pi.flags(); + peer[KEY_PEER_FLAGS_DESCRIPTION] = pi.flagsDescription(); + peer[KEY_PEER_RELEVANCE] = pi.relevance(); + peers[pi.address().ip.toString() + ":" + QString::number(pi.address().port)] = peer; + } + + data["peers"] = peers; + + return json::toJson(generateSyncData(acceptedResponseId, data, lastAcceptedData, lastData)); +} + /** * Returns the trackers for a torrent in JSON format. * @@ -610,7 +679,7 @@ QByteArray btjson::getTorrentsRatesLimits(QStringList &hashes, bool downloadLimi map[hash] = limit; } - return json::toJson(map); + return json::toJson(map); } QVariantMap toMap(BitTorrent::TorrentHandle *const torrent) diff --git a/src/webui/btjson.h b/src/webui/btjson.h index 246d1eb86..bada11240 100644 --- a/src/webui/btjson.h +++ b/src/webui/btjson.h @@ -46,6 +46,7 @@ public: static QByteArray getTorrents(QString filter = "all", QString label = QString(), QString sortedColumn = "name", bool reverse = false, int limit = 0, int offset = 0); static QByteArray getSyncMainData(int acceptedResponseId, QVariantMap &lastData, QVariantMap &lastAcceptedData); + static QByteArray getSyncTorrentPeersData(int acceptedResponseId, QString hash, QVariantMap &lastData, QVariantMap &lastAcceptedData); static QByteArray getTrackersForTorrent(const QString& hash); static QByteArray getWebSeedsForTorrent(const QString& hash); static QByteArray getPropertiesForTorrent(const QString& hash); diff --git a/src/webui/webapplication.cpp b/src/webui/webapplication.cpp index d5a37a086..38e58e39d 100644 --- a/src/webui/webapplication.cpp +++ b/src/webui/webapplication.cpp @@ -82,6 +82,7 @@ QMap > WebApplication::initialize ADD_ACTION(query, propertiesWebSeeds); ADD_ACTION(query, propertiesFiles); ADD_ACTION(sync, maindata); + ADD_ACTION(sync, torrent_peers); ADD_ACTION(command, shutdown); ADD_ACTION(command, download); ADD_ACTION(command, upload); @@ -277,6 +278,19 @@ void WebApplication::action_sync_maindata() session()->syncMainDataLastAcceptedResponse), Http::CONTENT_TYPE_JSON); } +// GET param: +// - hash (string): torrent hash +// - rid (int): last response id +void WebApplication::action_sync_torrent_peers() +{ + CHECK_URI(0); + print(btjson::getSyncTorrentPeersData(request().gets["rid"].toInt(), + request().gets["hash"], + session()->syncTorrentPeersLastResponse, + session()->syncTorrentPeersLastAcceptedResponse), Http::CONTENT_TYPE_JSON); +} + + void WebApplication::action_version_api() { CHECK_URI(0); diff --git a/src/webui/webapplication.h b/src/webui/webapplication.h index 9696c8e09..8cf8fac8e 100644 --- a/src/webui/webapplication.h +++ b/src/webui/webapplication.h @@ -55,6 +55,7 @@ private: void action_query_propertiesWebSeeds(); void action_query_propertiesFiles(); void action_sync_maindata(); + void action_sync_torrent_peers(); void action_command_shutdown(); void action_command_download(); void action_command_upload(); diff --git a/src/webui/websessiondata.h b/src/webui/websessiondata.h index fe7b4eeec..01dd6ab57 100644 --- a/src/webui/websessiondata.h +++ b/src/webui/websessiondata.h @@ -35,6 +35,8 @@ struct WebSessionData { QVariantMap syncMainDataLastResponse; QVariantMap syncMainDataLastAcceptedResponse; + QVariantMap syncTorrentPeersLastResponse; + QVariantMap syncTorrentPeersLastAcceptedResponse; }; #endif // WEBSESSIONDATA