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

@@ -782,6 +782,11 @@ void OptionsDialog::loadConnectionTabOptions()
m_ui->spinMaxUploadsPerTorrent->setEnabled(false);
}
m_ui->textI2PHost->setText(session->I2PAddress());
m_ui->spinI2PPort->setValue(session->I2PPort());
m_ui->checkI2PMixed->setChecked(session->I2PMixedMode());
m_ui->groupI2P->setChecked(session->isI2PEnabled());
const auto *proxyConfigManager = Net::ProxyConfigurationManager::instance();
const Net::ProxyConfiguration proxyConf = proxyConfigManager->proxyConfiguration();
@@ -831,6 +836,11 @@ void OptionsDialog::loadConnectionTabOptions()
connect(m_ui->textProxyIP, &QLineEdit::textChanged, this, &ThisType::enableApplyButton);
connect(m_ui->spinProxyPort, qSpinBoxValueChanged, this, &ThisType::enableApplyButton);
connect(m_ui->textI2PHost, &QLineEdit::textChanged, this, &ThisType::enableApplyButton);
connect(m_ui->spinI2PPort, qSpinBoxValueChanged, this, &ThisType::enableApplyButton);
connect(m_ui->checkI2PMixed, &QCheckBox::toggled, this, &ThisType::enableApplyButton);
connect(m_ui->groupI2P, &QGroupBox::toggled, this, &ThisType::enableApplyButton);
connect(m_ui->checkProxyBitTorrent, &QGroupBox::toggled, this, &ThisType::enableApplyButton);
connect(m_ui->checkProxyBitTorrent, &QGroupBox::toggled, this, &ThisType::adjustProxyOptions);
connect(m_ui->checkProxyPeerConnections, &QAbstractButton::toggled, this, &ThisType::enableApplyButton);
@@ -862,6 +872,11 @@ void OptionsDialog::saveConnectionTabOptions() const
session->setMaxUploads(getMaxUploads());
session->setMaxUploadsPerTorrent(getMaxUploadsPerTorrent());
session->setI2PEnabled(m_ui->groupI2P->isChecked());
session->setI2PAddress(m_ui->textI2PHost->text().trimmed());
session->setI2PPort(m_ui->spinI2PPort->value());
session->setI2PMixedMode(m_ui->checkI2PMixed->isChecked());
auto proxyConfigManager = Net::ProxyConfigurationManager::instance();
Net::ProxyConfiguration proxyConf;
proxyConf.type = getProxyType();

View File

@@ -1572,8 +1572,8 @@ readme[0-9].txt: filter 'readme1.txt', 'readme2.txt' but not 'readme10.txt'.</st
<rect>
<x>0</x>
<y>0</y>
<width>377</width>
<height>756</height>
<width>504</width>
<height>848</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout_20">
@@ -1788,6 +1788,78 @@ readme[0-9].txt: filter 'readme1.txt', 'readme2.txt' but not 'readme10.txt'.</st
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupI2P">
<property name="title">
<string>I2P (experimental)</string>
</property>
<property name="checkable">
<bool>true</bool>
</property>
<property name="checked">
<bool>true</bool>
</property>
<layout class="QVBoxLayout" name="verticalLayout_40">
<item>
<layout class="QHBoxLayout" name="i2pLayout">
<item>
<widget class="QLabel" name="labelI2PHost">
<property name="text">
<string>Host:</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="textI2PHost"/>
</item>
<item>
<widget class="QLabel" name="labelI2PPort">
<property name="text">
<string>Port:</string>
</property>
</widget>
</item>
<item>
<widget class="QSpinBox" name="spinI2PPort">
<property name="minimum">
<number>1</number>
</property>
<property name="maximum">
<number>65535</number>
</property>
<property name="value">
<number>8080</number>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_22">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item>
<widget class="QCheckBox" name="checkI2PMixed">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;If &amp;quot;mixed mode&amp;quot; is enabled I2P torrents are allowed to also get peers from other sources than the tracker, and connect to regular IPs, not providing any anonymization. This may be useful if the user is not interested in the anonymization of I2P, but still wants to be able to connect to I2P peers.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Mixed mode</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupProxy">
<property name="enabled">
@@ -1839,6 +1911,19 @@ readme[0-9].txt: filter 'readme1.txt', 'readme2.txt' but not 'readme10.txt'.</st
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_24">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item>
@@ -1859,7 +1944,7 @@ readme[0-9].txt: filter 'readme1.txt', 'readme2.txt' but not 'readme10.txt'.</st
<string>If checked, hostname lookups are done via the proxy</string>
</property>
<property name="text">
<string>Use proxy for hostname lookups</string>
<string>Perform hostname lookup via proxy</string>
</property>
</widget>
</item>

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)
{

View File

@@ -98,7 +98,7 @@ private slots:
void handleResolved(const QHostAddress &ip, const QString &hostname) const;
private:
void updatePeer(const BitTorrent::Torrent *torrent, const BitTorrent::PeerInfo &peer, bool &isNewPeer);
void updatePeer(int row, const BitTorrent::Torrent *torrent, const BitTorrent::PeerInfo &peer, bool hideZeroValues);
int visibleColumnsCount() const;
void wheelEvent(QWheelEvent *event) override;
@@ -108,6 +108,7 @@ private:
PropertiesWidget *m_properties = nullptr;
Net::ReverseResolution *m_resolver = nullptr;
QHash<PeerEndpoint, QStandardItem *> m_peerItems;
QList<QStandardItem *> m_I2PPeerItems;
QHash<QHostAddress, QSet<QStandardItem *>> m_itemsByIP; // must be kept in sync with `m_peerItems`
bool m_resolveCountries;
};