Browse Source
* Extract transfer list filter classes into separate files * Add option to auto hide zero status filters PR #18801. Closes #13996.adaptive-webui-19844
Vladimir Golovnev
2 years ago
committed by
GitHub
26 changed files with 1222 additions and 891 deletions
@ -0,0 +1,92 @@
@@ -0,0 +1,92 @@
|
||||
/*
|
||||
* 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 |
||||
* 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 "basefilterwidget.h" |
||||
|
||||
#include "base/bittorrent/session.h" |
||||
#include "gui/utils.h" |
||||
|
||||
constexpr int ALL_ROW = 0; |
||||
|
||||
BaseFilterWidget::BaseFilterWidget(QWidget *parent, TransferListWidget *transferList) |
||||
: QListWidget(parent) |
||||
, m_transferList {transferList} |
||||
{ |
||||
setFrameShape(QFrame::NoFrame); |
||||
setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); |
||||
setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); |
||||
setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); |
||||
setUniformItemSizes(true); |
||||
setSpacing(0); |
||||
|
||||
setIconSize(Utils::Gui::smallIconSize()); |
||||
|
||||
#if defined(Q_OS_MACOS) |
||||
setAttribute(Qt::WA_MacShowFocusRect, false); |
||||
#endif |
||||
|
||||
setContextMenuPolicy(Qt::CustomContextMenu); |
||||
connect(this, &BaseFilterWidget::customContextMenuRequested, this, &BaseFilterWidget::showMenu); |
||||
connect(this, &BaseFilterWidget::currentRowChanged, this, &BaseFilterWidget::applyFilter); |
||||
|
||||
connect(BitTorrent::Session::instance(), &BitTorrent::Session::torrentsLoaded |
||||
, this, &BaseFilterWidget::handleTorrentsLoaded); |
||||
connect(BitTorrent::Session::instance(), &BitTorrent::Session::torrentAboutToBeRemoved |
||||
, this, &BaseFilterWidget::torrentAboutToBeDeleted); |
||||
} |
||||
|
||||
QSize BaseFilterWidget::sizeHint() const |
||||
{ |
||||
return { |
||||
// Width should be exactly the width of the content
|
||||
sizeHintForColumn(0), |
||||
// Height should be exactly the height of the content
|
||||
static_cast<int>((sizeHintForRow(0) + 2 * spacing()) * (count() + 0.5))}; |
||||
} |
||||
|
||||
QSize BaseFilterWidget::minimumSizeHint() const |
||||
{ |
||||
QSize size = sizeHint(); |
||||
size.setWidth(6); |
||||
return size; |
||||
} |
||||
|
||||
TransferListWidget *BaseFilterWidget::transferList() const |
||||
{ |
||||
return m_transferList; |
||||
} |
||||
|
||||
void BaseFilterWidget::toggleFilter(const bool checked) |
||||
{ |
||||
setVisible(checked); |
||||
if (checked) |
||||
applyFilter(currentRow()); |
||||
else |
||||
applyFilter(ALL_ROW); |
||||
} |
@ -0,0 +1,63 @@
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* 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 |
||||
* 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 <QtContainerFwd> |
||||
#include <QListWidget> |
||||
|
||||
#include "base/bittorrent/torrent.h" |
||||
|
||||
class TransferListWidget; |
||||
|
||||
class BaseFilterWidget : public QListWidget |
||||
{ |
||||
Q_OBJECT |
||||
Q_DISABLE_COPY_MOVE(BaseFilterWidget) |
||||
|
||||
public: |
||||
BaseFilterWidget(QWidget *parent, TransferListWidget *transferList); |
||||
|
||||
QSize sizeHint() const override; |
||||
QSize minimumSizeHint() const override; |
||||
|
||||
TransferListWidget *transferList() const; |
||||
|
||||
public slots: |
||||
void toggleFilter(bool checked); |
||||
|
||||
private slots: |
||||
virtual void showMenu() = 0; |
||||
virtual void applyFilter(int row) = 0; |
||||
virtual void handleTorrentsLoaded(const QVector<BitTorrent::Torrent *> &torrents) = 0; |
||||
virtual void torrentAboutToBeDeleted(BitTorrent::Torrent *const) = 0; |
||||
|
||||
private: |
||||
TransferListWidget *m_transferList = nullptr; |
||||
}; |
@ -0,0 +1,291 @@
@@ -0,0 +1,291 @@
|
||||
/*
|
||||
* 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 |
||||
* 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 "statusfilterwidget.h" |
||||
|
||||
#include <QListWidgetItem> |
||||
#include <QMenu> |
||||
|
||||
#include "base/bittorrent/session.h" |
||||
#include "base/global.h" |
||||
#include "base/preferences.h" |
||||
#include "base/torrentfilter.h" |
||||
#include "gui/transferlistwidget.h" |
||||
#include "gui/uithememanager.h" |
||||
|
||||
StatusFilterWidget::StatusFilterWidget(QWidget *parent, TransferListWidget *transferList) |
||||
: BaseFilterWidget(parent, transferList) |
||||
{ |
||||
// Add status filters
|
||||
auto *all = new QListWidgetItem(this); |
||||
all->setData(Qt::DisplayRole, tr("All (0)", "this is for the status filter")); |
||||
all->setData(Qt::DecorationRole, UIThemeManager::instance()->getIcon(u"filter-all"_qs, u"filterall"_qs)); |
||||
auto *downloading = new QListWidgetItem(this); |
||||
downloading->setData(Qt::DisplayRole, tr("Downloading (0)")); |
||||
downloading->setData(Qt::DecorationRole, UIThemeManager::instance()->getIcon(u"downloading"_qs)); |
||||
auto *seeding = new QListWidgetItem(this); |
||||
seeding->setData(Qt::DisplayRole, tr("Seeding (0)")); |
||||
seeding->setData(Qt::DecorationRole, UIThemeManager::instance()->getIcon(u"upload"_qs, u"uploading"_qs)); |
||||
auto *completed = new QListWidgetItem(this); |
||||
completed->setData(Qt::DisplayRole, tr("Completed (0)")); |
||||
completed->setData(Qt::DecorationRole, UIThemeManager::instance()->getIcon(u"checked-completed"_qs, u"completed"_qs)); |
||||
auto *resumed = new QListWidgetItem(this); |
||||
resumed->setData(Qt::DisplayRole, tr("Resumed (0)")); |
||||
resumed->setData(Qt::DecorationRole, UIThemeManager::instance()->getIcon(u"torrent-start"_qs, u"media-playback-start"_qs)); |
||||
auto *paused = new QListWidgetItem(this); |
||||
paused->setData(Qt::DisplayRole, tr("Paused (0)")); |
||||
paused->setData(Qt::DecorationRole, UIThemeManager::instance()->getIcon(u"stopped"_qs, u"media-playback-pause"_qs)); |
||||
auto *active = new QListWidgetItem(this); |
||||
active->setData(Qt::DisplayRole, tr("Active (0)")); |
||||
active->setData(Qt::DecorationRole, UIThemeManager::instance()->getIcon(u"filter-active"_qs, u"filteractive"_qs)); |
||||
auto *inactive = new QListWidgetItem(this); |
||||
inactive->setData(Qt::DisplayRole, tr("Inactive (0)")); |
||||
inactive->setData(Qt::DecorationRole, UIThemeManager::instance()->getIcon(u"filter-inactive"_qs, u"filterinactive"_qs)); |
||||
auto *stalled = new QListWidgetItem(this); |
||||
stalled->setData(Qt::DisplayRole, tr("Stalled (0)")); |
||||
stalled->setData(Qt::DecorationRole, UIThemeManager::instance()->getIcon(u"filter-stalled"_qs, u"filterstalled"_qs)); |
||||
auto *stalledUploading = new QListWidgetItem(this); |
||||
stalledUploading->setData(Qt::DisplayRole, tr("Stalled Uploading (0)")); |
||||
stalledUploading->setData(Qt::DecorationRole, UIThemeManager::instance()->getIcon(u"stalledUP"_qs)); |
||||
auto *stalledDownloading = new QListWidgetItem(this); |
||||
stalledDownloading->setData(Qt::DisplayRole, tr("Stalled Downloading (0)")); |
||||
stalledDownloading->setData(Qt::DecorationRole, UIThemeManager::instance()->getIcon(u"stalledDL"_qs)); |
||||
auto *checking = new QListWidgetItem(this); |
||||
checking->setData(Qt::DisplayRole, tr("Checking (0)")); |
||||
checking->setData(Qt::DecorationRole, UIThemeManager::instance()->getIcon(u"force-recheck"_qs, u"checking"_qs)); |
||||
auto *moving = new QListWidgetItem(this); |
||||
moving->setData(Qt::DisplayRole, tr("Moving (0)")); |
||||
moving->setData(Qt::DecorationRole, UIThemeManager::instance()->getIcon(u"set-location"_qs)); |
||||
auto *errored = new QListWidgetItem(this); |
||||
errored->setData(Qt::DisplayRole, tr("Errored (0)")); |
||||
errored->setData(Qt::DecorationRole, UIThemeManager::instance()->getIcon(u"error"_qs)); |
||||
|
||||
const QVector<BitTorrent::Torrent *> torrents = BitTorrent::Session::instance()->torrents(); |
||||
update(torrents); |
||||
connect(BitTorrent::Session::instance(), &BitTorrent::Session::torrentsUpdated |
||||
, this, &StatusFilterWidget::update); |
||||
|
||||
const Preferences *const pref = Preferences::instance(); |
||||
connect(pref, &Preferences::changed, this, &StatusFilterWidget::configure); |
||||
|
||||
const int storedRow = pref->getTransSelFilter(); |
||||
if (item((storedRow < count()) ? storedRow : 0)->isHidden()) |
||||
setCurrentRow(TorrentFilter::All, QItemSelectionModel::SelectCurrent); |
||||
else |
||||
setCurrentRow(storedRow, QItemSelectionModel::SelectCurrent); |
||||
|
||||
toggleFilter(pref->getStatusFilterState()); |
||||
} |
||||
|
||||
StatusFilterWidget::~StatusFilterWidget() |
||||
{ |
||||
Preferences::instance()->setTransSelFilter(currentRow()); |
||||
} |
||||
|
||||
QSize StatusFilterWidget::sizeHint() const |
||||
{ |
||||
int numVisibleItems = 0; |
||||
for (int i = 0; i < count(); ++i) |
||||
{ |
||||
if (!item(i)->isHidden()) |
||||
++numVisibleItems; |
||||
} |
||||
|
||||
return { |
||||
// Width should be exactly the width of the content
|
||||
sizeHintForColumn(0), |
||||
// Height should be exactly the height of the content
|
||||
static_cast<int>((sizeHintForRow(0) + 2 * spacing()) * (numVisibleItems + 0.5))}; |
||||
} |
||||
|
||||
void StatusFilterWidget::updateTorrentStatus(const BitTorrent::Torrent *torrent) |
||||
{ |
||||
TorrentFilterBitset &torrentStatus = m_torrentsStatus[torrent]; |
||||
|
||||
const auto update = [torrent, &torrentStatus](const TorrentFilter::Type status, int &counter) |
||||
{ |
||||
const bool hasStatus = torrentStatus[status]; |
||||
const bool needStatus = TorrentFilter(status).match(torrent); |
||||
if (needStatus && !hasStatus) |
||||
{ |
||||
++counter; |
||||
torrentStatus.set(status); |
||||
} |
||||
else if (!needStatus && hasStatus) |
||||
{ |
||||
--counter; |
||||
torrentStatus.reset(status); |
||||
} |
||||
}; |
||||
|
||||
update(TorrentFilter::Downloading, m_nbDownloading); |
||||
update(TorrentFilter::Seeding, m_nbSeeding); |
||||
update(TorrentFilter::Completed, m_nbCompleted); |
||||
update(TorrentFilter::Resumed, m_nbResumed); |
||||
update(TorrentFilter::Paused, m_nbPaused); |
||||
update(TorrentFilter::Active, m_nbActive); |
||||
update(TorrentFilter::Inactive, m_nbInactive); |
||||
update(TorrentFilter::StalledUploading, m_nbStalledUploading); |
||||
update(TorrentFilter::StalledDownloading, m_nbStalledDownloading); |
||||
update(TorrentFilter::Checking, m_nbChecking); |
||||
update(TorrentFilter::Moving, m_nbMoving); |
||||
update(TorrentFilter::Errored, m_nbErrored); |
||||
|
||||
m_nbStalled = m_nbStalledUploading + m_nbStalledDownloading; |
||||
} |
||||
|
||||
void StatusFilterWidget::updateTexts() |
||||
{ |
||||
const qsizetype torrentsCount = BitTorrent::Session::instance()->torrentsCount(); |
||||
item(TorrentFilter::All)->setData(Qt::DisplayRole, tr("All (%1)").arg(torrentsCount)); |
||||
item(TorrentFilter::Downloading)->setData(Qt::DisplayRole, tr("Downloading (%1)").arg(m_nbDownloading)); |
||||
item(TorrentFilter::Seeding)->setData(Qt::DisplayRole, tr("Seeding (%1)").arg(m_nbSeeding)); |
||||
item(TorrentFilter::Completed)->setData(Qt::DisplayRole, tr("Completed (%1)").arg(m_nbCompleted)); |
||||
item(TorrentFilter::Resumed)->setData(Qt::DisplayRole, tr("Resumed (%1)").arg(m_nbResumed)); |
||||
item(TorrentFilter::Paused)->setData(Qt::DisplayRole, tr("Paused (%1)").arg(m_nbPaused)); |
||||
item(TorrentFilter::Active)->setData(Qt::DisplayRole, tr("Active (%1)").arg(m_nbActive)); |
||||
item(TorrentFilter::Inactive)->setData(Qt::DisplayRole, tr("Inactive (%1)").arg(m_nbInactive)); |
||||
item(TorrentFilter::Stalled)->setData(Qt::DisplayRole, tr("Stalled (%1)").arg(m_nbStalled)); |
||||
item(TorrentFilter::StalledUploading)->setData(Qt::DisplayRole, tr("Stalled Uploading (%1)").arg(m_nbStalledUploading)); |
||||
item(TorrentFilter::StalledDownloading)->setData(Qt::DisplayRole, tr("Stalled Downloading (%1)").arg(m_nbStalledDownloading)); |
||||
item(TorrentFilter::Checking)->setData(Qt::DisplayRole, tr("Checking (%1)").arg(m_nbChecking)); |
||||
item(TorrentFilter::Moving)->setData(Qt::DisplayRole, tr("Moving (%1)").arg(m_nbMoving)); |
||||
item(TorrentFilter::Errored)->setData(Qt::DisplayRole, tr("Errored (%1)").arg(m_nbErrored)); |
||||
} |
||||
|
||||
void StatusFilterWidget::hideZeroItems() |
||||
{ |
||||
item(TorrentFilter::Downloading)->setHidden(m_nbDownloading == 0); |
||||
item(TorrentFilter::Seeding)->setHidden(m_nbSeeding == 0); |
||||
item(TorrentFilter::Completed)->setHidden(m_nbCompleted == 0); |
||||
item(TorrentFilter::Resumed)->setHidden(m_nbResumed == 0); |
||||
item(TorrentFilter::Paused)->setHidden(m_nbPaused == 0); |
||||
item(TorrentFilter::Active)->setHidden(m_nbActive == 0); |
||||
item(TorrentFilter::Inactive)->setHidden(m_nbInactive == 0); |
||||
item(TorrentFilter::Stalled)->setHidden(m_nbStalled == 0); |
||||
item(TorrentFilter::StalledUploading)->setHidden(m_nbStalledUploading == 0); |
||||
item(TorrentFilter::StalledDownloading)->setHidden(m_nbStalledDownloading == 0); |
||||
item(TorrentFilter::Checking)->setHidden(m_nbChecking == 0); |
||||
item(TorrentFilter::Moving)->setHidden(m_nbMoving == 0); |
||||
item(TorrentFilter::Errored)->setHidden(m_nbErrored == 0); |
||||
|
||||
if (currentItem() && currentItem()->isHidden()) |
||||
setCurrentRow(TorrentFilter::All, QItemSelectionModel::SelectCurrent); |
||||
} |
||||
|
||||
void StatusFilterWidget::update(const QVector<BitTorrent::Torrent *> torrents) |
||||
{ |
||||
for (const BitTorrent::Torrent *torrent : torrents) |
||||
updateTorrentStatus(torrent); |
||||
|
||||
updateTexts(); |
||||
|
||||
if (Preferences::instance()->getHideZeroStatusFilters()) |
||||
{ |
||||
hideZeroItems(); |
||||
updateGeometry(); |
||||
} |
||||
} |
||||
|
||||
void StatusFilterWidget::showMenu() |
||||
{ |
||||
QMenu *menu = new QMenu(this); |
||||
menu->setAttribute(Qt::WA_DeleteOnClose); |
||||
|
||||
menu->addAction(UIThemeManager::instance()->getIcon(u"torrent-start"_qs, u"media-playback-start"_qs), tr("Resume torrents") |
||||
, transferList(), &TransferListWidget::startVisibleTorrents); |
||||
menu->addAction(UIThemeManager::instance()->getIcon(u"torrent-stop"_qs, u"media-playback-pause"_qs), tr("Pause torrents") |
||||
, transferList(), &TransferListWidget::pauseVisibleTorrents); |
||||
menu->addAction(UIThemeManager::instance()->getIcon(u"list-remove"_qs), tr("Remove torrents") |
||||
, transferList(), &TransferListWidget::deleteVisibleTorrents); |
||||
|
||||
menu->popup(QCursor::pos()); |
||||
} |
||||
|
||||
void StatusFilterWidget::applyFilter(int row) |
||||
{ |
||||
transferList()->applyStatusFilter(row); |
||||
} |
||||
|
||||
void StatusFilterWidget::handleTorrentsLoaded(const QVector<BitTorrent::Torrent *> &torrents) |
||||
{ |
||||
for (const BitTorrent::Torrent *torrent : torrents) |
||||
updateTorrentStatus(torrent); |
||||
|
||||
updateTexts(); |
||||
} |
||||
|
||||
void StatusFilterWidget::torrentAboutToBeDeleted(BitTorrent::Torrent *const torrent) |
||||
{ |
||||
const TorrentFilterBitset status = m_torrentsStatus.take(torrent); |
||||
|
||||
if (status[TorrentFilter::Downloading]) |
||||
--m_nbDownloading; |
||||
if (status[TorrentFilter::Seeding]) |
||||
--m_nbSeeding; |
||||
if (status[TorrentFilter::Completed]) |
||||
--m_nbCompleted; |
||||
if (status[TorrentFilter::Resumed]) |
||||
--m_nbResumed; |
||||
if (status[TorrentFilter::Paused]) |
||||
--m_nbPaused; |
||||
if (status[TorrentFilter::Active]) |
||||
--m_nbActive; |
||||
if (status[TorrentFilter::Inactive]) |
||||
--m_nbInactive; |
||||
if (status[TorrentFilter::StalledUploading]) |
||||
--m_nbStalledUploading; |
||||
if (status[TorrentFilter::StalledDownloading]) |
||||
--m_nbStalledDownloading; |
||||
if (status[TorrentFilter::Checking]) |
||||
--m_nbChecking; |
||||
if (status[TorrentFilter::Moving]) |
||||
--m_nbMoving; |
||||
if (status[TorrentFilter::Errored]) |
||||
--m_nbErrored; |
||||
|
||||
m_nbStalled = m_nbStalledUploading + m_nbStalledDownloading; |
||||
|
||||
updateTexts(); |
||||
} |
||||
|
||||
void StatusFilterWidget::configure() |
||||
{ |
||||
if (Preferences::instance()->getHideZeroStatusFilters()) |
||||
{ |
||||
hideZeroItems(); |
||||
} |
||||
else |
||||
{ |
||||
for (int i = 0; i < count(); ++i) |
||||
item(i)->setHidden(false); |
||||
} |
||||
|
||||
updateGeometry(); |
||||
} |
@ -0,0 +1,81 @@
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
* 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 |
||||
* 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 <bitset> |
||||
|
||||
#include <QtContainerFwd> |
||||
#include <QHash> |
||||
|
||||
#include "base/torrentfilter.h" |
||||
#include "basefilterwidget.h" |
||||
|
||||
class StatusFilterWidget final : public BaseFilterWidget |
||||
{ |
||||
Q_OBJECT |
||||
Q_DISABLE_COPY_MOVE(StatusFilterWidget) |
||||
|
||||
public: |
||||
StatusFilterWidget(QWidget *parent, TransferListWidget *transferList); |
||||
~StatusFilterWidget() override; |
||||
|
||||
private: |
||||
QSize sizeHint() const override; |
||||
|
||||
// These 4 methods are virtual slots in the base class.
|
||||
// No need to redeclare them here as slots.
|
||||
void showMenu() override; |
||||
void applyFilter(int row) override; |
||||
void handleTorrentsLoaded(const QVector<BitTorrent::Torrent *> &torrents) override; |
||||
void torrentAboutToBeDeleted(BitTorrent::Torrent *const) override; |
||||
|
||||
void configure(); |
||||
|
||||
void update(const QVector<BitTorrent::Torrent *> torrents); |
||||
void updateTorrentStatus(const BitTorrent::Torrent *torrent); |
||||
void updateTexts(); |
||||
void hideZeroItems(); |
||||
|
||||
using TorrentFilterBitset = std::bitset<32>; // approximated size, this should be the number of TorrentFilter::Type elements
|
||||
QHash<const BitTorrent::Torrent *, TorrentFilterBitset> m_torrentsStatus; |
||||
int m_nbDownloading = 0; |
||||
int m_nbSeeding = 0; |
||||
int m_nbCompleted = 0; |
||||
int m_nbResumed = 0; |
||||
int m_nbPaused = 0; |
||||
int m_nbActive = 0; |
||||
int m_nbInactive = 0; |
||||
int m_nbStalled = 0; |
||||
int m_nbStalledUploading = 0; |
||||
int m_nbStalledDownloading = 0; |
||||
int m_nbChecking = 0; |
||||
int m_nbMoving = 0; |
||||
int m_nbErrored = 0; |
||||
}; |
@ -0,0 +1,513 @@
@@ -0,0 +1,513 @@
|
||||
/*
|
||||
* 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 |
||||
* 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 "trackersfilterwidget.h" |
||||
|
||||
#include <QIcon> |
||||
#include <QListWidgetItem> |
||||
#include <QMenu> |
||||
#include <QUrl> |
||||
|
||||
#include "base/algorithm.h" |
||||
#include "base/bittorrent/session.h" |
||||
#include "base/global.h" |
||||
#include "base/net/downloadmanager.h" |
||||
#include "base/preferences.h" |
||||
#include "base/utils/compare.h" |
||||
#include "base/utils/fs.h" |
||||
#include "gui/transferlistwidget.h" |
||||
#include "gui/uithememanager.h" |
||||
|
||||
namespace |
||||
{ |
||||
enum TRACKER_FILTER_ROW |
||||
{ |
||||
ALL_ROW, |
||||
TRACKERLESS_ROW, |
||||
ERROR_ROW, |
||||
WARNING_ROW |
||||
}; |
||||
|
||||
QString getScheme(const QString &tracker) |
||||
{ |
||||
const QString scheme = QUrl(tracker).scheme(); |
||||
return !scheme.isEmpty() ? scheme : u"http"_qs; |
||||
} |
||||
|
||||
QString getHost(const QString &url) |
||||
{ |
||||
// We want the domain + tld. Subdomains should be disregarded
|
||||
// If failed to parse the domain or IP address, original input should be returned
|
||||
|
||||
const QString host = QUrl(url).host(); |
||||
if (host.isEmpty()) |
||||
return url; |
||||
|
||||
// host is in IP format
|
||||
if (!QHostAddress(host).isNull()) |
||||
return host; |
||||
|
||||
return host.section(u'.', -2, -1); |
||||
} |
||||
|
||||
const QString NULL_HOST = u""_qs; |
||||
} |
||||
|
||||
TrackersFilterWidget::TrackersFilterWidget(QWidget *parent, TransferListWidget *transferList, const bool downloadFavicon) |
||||
: BaseFilterWidget(parent, transferList) |
||||
, m_downloadTrackerFavicon(downloadFavicon) |
||||
{ |
||||
auto *allTrackers = new QListWidgetItem(this); |
||||
allTrackers->setData(Qt::DisplayRole, tr("All (0)", "this is for the tracker filter")); |
||||
allTrackers->setData(Qt::DecorationRole, UIThemeManager::instance()->getIcon(u"trackers"_qs, u"network-server"_qs)); |
||||
auto *noTracker = new QListWidgetItem(this); |
||||
noTracker->setData(Qt::DisplayRole, tr("Trackerless (0)")); |
||||
noTracker->setData(Qt::DecorationRole, UIThemeManager::instance()->getIcon(u"trackerless"_qs, u"network-server"_qs)); |
||||
auto *errorTracker = new QListWidgetItem(this); |
||||
errorTracker->setData(Qt::DisplayRole, tr("Error (0)")); |
||||
errorTracker->setData(Qt::DecorationRole, UIThemeManager::instance()->getIcon(u"tracker-error"_qs, u"dialog-error"_qs)); |
||||
auto *warningTracker = new QListWidgetItem(this); |
||||
warningTracker->setData(Qt::DisplayRole, tr("Warning (0)")); |
||||
warningTracker->setData(Qt::DecorationRole, UIThemeManager::instance()->getIcon(u"tracker-warning"_qs, u"dialog-warning"_qs)); |
||||
|
||||
m_trackers[NULL_HOST] = {{}, noTracker}; |
||||
|
||||
handleTorrentsLoaded(BitTorrent::Session::instance()->torrents()); |
||||
|
||||
setCurrentRow(0, QItemSelectionModel::SelectCurrent); |
||||
toggleFilter(Preferences::instance()->getTrackerFilterState()); |
||||
} |
||||
|
||||
TrackersFilterWidget::~TrackersFilterWidget() |
||||
{ |
||||
for (const Path &iconPath : asConst(m_iconPaths)) |
||||
Utils::Fs::removeFile(iconPath); |
||||
} |
||||
|
||||
void TrackersFilterWidget::addTrackers(const BitTorrent::Torrent *torrent, const QVector<BitTorrent::TrackerEntry> &trackers) |
||||
{ |
||||
const BitTorrent::TorrentID torrentID = torrent->id(); |
||||
for (const BitTorrent::TrackerEntry &tracker : trackers) |
||||
addItems(tracker.url, {torrentID}); |
||||
} |
||||
|
||||
void TrackersFilterWidget::removeTrackers(const BitTorrent::Torrent *torrent, const QStringList &trackers) |
||||
{ |
||||
const BitTorrent::TorrentID torrentID = torrent->id(); |
||||
for (const QString &tracker : trackers) |
||||
removeItem(tracker, torrentID); |
||||
} |
||||
|
||||
void TrackersFilterWidget::refreshTrackers(const BitTorrent::Torrent *torrent) |
||||
{ |
||||
const BitTorrent::TorrentID torrentID = torrent->id(); |
||||
|
||||
m_errors.remove(torrentID); |
||||
m_warnings.remove(torrentID); |
||||
|
||||
Algorithm::removeIf(m_trackers, [this, &torrentID](const QString &host, TrackerData &trackerData) |
||||
{ |
||||
QSet<BitTorrent::TorrentID> &torrentIDs = trackerData.torrents; |
||||
if (!torrentIDs.remove(torrentID)) |
||||
return false; |
||||
|
||||
QListWidgetItem *trackerItem = trackerData.item; |
||||
|
||||
if (!host.isEmpty() && torrentIDs.isEmpty()) |
||||
{ |
||||
if (currentItem() == trackerItem) |
||||
setCurrentRow(0, QItemSelectionModel::SelectCurrent); |
||||
delete trackerItem; |
||||
return true; |
||||
} |
||||
|
||||
trackerItem->setText(u"%1 (%2)"_qs.arg((host.isEmpty() ? tr("Trackerless") : host), QString::number(torrentIDs.size()))); |
||||
return false; |
||||
}); |
||||
|
||||
const QVector<BitTorrent::TrackerEntry> trackerEntries = torrent->trackers(); |
||||
const bool isTrackerless = trackerEntries.isEmpty(); |
||||
if (isTrackerless) |
||||
{ |
||||
addItems(NULL_HOST, {torrentID}); |
||||
} |
||||
else |
||||
{ |
||||
for (const BitTorrent::TrackerEntry &trackerEntry : trackerEntries) |
||||
addItems(trackerEntry.url, {torrentID}); |
||||
} |
||||
|
||||
updateGeometry(); |
||||
} |
||||
|
||||
void TrackersFilterWidget::changeTrackerless(const BitTorrent::Torrent *torrent, const bool trackerless) |
||||
{ |
||||
if (trackerless) |
||||
addItems(NULL_HOST, {torrent->id()}); |
||||
else |
||||
removeItem(NULL_HOST, torrent->id()); |
||||
} |
||||
|
||||
void TrackersFilterWidget::addItems(const QString &trackerURL, const QVector<BitTorrent::TorrentID> &torrents) |
||||
{ |
||||
const QString host = getHost(trackerURL); |
||||
auto trackersIt = m_trackers.find(host); |
||||
const bool exists = (trackersIt != m_trackers.end()); |
||||
QListWidgetItem *trackerItem = nullptr; |
||||
|
||||
if (exists) |
||||
{ |
||||
trackerItem = trackersIt->item; |
||||
} |
||||
else |
||||
{ |
||||
trackerItem = new QListWidgetItem(); |
||||
trackerItem->setData(Qt::DecorationRole, UIThemeManager::instance()->getIcon(u"trackers"_qs, u"network-server"_qs)); |
||||
|
||||
const TrackerData trackerData {{}, trackerItem}; |
||||
trackersIt = m_trackers.insert(host, trackerData); |
||||
|
||||
const QString scheme = getScheme(trackerURL); |
||||
downloadFavicon(u"%1://%2/favicon.ico"_qs.arg((scheme.startsWith(u"http") ? scheme : u"http"_qs), host)); |
||||
} |
||||
|
||||
Q_ASSERT(trackerItem); |
||||
|
||||
QSet<BitTorrent::TorrentID> &torrentIDs = trackersIt->torrents; |
||||
for (const BitTorrent::TorrentID &torrentID : torrents) |
||||
torrentIDs.insert(torrentID); |
||||
|
||||
trackerItem->setText(u"%1 (%2)"_qs.arg(((host == NULL_HOST) ? tr("Trackerless") : host), QString::number(torrentIDs.size()))); |
||||
if (exists) |
||||
{ |
||||
if (item(currentRow()) == trackerItem) |
||||
applyFilter(currentRow()); |
||||
return; |
||||
} |
||||
|
||||
Q_ASSERT(count() >= 4); |
||||
const Utils::Compare::NaturalLessThan<Qt::CaseSensitive> naturalLessThan {}; |
||||
int insPos = count(); |
||||
for (int i = 4; i < count(); ++i) |
||||
{ |
||||
if (naturalLessThan(host, item(i)->text())) |
||||
{ |
||||
insPos = i; |
||||
break; |
||||
} |
||||
} |
||||
QListWidget::insertItem(insPos, trackerItem); |
||||
updateGeometry(); |
||||
} |
||||
|
||||
void TrackersFilterWidget::removeItem(const QString &trackerURL, const BitTorrent::TorrentID &id) |
||||
{ |
||||
const QString host = getHost(trackerURL); |
||||
|
||||
QSet<BitTorrent::TorrentID> torrentIDs = m_trackers.value(host).torrents; |
||||
torrentIDs.remove(id); |
||||
|
||||
QListWidgetItem *trackerItem = nullptr; |
||||
|
||||
if (!host.isEmpty()) |
||||
{ |
||||
// Remove from 'Error' and 'Warning' view
|
||||
const auto errorHashesIt = m_errors.find(id); |
||||
if (errorHashesIt != m_errors.end()) |
||||
{ |
||||
QSet<QString> &errored = errorHashesIt.value(); |
||||
errored.remove(trackerURL); |
||||
if (errored.isEmpty()) |
||||
{ |
||||
m_errors.erase(errorHashesIt); |
||||
item(ERROR_ROW)->setText(tr("Error (%1)").arg(m_errors.size())); |
||||
if (currentRow() == ERROR_ROW) |
||||
applyFilter(ERROR_ROW); |
||||
} |
||||
} |
||||
|
||||
const auto warningHashesIt = m_warnings.find(id); |
||||
if (warningHashesIt != m_warnings.end()) |
||||
{ |
||||
QSet<QString> &warned = *warningHashesIt; |
||||
warned.remove(trackerURL); |
||||
if (warned.isEmpty()) |
||||
{ |
||||
m_warnings.erase(warningHashesIt); |
||||
item(WARNING_ROW)->setText(tr("Warning (%1)").arg(m_warnings.size())); |
||||
if (currentRow() == WARNING_ROW) |
||||
applyFilter(WARNING_ROW); |
||||
} |
||||
} |
||||
|
||||
trackerItem = m_trackers.value(host).item; |
||||
|
||||
if (torrentIDs.isEmpty()) |
||||
{ |
||||
if (currentItem() == trackerItem) |
||||
setCurrentRow(0, QItemSelectionModel::SelectCurrent); |
||||
delete trackerItem; |
||||
m_trackers.remove(host); |
||||
updateGeometry(); |
||||
return; |
||||
} |
||||
|
||||
if (trackerItem) |
||||
trackerItem->setText(u"%1 (%2)"_qs.arg(host, QString::number(torrentIDs.size()))); |
||||
} |
||||
else |
||||
{ |
||||
trackerItem = item(TRACKERLESS_ROW); |
||||
trackerItem->setText(tr("Trackerless (%1)").arg(torrentIDs.size())); |
||||
} |
||||
|
||||
m_trackers.insert(host, {torrentIDs, trackerItem}); |
||||
|
||||
if (currentItem() == trackerItem) |
||||
applyFilter(currentRow()); |
||||
} |
||||
|
||||
void TrackersFilterWidget::setDownloadTrackerFavicon(bool value) |
||||
{ |
||||
if (value == m_downloadTrackerFavicon) return; |
||||
m_downloadTrackerFavicon = value; |
||||
|
||||
if (m_downloadTrackerFavicon) |
||||
{ |
||||
for (auto i = m_trackers.cbegin(); i != m_trackers.cend(); ++i) |
||||
{ |
||||
const QString &tracker = i.key(); |
||||
if (!tracker.isEmpty()) |
||||
{ |
||||
const QString scheme = getScheme(tracker); |
||||
downloadFavicon(u"%1://%2/favicon.ico"_qs |
||||
.arg((scheme.startsWith(u"http") ? scheme : u"http"_qs), getHost(tracker))); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
void TrackersFilterWidget::handleTrackerEntriesUpdated(const BitTorrent::Torrent *torrent |
||||
, const QHash<QString, BitTorrent::TrackerEntry> &updatedTrackerEntries) |
||||
{ |
||||
const BitTorrent::TorrentID id = torrent->id(); |
||||
|
||||
auto errorHashesIt = m_errors.find(id); |
||||
auto warningHashesIt = m_warnings.find(id); |
||||
|
||||
for (const BitTorrent::TrackerEntry &trackerEntry : updatedTrackerEntries) |
||||
{ |
||||
if (trackerEntry.status == BitTorrent::TrackerEntry::Working) |
||||
{ |
||||
if (errorHashesIt != m_errors.end()) |
||||
{ |
||||
QSet<QString> &errored = errorHashesIt.value(); |
||||
errored.remove(trackerEntry.url); |
||||
} |
||||
|
||||
if (trackerEntry.message.isEmpty()) |
||||
{ |
||||
if (warningHashesIt != m_warnings.end()) |
||||
{ |
||||
QSet<QString> &warned = *warningHashesIt; |
||||
warned.remove(trackerEntry.url); |
||||
} |
||||
} |
||||
else |
||||
{ |
||||
if (warningHashesIt == m_warnings.end()) |
||||
warningHashesIt = m_warnings.insert(id, {}); |
||||
warningHashesIt.value().insert(trackerEntry.url); |
||||
} |
||||
} |
||||
else if (trackerEntry.status == BitTorrent::TrackerEntry::NotWorking) |
||||
{ |
||||
if (errorHashesIt == m_errors.end()) |
||||
errorHashesIt = m_errors.insert(id, {}); |
||||
errorHashesIt.value().insert(trackerEntry.url); |
||||
} |
||||
} |
||||
|
||||
if ((errorHashesIt != m_errors.end()) && errorHashesIt.value().isEmpty()) |
||||
m_errors.erase(errorHashesIt); |
||||
if ((warningHashesIt != m_warnings.end()) && warningHashesIt.value().isEmpty()) |
||||
m_warnings.erase(warningHashesIt); |
||||
|
||||
item(ERROR_ROW)->setText(tr("Error (%1)").arg(m_errors.size())); |
||||
item(WARNING_ROW)->setText(tr("Warning (%1)").arg(m_warnings.size())); |
||||
|
||||
if (currentRow() == ERROR_ROW) |
||||
applyFilter(ERROR_ROW); |
||||
else if (currentRow() == WARNING_ROW) |
||||
applyFilter(WARNING_ROW); |
||||
} |
||||
|
||||
void TrackersFilterWidget::downloadFavicon(const QString &url) |
||||
{ |
||||
if (!m_downloadTrackerFavicon) return; |
||||
Net::DownloadManager::instance()->download( |
||||
Net::DownloadRequest(url).saveToFile(true), Preferences::instance()->useProxyForGeneralPurposes() |
||||
, this, &TrackersFilterWidget::handleFavicoDownloadFinished); |
||||
} |
||||
|
||||
void TrackersFilterWidget::handleFavicoDownloadFinished(const Net::DownloadResult &result) |
||||
{ |
||||
if (result.status != Net::DownloadStatus::Success) |
||||
{ |
||||
if (result.url.endsWith(u".ico", Qt::CaseInsensitive)) |
||||
downloadFavicon(result.url.left(result.url.size() - 4) + u".png"); |
||||
return; |
||||
} |
||||
|
||||
const QString host = getHost(result.url); |
||||
|
||||
if (!m_trackers.contains(host)) |
||||
{ |
||||
Utils::Fs::removeFile(result.filePath); |
||||
return; |
||||
} |
||||
|
||||
QListWidgetItem *trackerItem = item(rowFromTracker(host)); |
||||
if (!trackerItem) return; |
||||
|
||||
const QIcon icon {result.filePath.data()}; |
||||
//Detect a non-decodable icon
|
||||
QList<QSize> sizes = icon.availableSizes(); |
||||
bool invalid = (sizes.isEmpty() || icon.pixmap(sizes.first()).isNull()); |
||||
if (invalid) |
||||
{ |
||||
if (result.url.endsWith(u".ico", Qt::CaseInsensitive)) |
||||
downloadFavicon(result.url.left(result.url.size() - 4) + u".png"); |
||||
Utils::Fs::removeFile(result.filePath); |
||||
} |
||||
else |
||||
{ |
||||
trackerItem->setData(Qt::DecorationRole, QIcon(result.filePath.data())); |
||||
m_iconPaths.append(result.filePath); |
||||
} |
||||
} |
||||
|
||||
void TrackersFilterWidget::showMenu() |
||||
{ |
||||
QMenu *menu = new QMenu(this); |
||||
menu->setAttribute(Qt::WA_DeleteOnClose); |
||||
|
||||
menu->addAction(UIThemeManager::instance()->getIcon(u"torrent-start"_qs, u"media-playback-start"_qs), tr("Resume torrents") |
||||
, transferList(), &TransferListWidget::startVisibleTorrents); |
||||
menu->addAction(UIThemeManager::instance()->getIcon(u"torrent-stop"_qs, u"media-playback-pause"_qs), tr("Pause torrents") |
||||
, transferList(), &TransferListWidget::pauseVisibleTorrents); |
||||
menu->addAction(UIThemeManager::instance()->getIcon(u"list-remove"_qs), tr("Remove torrents") |
||||
, transferList(), &TransferListWidget::deleteVisibleTorrents); |
||||
|
||||
menu->popup(QCursor::pos()); |
||||
} |
||||
|
||||
void TrackersFilterWidget::applyFilter(const int row) |
||||
{ |
||||
if (row == ALL_ROW) |
||||
transferList()->applyTrackerFilterAll(); |
||||
else if (isVisible()) |
||||
transferList()->applyTrackerFilter(getTorrentIDs(row)); |
||||
} |
||||
|
||||
void TrackersFilterWidget::handleTorrentsLoaded(const QVector<BitTorrent::Torrent *> &torrents) |
||||
{ |
||||
QHash<QString, QVector<BitTorrent::TorrentID>> torrentsPerTracker; |
||||
for (const BitTorrent::Torrent *torrent : torrents) |
||||
{ |
||||
const BitTorrent::TorrentID torrentID = torrent->id(); |
||||
const QVector<BitTorrent::TrackerEntry> trackers = torrent->trackers(); |
||||
for (const BitTorrent::TrackerEntry &tracker : trackers) |
||||
torrentsPerTracker[tracker.url].append(torrentID); |
||||
|
||||
// Check for trackerless torrent
|
||||
if (trackers.isEmpty()) |
||||
torrentsPerTracker[NULL_HOST].append(torrentID); |
||||
} |
||||
|
||||
for (auto it = torrentsPerTracker.cbegin(); it != torrentsPerTracker.cend(); ++it) |
||||
{ |
||||
const QString &trackerURL = it.key(); |
||||
const QVector<BitTorrent::TorrentID> &torrents = it.value(); |
||||
addItems(trackerURL, torrents); |
||||
} |
||||
|
||||
m_totalTorrents += torrents.count(); |
||||
item(ALL_ROW)->setText(tr("All (%1)", "this is for the tracker filter").arg(m_totalTorrents)); |
||||
} |
||||
|
||||
void TrackersFilterWidget::torrentAboutToBeDeleted(BitTorrent::Torrent *const torrent) |
||||
{ |
||||
const BitTorrent::TorrentID torrentID = torrent->id(); |
||||
const QVector<BitTorrent::TrackerEntry> trackers = torrent->trackers(); |
||||
for (const BitTorrent::TrackerEntry &tracker : trackers) |
||||
removeItem(tracker.url, torrentID); |
||||
|
||||
// Check for trackerless torrent
|
||||
if (trackers.isEmpty()) |
||||
removeItem(NULL_HOST, torrentID); |
||||
|
||||
item(ALL_ROW)->setText(tr("All (%1)", "this is for the tracker filter").arg(--m_totalTorrents)); |
||||
} |
||||
|
||||
QString TrackersFilterWidget::trackerFromRow(int row) const |
||||
{ |
||||
Q_ASSERT(row > 1); |
||||
const QString tracker = item(row)->text(); |
||||
QStringList parts = tracker.split(u' '); |
||||
Q_ASSERT(parts.size() >= 2); |
||||
parts.removeLast(); // Remove trailing number
|
||||
return parts.join(u' '); |
||||
} |
||||
|
||||
int TrackersFilterWidget::rowFromTracker(const QString &tracker) const |
||||
{ |
||||
Q_ASSERT(!tracker.isEmpty()); |
||||
for (int i = 4; i < count(); ++i) |
||||
{ |
||||
if (tracker == trackerFromRow(i)) |
||||
return i; |
||||
} |
||||
return -1; |
||||
} |
||||
|
||||
QSet<BitTorrent::TorrentID> TrackersFilterWidget::getTorrentIDs(const int row) const |
||||
{ |
||||
switch (row) |
||||
{ |
||||
case TRACKERLESS_ROW: |
||||
return m_trackers.value(NULL_HOST).torrents; |
||||
case ERROR_ROW: |
||||
return {m_errors.keyBegin(), m_errors.keyEnd()}; |
||||
case WARNING_ROW: |
||||
return {m_warnings.keyBegin(), m_warnings.keyEnd()}; |
||||
default: |
||||
return m_trackers.value(trackerFromRow(row)).torrents; |
||||
} |
||||
} |
@ -0,0 +1,93 @@
@@ -0,0 +1,93 @@
|
||||
/*
|
||||
* 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 |
||||
* 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 <QtContainerFwd> |
||||
#include <QHash> |
||||
|
||||
#include "base/bittorrent/trackerentry.h" |
||||
#include "base/path.h" |
||||
#include "basefilterwidget.h" |
||||
|
||||
class TransferListWidget; |
||||
|
||||
namespace Net |
||||
{ |
||||
struct DownloadResult; |
||||
} |
||||
|
||||
class TrackersFilterWidget final : public BaseFilterWidget |
||||
{ |
||||
Q_OBJECT |
||||
Q_DISABLE_COPY_MOVE(TrackersFilterWidget) |
||||
|
||||
public: |
||||
TrackersFilterWidget(QWidget *parent, TransferListWidget *transferList, bool downloadFavicon); |
||||
~TrackersFilterWidget() override; |
||||
|
||||
void addTrackers(const BitTorrent::Torrent *torrent, const QVector<BitTorrent::TrackerEntry> &trackers); |
||||
void removeTrackers(const BitTorrent::Torrent *torrent, const QStringList &trackers); |
||||
void refreshTrackers(const BitTorrent::Torrent *torrent); |
||||
void changeTrackerless(const BitTorrent::Torrent *torrent, bool trackerless); |
||||
void handleTrackerEntriesUpdated(const BitTorrent::Torrent *torrent |
||||
, const QHash<QString, BitTorrent::TrackerEntry> &updatedTrackerEntries); |
||||
void setDownloadTrackerFavicon(bool value); |
||||
|
||||
private slots: |
||||
void handleFavicoDownloadFinished(const Net::DownloadResult &result); |
||||
|
||||
private: |
||||
// These 4 methods are virtual slots in the base class.
|
||||
// No need to redeclare them here as slots.
|
||||
void showMenu() override; |
||||
void applyFilter(int row) override; |
||||
void handleTorrentsLoaded(const QVector<BitTorrent::Torrent *> &torrents) override; |
||||
void torrentAboutToBeDeleted(BitTorrent::Torrent *const torrent) override; |
||||
|
||||
void addItems(const QString &trackerURL, const QVector<BitTorrent::TorrentID> &torrents); |
||||
void removeItem(const QString &trackerURL, const BitTorrent::TorrentID &id); |
||||
QString trackerFromRow(int row) const; |
||||
int rowFromTracker(const QString &tracker) const; |
||||
QSet<BitTorrent::TorrentID> getTorrentIDs(int row) const; |
||||
void downloadFavicon(const QString &url); |
||||
|
||||
struct TrackerData |
||||
{ |
||||
QSet<BitTorrent::TorrentID> torrents; |
||||
QListWidgetItem *item = nullptr; |
||||
}; |
||||
|
||||
QHash<QString, TrackerData> m_trackers; // <tracker host, tracker data>
|
||||
QHash<BitTorrent::TorrentID, QSet<QString>> m_errors; // <torrent ID, tracker hosts>
|
||||
QHash<BitTorrent::TorrentID, QSet<QString>> m_warnings; // <torrent ID, tracker hosts>
|
||||
PathList m_iconPaths; |
||||
int m_totalTorrents = 0; |
||||
bool m_downloadTrackerFavicon = false; |
||||
}; |
Loading…
Reference in new issue