Add (experimental) I2P support

PR #18717.
Closes #16257.
This commit is contained in:
Vladimir Golovnev
2023-03-21 08:33:46 +03:00
committed by GitHub
parent 8cbe4a571c
commit cdded6cef7
8 changed files with 295 additions and 72 deletions

View File

@@ -1,5 +1,6 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2023 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
*
* This program is free software; you can redistribute it and/or
@@ -85,6 +86,21 @@ uint qHash(const PeerEndpoint &peerEndpoint, const uint seed = 0)
}
#endif
namespace
{
void setModelData(QStandardItemModel *model, const int row, const int column, const QString &displayData
, const QVariant &underlyingData, const Qt::Alignment textAlignmentData = {}, const QString &toolTip = {})
{
const QMap<int, QVariant> data = {
{Qt::DisplayRole, displayData},
{PeerListSortModel::UnderlyingDataRole, underlyingData},
{Qt::TextAlignmentRole, QVariant {textAlignmentData}},
{Qt::ToolTipRole, toolTip}};
model->setItemData(model->index(row, column), data);
}
}
PeerListWidget::PeerListWidget(PropertiesWidget *parent)
: QTreeView(parent)
, m_properties(parent)
@@ -404,23 +420,58 @@ void PeerListWidget::loadPeers(const BitTorrent::Torrent *torrent)
if (torrent != m_properties->getCurrentTorrent())
return;
// Remove I2P peers since they will be completely reloaded.
for (QStandardItem *item : asConst(m_I2PPeerItems))
m_listModel->removeRow(item->row());
m_I2PPeerItems.clear();
QSet<PeerEndpoint> existingPeers;
existingPeers.reserve(m_peerItems.size());
for (auto i = m_peerItems.cbegin(); i != m_peerItems.cend(); ++i)
existingPeers.insert(i.key());
const bool hideZeroValues = Preferences::instance()->getHideZeroValues();
for (const BitTorrent::PeerInfo &peer : peers)
{
if (peer.address().ip.isNull())
continue;
bool isNewPeer = false;
updatePeer(torrent, peer, isNewPeer);
if (!isNewPeer)
const PeerEndpoint peerEndpoint {peer.address(), peer.connectionType()};
auto itemIter = m_peerItems.find(peerEndpoint);
const bool isNewPeer = (itemIter == m_peerItems.end());
const int row = isNewPeer ? m_listModel->rowCount() : (*itemIter)->row();
if (isNewPeer)
{
m_listModel->insertRow(row);
const bool useI2PSocket = peer.useI2PSocket();
const QString peerIPString = useI2PSocket ? tr("N/A") : peerEndpoint.address.ip.toString();
setModelData(m_listModel, row, PeerListColumns::IP, peerIPString, peerIPString, {}, peerIPString);
const QString peerIPHiddenString = useI2PSocket ? QString() : peerEndpoint.address.ip.toString();
setModelData(m_listModel, row, PeerListColumns::IP_HIDDEN, peerIPHiddenString, peerIPHiddenString);
const QString peerPortString = useI2PSocket ? tr("N/A") : QString::number(peer.address().port);
setModelData(m_listModel, row, PeerListColumns::PORT, peerPortString, peer.address().port, (Qt::AlignRight | Qt::AlignVCenter));
if (useI2PSocket)
{
m_I2PPeerItems.append(m_listModel->item(row, PeerListColumns::IP));
}
else
{
itemIter = m_peerItems.insert(peerEndpoint, m_listModel->item(row, PeerListColumns::IP));
m_itemsByIP[peerEndpoint.address.ip].insert(itemIter.value());
}
}
else
{
const PeerEndpoint peerEndpoint {peer.address(), peer.connectionType()};
existingPeers.remove(peerEndpoint);
}
updatePeer(row, torrent, peer, hideZeroValues);
}
// Remove peers that are gone
@@ -438,73 +489,51 @@ void PeerListWidget::loadPeers(const BitTorrent::Torrent *torrent)
});
}
void PeerListWidget::updatePeer(const BitTorrent::Torrent *torrent, const BitTorrent::PeerInfo &peer, bool &isNewPeer)
void PeerListWidget::updatePeer(const int row, const BitTorrent::Torrent *torrent, const BitTorrent::PeerInfo &peer, const bool hideZeroValues)
{
const PeerEndpoint peerEndpoint {peer.address(), peer.connectionType()};
const QString peerIp = peerEndpoint.address.ip.toString();
const Qt::Alignment intDataTextAlignment = Qt::AlignRight | Qt::AlignVCenter;
const auto setModelData =
[this](const int row, const int column, const QString &displayData
, const QVariant &underlyingData, const Qt::Alignment textAlignmentData = {}
, const QString &toolTip = {})
{
const QMap<int, QVariant> data =
{
{Qt::DisplayRole, displayData},
{PeerListSortModel::UnderlyingDataRole, underlyingData},
{Qt::TextAlignmentRole, QVariant {textAlignmentData}},
{Qt::ToolTipRole, toolTip}
};
m_listModel->setItemData(m_listModel->index(row, column), data);
};
auto itemIter = m_peerItems.find(peerEndpoint);
isNewPeer = (itemIter == m_peerItems.end());
if (isNewPeer)
{
// new item
const int row = m_listModel->rowCount();
m_listModel->insertRow(row);
setModelData(row, PeerListColumns::IP, peerIp, peerIp, {}, peerIp);
setModelData(row, PeerListColumns::PORT, QString::number(peer.address().port), peer.address().port, intDataTextAlignment);
setModelData(row, PeerListColumns::IP_HIDDEN, peerIp, peerIp);
itemIter = m_peerItems.insert(peerEndpoint, m_listModel->item(row, PeerListColumns::IP));
m_itemsByIP[peerEndpoint.address.ip].insert(itemIter.value());
}
const int row = (*itemIter)->row();
const bool hideValues = Preferences::instance()->getHideZeroValues();
setModelData(row, PeerListColumns::CONNECTION, peer.connectionType(), peer.connectionType());
setModelData(row, PeerListColumns::FLAGS, peer.flags(), peer.flags(), {}, peer.flagsDescription());
const QString client = peer.client().toHtmlEscaped();
setModelData(row, PeerListColumns::CLIENT, client, client, {}, client);
setModelData(m_listModel, row, PeerListColumns::CLIENT, client, client, {}, client);
const QString peerIdClient = peer.peerIdClient().toHtmlEscaped();
setModelData(row, PeerListColumns::PEERID_CLIENT, peerIdClient, peerIdClient);
setModelData(row, PeerListColumns::PROGRESS, (Utils::String::fromDouble(peer.progress() * 100, 1) + u'%'), peer.progress(), intDataTextAlignment);
const QString downSpeed = (hideValues && (peer.payloadDownSpeed() <= 0)) ? QString {} : Utils::Misc::friendlyUnit(peer.payloadDownSpeed(), true);
setModelData(row, PeerListColumns::DOWN_SPEED, downSpeed, peer.payloadDownSpeed(), intDataTextAlignment);
const QString upSpeed = (hideValues && (peer.payloadUpSpeed() <= 0)) ? QString {} : Utils::Misc::friendlyUnit(peer.payloadUpSpeed(), true);
setModelData(row, PeerListColumns::UP_SPEED, upSpeed, peer.payloadUpSpeed(), intDataTextAlignment);
const QString totalDown = (hideValues && (peer.totalDownload() <= 0)) ? QString {} : Utils::Misc::friendlyUnit(peer.totalDownload());
setModelData(row, PeerListColumns::TOT_DOWN, totalDown, peer.totalDownload(), intDataTextAlignment);
const QString totalUp = (hideValues && (peer.totalUpload() <= 0)) ? QString {} : Utils::Misc::friendlyUnit(peer.totalUpload());
setModelData(row, PeerListColumns::TOT_UP, totalUp, peer.totalUpload(), intDataTextAlignment);
setModelData(row, PeerListColumns::RELEVANCE, (Utils::String::fromDouble(peer.relevance() * 100, 1) + u'%'), peer.relevance(), intDataTextAlignment);
setModelData(m_listModel, row, PeerListColumns::PEERID_CLIENT, peerIdClient, peerIdClient);
const QString downSpeed = (hideZeroValues && (peer.payloadDownSpeed() <= 0))
? QString() : Utils::Misc::friendlyUnit(peer.payloadDownSpeed(), true);
setModelData(m_listModel, row, PeerListColumns::DOWN_SPEED, downSpeed, peer.payloadDownSpeed(), intDataTextAlignment);
const QString upSpeed = (hideZeroValues && (peer.payloadUpSpeed() <= 0))
? QString() : Utils::Misc::friendlyUnit(peer.payloadUpSpeed(), true);
setModelData(m_listModel, row, PeerListColumns::UP_SPEED, upSpeed, peer.payloadUpSpeed(), intDataTextAlignment);
const QString totalDown = (hideZeroValues && (peer.totalDownload() <= 0))
? QString() : Utils::Misc::friendlyUnit(peer.totalDownload());
setModelData(m_listModel, row, PeerListColumns::TOT_DOWN, totalDown, peer.totalDownload(), intDataTextAlignment);
const QString totalUp = (hideZeroValues && (peer.totalUpload() <= 0))
? QString() : Utils::Misc::friendlyUnit(peer.totalUpload());
setModelData(m_listModel, row, PeerListColumns::TOT_UP, totalUp, peer.totalUpload(), intDataTextAlignment);
setModelData(m_listModel, row, PeerListColumns::CONNECTION, peer.connectionType(), peer.connectionType());
setModelData(m_listModel, row, PeerListColumns::FLAGS, peer.flags(), peer.flags(), {}, peer.flagsDescription());
setModelData(m_listModel, row, PeerListColumns::PROGRESS, (Utils::String::fromDouble(peer.progress() * 100, 1) + u'%')
, peer.progress(), intDataTextAlignment);
setModelData(m_listModel, row, PeerListColumns::RELEVANCE, (Utils::String::fromDouble(peer.relevance() * 100, 1) + u'%')
, peer.relevance(), intDataTextAlignment);
const PathList filePaths = torrent->info().filesForPiece(peer.downloadingPieceIndex());
QStringList downloadingFiles;
downloadingFiles.reserve(filePaths.size());
for (const Path &filePath : filePaths)
downloadingFiles.append(filePath.toString());
const QString downloadingFilesDisplayValue = downloadingFiles.join(u';');
setModelData(row, PeerListColumns::DOWNLOADING_PIECE, downloadingFilesDisplayValue, downloadingFilesDisplayValue, {}, downloadingFiles.join(u'\n'));
if (m_resolver)
m_resolver->resolve(peerEndpoint.address.ip);
const QString downloadingFilesDisplayValue = downloadingFiles.join(u';');
setModelData(m_listModel, row, PeerListColumns::DOWNLOADING_PIECE, downloadingFilesDisplayValue
, downloadingFilesDisplayValue, {}, downloadingFiles.join(u'\n'));
if (!peer.useI2PSocket() && m_resolver)
m_resolver->resolve(peer.address().ip);
if (m_resolveCountries)
{