diff --git a/src/base/bittorrent/trackerentry.cpp b/src/base/bittorrent/trackerentry.cpp index 9946f3ae7..3fc809197 100644 --- a/src/base/bittorrent/trackerentry.cpp +++ b/src/base/bittorrent/trackerentry.cpp @@ -154,5 +154,11 @@ const lt::announce_entry &TrackerEntry::nativeEntry() const bool BitTorrent::operator==(const TrackerEntry &left, const TrackerEntry &right) { - return (QUrl(left.url()) == QUrl(right.url())); + return ((left.tier() == right.tier()) + && QUrl(left.url()) == QUrl(right.url())); +} + +uint BitTorrent::qHash(const TrackerEntry &key, const uint seed) +{ + return (::qHash(key.url(), seed) ^ key.tier()); } diff --git a/src/base/bittorrent/trackerentry.h b/src/base/bittorrent/trackerentry.h index b427aa455..22f7406d1 100644 --- a/src/base/bittorrent/trackerentry.h +++ b/src/base/bittorrent/trackerentry.h @@ -31,6 +31,8 @@ #include +#include + class QString; namespace BitTorrent @@ -70,6 +72,7 @@ namespace BitTorrent }; bool operator==(const TrackerEntry &left, const TrackerEntry &right); + uint qHash(const TrackerEntry &key, uint seed); } #endif // BITTORRENT_TRACKERENTRY_H diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt index 80f9c3687..93054ce16 100644 --- a/src/gui/CMakeLists.txt +++ b/src/gui/CMakeLists.txt @@ -52,6 +52,7 @@ torrentcontentmodelfolder.h torrentcontentmodelitem.h torrentcontenttreeview.h torrentcreatordialog.h +trackerentriesdialog.h transferlistdelegate.h transferlistfilterswidget.h transferlistmodel.h @@ -98,6 +99,7 @@ torrentcontentmodelfolder.cpp torrentcontentmodelitem.cpp torrentcontenttreeview.cpp torrentcreatordialog.cpp +trackerentriesdialog.cpp transferlistdelegate.cpp transferlistfilterswidget.cpp transferlistmodel.cpp @@ -125,6 +127,7 @@ speedlimitdialog.ui statsdialog.ui torrentcategorydialog.ui torrentcreatordialog.ui +trackerentriesdialog.ui updownratiodialog.ui ) diff --git a/src/gui/gui.pri b/src/gui/gui.pri index 6d3387643..452853e0f 100644 --- a/src/gui/gui.pri +++ b/src/gui/gui.pri @@ -57,6 +57,7 @@ HEADERS += \ $$PWD/torrentcontentmodelitem.h \ $$PWD/torrentcontenttreeview.h \ $$PWD/torrentcreatordialog.h \ + $$PWD/trackerentriesdialog.h \ $$PWD/transferlistdelegate.h \ $$PWD/transferlistfilterswidget.h \ $$PWD/transferlistmodel.h \ @@ -114,6 +115,7 @@ SOURCES += \ $$PWD/torrentcontentmodelitem.cpp \ $$PWD/torrentcontenttreeview.cpp \ $$PWD/torrentcreatordialog.cpp \ + $$PWD/trackerentriesdialog.cpp \ $$PWD/transferlistdelegate.cpp \ $$PWD/transferlistfilterswidget.cpp \ $$PWD/transferlistmodel.cpp \ @@ -157,6 +159,7 @@ FORMS += \ $$PWD/statsdialog.ui \ $$PWD/torrentcategorydialog.ui \ $$PWD/torrentcreatordialog.ui \ + $$PWD/trackerentriesdialog.ui \ $$PWD/updownratiodialog.ui RESOURCES += $$PWD/about.qrc diff --git a/src/gui/trackerentriesdialog.cpp b/src/gui/trackerentriesdialog.cpp new file mode 100644 index 000000000..f1dd8b33d --- /dev/null +++ b/src/gui/trackerentriesdialog.cpp @@ -0,0 +1,112 @@ +/* + * Bittorrent Client using Qt and libtorrent. + * Copyright (C) 2019 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 "trackerentriesdialog.h" + +#include + +#include + +#include "base/bittorrent/trackerentry.h" +#include "ui_trackerentriesdialog.h" +#include "utils.h" + +#define SETTINGS_KEY(name) "TrackerEntriesDialog/" name + +TrackerEntriesDialog::TrackerEntriesDialog(QWidget *parent) + : QDialog(parent) + , m_ui(new Ui::TrackerEntriesDialog) + , m_storeDialogSize(SETTINGS_KEY("Dimension")) +{ + m_ui->setupUi(this); + + connect(m_ui->buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept); + connect(m_ui->buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); + + loadSettings(); +} + +TrackerEntriesDialog::~TrackerEntriesDialog() +{ + saveSettings(); + + delete m_ui; +} + +void TrackerEntriesDialog::setTrackers(const QVector &trackers) +{ + int maxTier = -1; + QHash tiers; // + + for (const BitTorrent::TrackerEntry &entry : trackers) { + tiers[entry.tier()] += (entry.url() + '\n'); + maxTier = std::max(maxTier, entry.tier()); + } + + QString text = tiers.value(0); + + for (int i = 1; i <= maxTier; ++i) + text += ('\n' + tiers.value(i)); + + m_ui->plainTextEdit->setPlainText(text); +} + +QVector TrackerEntriesDialog::trackers() const +{ + const QString plainText = m_ui->plainTextEdit->toPlainText(); + const QVector lines = plainText.splitRef('\n'); + + QVector entries; + entries.reserve(lines.size()); + + int tier = 0; + for (QStringRef line : lines) { + line = line.trimmed(); + + if (line.isEmpty()) { + ++tier; + continue; + } + + BitTorrent::TrackerEntry entry {line.toString()}; + entry.setTier(tier); + entries.append(entry); + } + + return entries; +} + +void TrackerEntriesDialog::saveSettings() +{ + m_storeDialogSize = size(); +} + +void TrackerEntriesDialog::loadSettings() +{ + Utils::Gui::resize(this, m_storeDialogSize); +} diff --git a/src/gui/trackerentriesdialog.h b/src/gui/trackerentriesdialog.h new file mode 100644 index 000000000..d85d0273c --- /dev/null +++ b/src/gui/trackerentriesdialog.h @@ -0,0 +1,64 @@ +/* + * Bittorrent Client using Qt and libtorrent. + * Copyright (C) 2019 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. + */ + +#pragma once + +#include +#include + +#include "base/settingvalue.h" + +namespace BitTorrent +{ + class TrackerEntry; +} + +namespace Ui +{ + class TrackerEntriesDialog; +} + +class TrackerEntriesDialog : public QDialog +{ + Q_OBJECT + Q_DISABLE_COPY(TrackerEntriesDialog) + +public: + explicit TrackerEntriesDialog(QWidget *parent); + ~TrackerEntriesDialog() override; + + void setTrackers(const QVector &trackers); + QVector trackers() const; + +private: + void saveSettings(); + void loadSettings(); + + Ui::TrackerEntriesDialog *m_ui; + CachedSettingValue m_storeDialogSize; +}; diff --git a/src/gui/trackerentriesdialog.ui b/src/gui/trackerentriesdialog.ui new file mode 100644 index 000000000..74bc23dee --- /dev/null +++ b/src/gui/trackerentriesdialog.ui @@ -0,0 +1,43 @@ + + + TrackerEntriesDialog + + + + 0 + 0 + 506 + 500 + + + + Edit trackers + + + + + + One tracker URL per line. + +- You can split the trackers into groups by inserting blank lines. +- All trackers within the same group will belong to the same tier. +- The group on top will be tier 0, the next group tier 1 and so on. +- Below will show the common subset of trackers of the selected torrents. + + + + + + + + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + diff --git a/src/gui/transferlistwidget.cpp b/src/gui/transferlistwidget.cpp index 853c97f46..4f3fe511d 100644 --- a/src/gui/transferlistwidget.cpp +++ b/src/gui/transferlistwidget.cpp @@ -28,6 +28,8 @@ #include "transferlistwidget.h" +#include + #include #include #include @@ -36,6 +38,7 @@ #include #include #include +#include #include #include #include @@ -44,6 +47,7 @@ #include "base/bittorrent/session.h" #include "base/bittorrent/torrenthandle.h" +#include "base/bittorrent/trackerentry.h" #include "base/global.h" #include "base/logger.h" #include "base/preferences.h" @@ -59,6 +63,7 @@ #include "previewselectdialog.h" #include "speedlimitdialog.h" #include "torrentcategorydialog.h" +#include "trackerentriesdialog.h" #include "transferlistdelegate.h" #include "transferlistmodel.h" #include "transferlistsortmodel.h" @@ -73,7 +78,7 @@ namespace { using ToggleFn = std::function; - QStringList extractHashes(const QList &torrents) + QStringList extractHashes(const QVector &torrents) { QStringList hashes; for (BitTorrent::TorrentHandle *const torrent : torrents) @@ -395,9 +400,9 @@ void TransferListWidget::torrentDoubleClicked() } } -QList TransferListWidget::getSelectedTorrents() const +QVector TransferListWidget::getSelectedTorrents() const { - QList torrents; + QVector torrents; for (const QModelIndex &index : asConst(selectionModel()->selectedRows())) torrents << m_listModel->torrentHandle(mapToSource(index)); @@ -406,7 +411,7 @@ QList TransferListWidget::getSelectedTorrents() con void TransferListWidget::setSelectedTorrentsLocation() { - const QList torrents = getSelectedTorrents(); + const QVector torrents = getSelectedTorrents(); if (torrents.isEmpty()) return; const QString oldLocation = torrents[0]->savePath(); @@ -489,7 +494,7 @@ void TransferListWidget::deleteSelectedTorrents(bool deleteLocalFiles) { if (m_mainWindow->currentTabWidget() != this) return; - const QList torrents = getSelectedTorrents(); + const QVector torrents = getSelectedTorrents(); if (torrents.empty()) return; if (Preferences::instance()->confirmTorrentDeletion() @@ -503,7 +508,7 @@ void TransferListWidget::deleteVisibleTorrents() { if (m_sortFilterModel->rowCount() <= 0) return; - QList torrents; + QVector torrents; for (int i = 0; i < m_sortFilterModel->rowCount(); ++i) torrents << m_listModel->torrentHandle(mapToSource(m_sortFilterModel->index(i, 0))); @@ -617,7 +622,7 @@ void TransferListWidget::previewSelectedTorrents() void TransferListWidget::setDlLimitSelectedTorrents() { - QList torrentsList; + QVector torrentsList; for (BitTorrent::TorrentHandle *const torrent : asConst(getSelectedTorrents())) { if (torrent->isSeed()) continue; @@ -647,7 +652,7 @@ void TransferListWidget::setDlLimitSelectedTorrents() void TransferListWidget::setUpLimitSelectedTorrents() { - QList torrentsList = getSelectedTorrents(); + QVector torrentsList = getSelectedTorrents(); if (torrentsList.empty()) return; int oldLimit = torrentsList.first()->uploadLimit(); @@ -672,7 +677,7 @@ void TransferListWidget::setUpLimitSelectedTorrents() void TransferListWidget::setMaxRatioSelectedTorrents() { - const QList torrents = getSelectedTorrents(); + const QVector torrents = getSelectedTorrents(); if (torrents.isEmpty()) return; qreal currentMaxRatio = BitTorrent::Session::instance()->globalMaxRatio(); @@ -807,6 +812,39 @@ void TransferListWidget::askAddTagsForSelection() addSelectionTag(tag); } +void TransferListWidget::editTorrentTrackers() +{ + const QVector torrents = getSelectedTorrents(); + QVector commonTrackers; + + if (!torrents.empty()) { + commonTrackers = torrents[0]->trackers(); + + for (const BitTorrent::TorrentHandle *torrent : torrents) { + QSet trackerSet; + + for (const BitTorrent::TrackerEntry &entry : asConst(torrent->trackers())) + trackerSet.insert(entry); + + commonTrackers.erase(std::remove_if(commonTrackers.begin(), commonTrackers.end() + , [&trackerSet](const BitTorrent::TrackerEntry &entry) { return !trackerSet.contains(entry); }) + , commonTrackers.end()); + } + } + + auto trackerDialog = new TrackerEntriesDialog(this); + trackerDialog->setAttribute(Qt::WA_DeleteOnClose); + trackerDialog->setTrackers(commonTrackers); + + connect(trackerDialog, &QDialog::accepted, this, [torrents, trackerDialog]() + { + for (BitTorrent::TorrentHandle *torrent : torrents) + torrent->replaceTrackers(trackerDialog->trackers()); + }); + + trackerDialog->open(); +} + void TransferListWidget::confirmRemoveAllTagsForSelection() { QMessageBox::StandardButton response = QMessageBox::question( @@ -951,6 +989,8 @@ void TransferListWidget::displayListMenu(const QPoint &) actionAutoTMM->setCheckable(true); actionAutoTMM->setToolTip(tr("Automatic mode means that various torrent properties(eg save path) will be decided by the associated category")); connect(actionAutoTMM, &QAction::triggered, this, &TransferListWidget::setSelectedAutoTMMEnabled); + QAction *actionEditTracker = new QAction(GuiIconProvider::instance()->getIcon("edit-rename"), tr("Edit trackers..."), listMenu); + connect(actionEditTracker, &QAction::triggered, this, &TransferListWidget::editTorrentTrackers); // End of actions // Enable/disable pause/start action given the DL state @@ -1046,6 +1086,7 @@ void TransferListWidget::displayListMenu(const QPoint &) listMenu->addAction(actionSetTorrentPath); if (selectedIndexes.size() == 1) listMenu->addAction(actionRename); + listMenu->addAction(actionEditTracker); // Category Menu QStringList categories = BitTorrent::Session::instance()->categories().keys(); diff --git a/src/gui/transferlistwidget.h b/src/gui/transferlistwidget.h index c738166ff..41d3421e5 100644 --- a/src/gui/transferlistwidget.h +++ b/src/gui/transferlistwidget.h @@ -31,6 +31,7 @@ #include #include +#include namespace BitTorrent { @@ -99,7 +100,7 @@ protected: QModelIndex mapToSource(const QModelIndex &index) const; QModelIndex mapFromSource(const QModelIndex &index) const; bool loadSettings(); - QList getSelectedTorrents() const; + QVector getSelectedTorrents() const; protected slots: void torrentDoubleClicked(); @@ -118,6 +119,7 @@ signals: private: void wheelEvent(QWheelEvent *event) override; void askAddTagsForSelection(); + void editTorrentTrackers(); void confirmRemoveAllTagsForSelection(); QStringList askTagsForSelection(const QString &dialogTitle); void applyToSelectedTorrents(const std::function &fn);