diff --git a/src/base/bittorrent/torrentimpl.cpp b/src/base/bittorrent/torrentimpl.cpp index 5cf1f7052..8b31b3d6c 100644 --- a/src/base/bittorrent/torrentimpl.cpp +++ b/src/base/bittorrent/torrentimpl.cpp @@ -540,6 +540,7 @@ void TorrentImpl::addTrackers(QVector trackers) m_trackerEntries.append(trackers); std::sort(m_trackerEntries.begin(), m_trackerEntries.end() , [](const TrackerEntry &lhs, const TrackerEntry &rhs) { return lhs.tier < rhs.tier; }); + m_session->handleTorrentNeedSaveResumeData(this); m_session->handleTorrentTrackersAdded(this, trackers); } @@ -561,6 +562,7 @@ void TorrentImpl::removeTrackers(const QStringList &trackers) if (!removedTrackers.isEmpty()) { m_nativeHandle.replace_trackers(nativeTrackers); + m_session->handleTorrentNeedSaveResumeData(this); m_session->handleTorrentTrackersRemoved(this, removedTrackers); } @@ -579,12 +581,13 @@ void TorrentImpl::replaceTrackers(QVector trackers) nativeTrackers.emplace_back(makeNativeAnnounceEntry(tracker.url, tracker.tier)); m_nativeHandle.replace_trackers(nativeTrackers); + m_trackerEntries = trackers; + // Clear the peer list if it's a private torrent since // we do not want to keep connecting with peers from old tracker. if (isPrivate()) clearPeers(); - m_trackerEntries = trackers; m_session->handleTorrentNeedSaveResumeData(this); m_session->handleTorrentTrackersChanged(this); } diff --git a/src/base/bittorrent/trackerentry.cpp b/src/base/bittorrent/trackerentry.cpp index a87bfadf8..5521c63d3 100644 --- a/src/base/bittorrent/trackerentry.cpp +++ b/src/base/bittorrent/trackerentry.cpp @@ -28,6 +28,34 @@ #include "trackerentry.h" +#include +#include + +QVector BitTorrent::parseTrackerEntries(const QStringView str) +{ + const QList trackers = str.split(u'\n'); // keep the empty parts to track tracker tier + + QVector entries; + entries.reserve(trackers.size()); + + int trackerTier = 0; + for (QStringView tracker : trackers) + { + tracker = tracker.trimmed(); + + if (tracker.isEmpty()) + { + if (trackerTier < std::numeric_limits::max()) // prevent overflow + ++trackerTier; + continue; + } + + entries.append({tracker.toString(), trackerTier}); + } + + return entries; +} + bool BitTorrent::operator==(const TrackerEntry &left, const TrackerEntry &right) { return (left.url == right.url); diff --git a/src/base/bittorrent/trackerentry.h b/src/base/bittorrent/trackerentry.h index 0a6d9193a..fc933e82f 100644 --- a/src/base/bittorrent/trackerentry.h +++ b/src/base/bittorrent/trackerentry.h @@ -30,10 +30,12 @@ #include +#include #include #include #include #include +#include namespace BitTorrent { @@ -74,6 +76,8 @@ namespace BitTorrent QString message {}; }; + QVector parseTrackerEntries(QStringView str); + bool operator==(const TrackerEntry &left, const TrackerEntry &right); #if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) std::size_t qHash(const TrackerEntry &key, std::size_t seed = 0); diff --git a/src/gui/properties/trackerlistwidget.cpp b/src/gui/properties/trackerlistwidget.cpp index ae979d2f2..791700485 100644 --- a/src/gui/properties/trackerlistwidget.cpp +++ b/src/gui/properties/trackerlistwidget.cpp @@ -423,17 +423,15 @@ void TrackerListWidget::loadTrackers() delete m_trackerItems.take(tracker); } -// Ask the user for new trackers and add them to the torrent -void TrackerListWidget::askForTrackers() +void TrackerListWidget::openAddTrackersDialog() { - BitTorrent::Torrent *const torrent = m_properties->getCurrentTorrent(); - if (!torrent) return; - - QVector trackers; - for (const QString &tracker : asConst(TrackersAdditionDialog::askForTrackers(this, torrent))) - trackers.append({tracker}); + BitTorrent::Torrent *torrent = m_properties->getCurrentTorrent(); + if (!torrent) + return; - torrent->addTrackers(trackers); + const auto dialog = new TrackersAdditionDialog(this, torrent); + dialog->setAttribute(Qt::WA_DeleteOnClose); + dialog->open(); } void TrackerListWidget::copyTrackerUrl() @@ -568,7 +566,7 @@ void TrackerListWidget::showTrackerListMenu() // Add actions menu->addAction(UIThemeManager::instance()->getIcon(u"list-add"_qs), tr("Add trackers...") - , this, &TrackerListWidget::askForTrackers); + , this, &TrackerListWidget::openAddTrackersDialog); if (!getSelectedTrackerItems().isEmpty()) { diff --git a/src/gui/properties/trackerlistwidget.h b/src/gui/properties/trackerlistwidget.h index 26c94a8db..a8b0f2d46 100644 --- a/src/gui/properties/trackerlistwidget.h +++ b/src/gui/properties/trackerlistwidget.h @@ -70,7 +70,6 @@ public slots: void clear(); void loadStickyItems(const BitTorrent::Torrent *torrent); void loadTrackers(); - void askForTrackers(); void copyTrackerUrl(); void reannounceSelected(); void deleteSelectedTrackers(); @@ -83,6 +82,7 @@ protected: QVector getSelectedTrackerItems() const; private slots: + void openAddTrackersDialog(); void displayColumnHeaderMenu(); private: diff --git a/src/gui/properties/trackersadditiondialog.cpp b/src/gui/properties/trackersadditiondialog.cpp index 4d4f64435..4ea931ddd 100644 --- a/src/gui/properties/trackersadditiondialog.cpp +++ b/src/gui/properties/trackersadditiondialog.cpp @@ -1,5 +1,6 @@ /* * Bittorrent Client using Qt and libtorrent. + * Copyright (C) 2022 Mike Tzou (Chocobo1) * Copyright (C) 2006 Christophe Dumez * * This program is free software; you can redistribute it and/or @@ -28,9 +29,10 @@ #include "trackersadditiondialog.h" -#include #include -#include +#include +#include +#include #include "base/bittorrent/torrent.h" #include "base/bittorrent/trackerentry.h" @@ -39,102 +41,87 @@ #include "gui/uithememanager.h" #include "ui_trackersadditiondialog.h" +#define SETTINGS_KEY(name) u"AddTrackersDialog/" name + TrackersAdditionDialog::TrackersAdditionDialog(QWidget *parent, BitTorrent::Torrent *const torrent) : QDialog(parent) - , m_ui(new Ui::TrackersAdditionDialog()) + , m_ui(new Ui::TrackersAdditionDialog) , m_torrent(torrent) + , m_storeDialogSize(SETTINGS_KEY(u"Size"_qs)) + , m_storeTrackersListURL(SETTINGS_KEY(u"TrackersListURL"_qs)) { m_ui->setupUi(this); - // Icons - m_ui->uTorrentListButton->setIcon(UIThemeManager::instance()->getIcon(u"downloading"_qs)); + + m_ui->downloadButton->setIcon(UIThemeManager::instance()->getIcon(u"downloading"_qs)); + m_ui->buttonBox->button(QDialogButtonBox::Ok)->setText(tr("Add")); + + connect(m_ui->downloadButton, &QAbstractButton::clicked, this, &TrackersAdditionDialog::onDownloadButtonClicked); + connect(this, &QDialog::accepted, this, &TrackersAdditionDialog::onAccepted); + + loadSettings(); } TrackersAdditionDialog::~TrackersAdditionDialog() { + saveSettings(); + delete m_ui; } -QStringList TrackersAdditionDialog::newTrackers() const +void TrackersAdditionDialog::onAccepted() const { - const QString plainText = m_ui->textEditTrackersList->toPlainText(); + const QVector entries = BitTorrent::parseTrackerEntries(m_ui->textEditTrackersList->toPlainText()); + m_torrent->addTrackers(entries); +} - QStringList cleanTrackers; - for (QStringView url : asConst(QStringView(plainText).split(u'\n'))) +void TrackersAdditionDialog::onDownloadButtonClicked() +{ + const QString url = m_ui->lineEditListURL->text(); + if (url.isEmpty()) { - url = url.trimmed(); - if (!url.isEmpty()) - cleanTrackers << url.toString(); + QMessageBox::warning(this, tr("Trackers list URL error"), tr("The trackers list URL cannot be empty")); + return; } - return cleanTrackers; -} -void TrackersAdditionDialog::on_uTorrentListButton_clicked() -{ - m_ui->uTorrentListButton->setEnabled(false); - Net::DownloadManager::instance()->download(m_ui->lineEditListURL->text() - , this, &TrackersAdditionDialog::torrentListDownloadFinished); // Just to show that it takes times + m_ui->downloadButton->setEnabled(false); setCursor(Qt::WaitCursor); + + Net::DownloadManager::instance()->download(url, this, &TrackersAdditionDialog::onTorrentListDownloadFinished); } -void TrackersAdditionDialog::torrentListDownloadFinished(const Net::DownloadResult &result) +void TrackersAdditionDialog::onTorrentListDownloadFinished(const Net::DownloadResult &result) { + // Restore the cursor, buttons... + m_ui->downloadButton->setEnabled(true); + setCursor(Qt::ArrowCursor); + if (result.status != Net::DownloadStatus::Success) { - // To restore the cursor ... - setCursor(Qt::ArrowCursor); - m_ui->uTorrentListButton->setEnabled(true); - QMessageBox::warning( - this, tr("Download error") - , tr("The trackers list could not be downloaded, reason: %1") - .arg(result.errorString), QMessageBox::Ok); + QMessageBox::warning(this, tr("Download trackers list error") + , tr("Error occurred when downloading the trackers list. Reason: \"%1\"").arg(result.errorString)); return; } - const QStringList trackersFromUser = m_ui->textEditTrackersList->toPlainText().split(u'\n'); - QVector existingTrackers = m_torrent->trackers(); - existingTrackers.reserve(trackersFromUser.size()); - for (const QString &userURL : trackersFromUser) - { - const BitTorrent::TrackerEntry userTracker {userURL}; - if (!existingTrackers.contains(userTracker)) - existingTrackers << userTracker; - } - - // Add new trackers to the list - if (!m_ui->textEditTrackersList->toPlainText().isEmpty() && !m_ui->textEditTrackersList->toPlainText().endsWith(u'\n')) + // Add fetched trackers to the list + const QString existingText = m_ui->textEditTrackersList->toPlainText(); + if (!existingText.isEmpty() && !existingText.endsWith(u'\n')) m_ui->textEditTrackersList->insertPlainText(u"\n"_qs); - int nb = 0; - QBuffer buffer; - buffer.setData(result.data); - buffer.open(QBuffer::ReadOnly); - while (!buffer.atEnd()) - { - const auto line = QString::fromUtf8(buffer.readLine().trimmed()); - if (line.isEmpty()) continue; - - BitTorrent::TrackerEntry newTracker {line}; - if (!existingTrackers.contains(newTracker)) - { - m_ui->textEditTrackersList->insertPlainText(line + u'\n'); - ++nb; - } - } - // To restore the cursor ... - setCursor(Qt::ArrowCursor); - m_ui->uTorrentListButton->setEnabled(true); - // Display information message if necessary - if (nb == 0) - QMessageBox::information(this, tr("No change"), tr("No additional trackers were found."), QMessageBox::Ok); + // append the data as-is + const auto trackers = QString::fromUtf8(result.data).trimmed(); + m_ui->textEditTrackersList->insertPlainText(trackers); } -QStringList TrackersAdditionDialog::askForTrackers(QWidget *parent, BitTorrent::Torrent *const torrent) +void TrackersAdditionDialog::saveSettings() { - QStringList trackers; - TrackersAdditionDialog dlg(parent, torrent); - if (dlg.exec() == QDialog::Accepted) - return dlg.newTrackers(); + m_storeDialogSize = size(); + m_storeTrackersListURL = m_ui->lineEditListURL->text(); +} - return trackers; +void TrackersAdditionDialog::loadSettings() +{ + if (const QSize dialogSize = m_storeDialogSize; dialogSize.isValid()) + resize(dialogSize); + m_ui->lineEditListURL->setText(m_storeTrackersListURL); } diff --git a/src/gui/properties/trackersadditiondialog.h b/src/gui/properties/trackersadditiondialog.h index 90cf233ae..b9313b6fd 100644 --- a/src/gui/properties/trackersadditiondialog.h +++ b/src/gui/properties/trackersadditiondialog.h @@ -1,5 +1,6 @@ /* * Bittorrent Client using Qt and libtorrent. + * Copyright (C) 2022 Mike Tzou (Chocobo1) * Copyright (C) 2006 Christophe Dumez * * This program is free software; you can redistribute it and/or @@ -29,9 +30,8 @@ #pragma once #include -#include -class QString; +#include "base/settingvalue.h" namespace BitTorrent { @@ -57,14 +57,18 @@ public: TrackersAdditionDialog(QWidget *parent, BitTorrent::Torrent *const torrent); ~TrackersAdditionDialog(); - QStringList newTrackers() const; - static QStringList askForTrackers(QWidget *parent, BitTorrent::Torrent *const torrent); - -public slots: - void on_uTorrentListButton_clicked(); - void torrentListDownloadFinished(const Net::DownloadResult &result); +private slots: + void onAccepted() const; + void onDownloadButtonClicked(); + void onTorrentListDownloadFinished(const Net::DownloadResult &result); private: + void saveSettings(); + void loadSettings(); + Ui::TrackersAdditionDialog *m_ui = nullptr; BitTorrent::Torrent *const m_torrent = nullptr; + + SettingValue m_storeDialogSize; + SettingValue m_storeTrackersListURL; }; diff --git a/src/gui/properties/trackersadditiondialog.ui b/src/gui/properties/trackersadditiondialog.ui index c9bcf4d8f..d737aec6f 100644 --- a/src/gui/properties/trackersadditiondialog.ui +++ b/src/gui/properties/trackersadditiondialog.ui @@ -44,7 +44,11 @@ - + + + Download trackers list + + diff --git a/src/gui/trackerentriesdialog.cpp b/src/gui/trackerentriesdialog.cpp index 62e82904d..7be839590 100644 --- a/src/gui/trackerentriesdialog.cpp +++ b/src/gui/trackerentriesdialog.cpp @@ -80,27 +80,7 @@ void TrackerEntriesDialog::setTrackers(const QVector & QVector TrackerEntriesDialog::trackers() const { - const QString plainText = m_ui->plainTextEdit->toPlainText(); - const QList lines = QStringView(plainText).split(u'\n'); - - QVector entries; - entries.reserve(lines.size()); - - int tier = 0; - for (QStringView line : lines) - { - line = line.trimmed(); - - if (line.isEmpty()) - { - ++tier; - continue; - } - - entries.append({line.toString(), tier}); - } - - return entries; + return BitTorrent::parseTrackerEntries(m_ui->plainTextEdit->toPlainText()); } void TrackerEntriesDialog::saveSettings() diff --git a/src/webui/api/torrentscontroller.cpp b/src/webui/api/torrentscontroller.cpp index 58ae26742..192cecac4 100644 --- a/src/webui/api/torrentscontroller.cpp +++ b/src/webui/api/torrentscontroller.cpp @@ -745,14 +745,8 @@ void TorrentsController::addTrackersAction() if (!torrent) throw APIError(APIErrorType::NotFound); - QVector trackers; - for (const QString &urlStr : asConst(params()[u"urls"_qs].split(u'\n'))) - { - const QUrl url {urlStr.trimmed()}; - if (url.isValid()) - trackers.append({url.toString()}); - } - torrent->addTrackers(trackers); + const QVector entries = BitTorrent::parseTrackerEntries(params()[u"urls"_qs]); + torrent->addTrackers(entries); } void TorrentsController::editTrackerAction() diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index ad00a4bdc..7d8dce892 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -11,6 +11,7 @@ include_directories("../src") set(testFiles testalgorithm.cpp + testbittorrenttrackerentry.cpp testorderedset.cpp testpath.cpp testutilscompare.cpp diff --git a/test/testbittorrenttrackerentry.cpp b/test/testbittorrenttrackerentry.cpp new file mode 100644 index 000000000..39d188ab1 --- /dev/null +++ b/test/testbittorrenttrackerentry.cpp @@ -0,0 +1,143 @@ +/* + * Bittorrent Client using Qt and libtorrent. + * Copyright (C) 2022 Mike Tzou (Chocobo1) + * + * This program 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 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * In addition, as a special exception, the copyright holders give permission to + * link this program with the OpenSSL project's "OpenSSL" library (or with + * modified versions of it that use the same license as the "OpenSSL" library), + * and distribute the linked executables. You must obey the GNU General Public + * License in all respects for all of the code used other than "OpenSSL". If you + * modify file(s), you may extend this exception to your version of the file(s), + * but you are not obligated to do so. If you do not wish to do so, delete this + * exception statement from your version. + */ + +#include + +#include +#include + +#include "base/bittorrent/trackerentry.h" +#include "base/global.h" + +class TestBittorrentTrackerEntry final : public QObject +{ + Q_OBJECT + Q_DISABLE_COPY_MOVE(TestBittorrentTrackerEntry) + +public: + TestBittorrentTrackerEntry() = default; + +private slots: + void testParseTrackerEntries() const + { + using Entries = QVector; + + const auto isEqual = [](const Entries &left, const Entries &right) -> bool + { + return std::equal(left.begin(), left.end(), right.begin(), right.end() + , [](const BitTorrent::TrackerEntry &leftEntry, const BitTorrent::TrackerEntry &rightEntry) + { + return (leftEntry.url == rightEntry.url) + && (leftEntry.tier == rightEntry.tier); + }); + }; + + { + const QString input; + const Entries output; + QVERIFY(isEqual(BitTorrent::parseTrackerEntries(input), output)); + } + + { + const QString input = u"http://localhost:1234"_qs; + const Entries output = {{u"http://localhost:1234"_qs, 0}}; + QVERIFY(isEqual(BitTorrent::parseTrackerEntries(input), output)); + } + + { + const QString input = u" http://localhost:1234 "_qs; + const Entries output = {{u"http://localhost:1234"_qs, 0}}; + QVERIFY(isEqual(BitTorrent::parseTrackerEntries(input), output)); + } + + { + const QString input = u"\nhttp://localhost:1234"_qs; + const Entries output = {{u"http://localhost:1234"_qs, 1}}; + QVERIFY(isEqual(BitTorrent::parseTrackerEntries(input), output)); + } + + { + const QString input = u"http://localhost:1234\n"_qs; + const Entries output = {{u"http://localhost:1234"_qs, 0}}; + QVERIFY(isEqual(BitTorrent::parseTrackerEntries(input), output)); + } + + { + const QString input = u"http://localhost:1234 \n http://[::1]:4567"_qs; + const Entries output = + { + {u"http://localhost:1234"_qs, 0}, + {u"http://[::1]:4567"_qs, 0} + }; + QVERIFY(isEqual(BitTorrent::parseTrackerEntries(input), output)); + } + + { + const QString input = u"\n http://localhost:1234 \n http://[::1]:4567"_qs; + const Entries output = + { + {u"http://localhost:1234"_qs, 1}, + {u"http://[::1]:4567"_qs, 1} + }; + QVERIFY(isEqual(BitTorrent::parseTrackerEntries(input), output)); + } + + { + const QString input = u"http://localhost:1234 \n http://[::1]:4567 \n \n \n"_qs; + const Entries output = + { + {u"http://localhost:1234"_qs, 0}, + {u"http://[::1]:4567"_qs, 0} + }; + QVERIFY(isEqual(BitTorrent::parseTrackerEntries(input), output)); + } + + { + const QString input = u"http://localhost:1234 \n \n http://[::1]:4567"_qs; + const Entries output = + { + {u"http://localhost:1234"_qs, 0}, + {u"http://[::1]:4567"_qs, 1} + }; + QVERIFY(isEqual(BitTorrent::parseTrackerEntries(input), output)); + } + + { + const QString input = u"\n \n \n http://localhost:1234 \n \n \n \n http://[::1]:4567 \n \n \n"_qs; + const Entries output = + { + {u"http://localhost:1234"_qs, 3}, + {u"http://[::1]:4567"_qs, 6} + }; + QVERIFY(isEqual(BitTorrent::parseTrackerEntries(input), output)); + } + } +}; + +QTEST_APPLESS_MAIN(TestBittorrentTrackerEntry) +#include "testbittorrenttrackerentry.moc"