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 @@ |
|||||||
|
/*
|
||||||
|
* 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 @@ |
|||||||
|
/*
|
||||||
|
* 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 @@ |
|||||||
|
/*
|
||||||
|
* 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 @@ |
|||||||
|
/*
|
||||||
|
* 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 @@ |
|||||||
|
/*
|
||||||
|
* 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 @@ |
|||||||
|
/*
|
||||||
|
* 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