mirror of
https://github.com/qbittorrent/qBittorrent.git
synced 2025-12-10 00:46:48 -06:00
parent
f68bc3fef9
commit
c45dfb6662
@ -38,6 +38,7 @@ add_library(qbt_base STATIC
|
||||
bittorrent/speedmonitor.h
|
||||
bittorrent/sslparameters.h
|
||||
bittorrent/torrent.h
|
||||
bittorrent/torrentannouncestatus.h
|
||||
bittorrent/torrentcontenthandler.h
|
||||
bittorrent/torrentcontentlayout.h
|
||||
bittorrent/torrentcontentremoveoption.h
|
||||
|
||||
@ -518,7 +518,7 @@ namespace BitTorrent
|
||||
void torrentTagRemoved(Torrent *torrent, const Tag &tag);
|
||||
void trackerError(Torrent *torrent, const QString &tracker);
|
||||
void trackersAdded(Torrent *torrent, const QList<TrackerEntry> &trackers);
|
||||
void trackersChanged(Torrent *torrent);
|
||||
void trackersReset(Torrent *torrent, const QList<TrackerEntryStatus> &oldEntries, const QList<TrackerEntry> &newEntries);
|
||||
void trackersRemoved(Torrent *torrent, const QStringList &trackers);
|
||||
void trackerSuccess(Torrent *torrent, const QString &tracker);
|
||||
void trackerWarning(Torrent *torrent, const QString &tracker);
|
||||
|
||||
@ -5279,9 +5279,9 @@ void SessionImpl::handleTorrentTrackersRemoved(TorrentImpl *const torrent, const
|
||||
emit trackersRemoved(torrent, deletedTrackers);
|
||||
}
|
||||
|
||||
void SessionImpl::handleTorrentTrackersChanged(TorrentImpl *const torrent)
|
||||
void SessionImpl::handleTorrentTrackersReset(TorrentImpl *const torrent, const QList<TrackerEntryStatus> &oldEntries, const QList<TrackerEntry> &newEntries)
|
||||
{
|
||||
emit trackersChanged(torrent);
|
||||
emit trackersReset(torrent, oldEntries, newEntries);
|
||||
}
|
||||
|
||||
void SessionImpl::handleTorrentUrlSeedsAdded(TorrentImpl *const torrent, const QList<QUrl> &newUrlSeeds)
|
||||
|
||||
@ -474,7 +474,7 @@ namespace BitTorrent
|
||||
void handleTorrentFinished(TorrentImpl *torrent);
|
||||
void handleTorrentTrackersAdded(TorrentImpl *torrent, const QList<TrackerEntry> &newTrackers);
|
||||
void handleTorrentTrackersRemoved(TorrentImpl *torrent, const QStringList &deletedTrackers);
|
||||
void handleTorrentTrackersChanged(TorrentImpl *torrent);
|
||||
void handleTorrentTrackersReset(TorrentImpl *torrent, const QList<TrackerEntryStatus> &oldEntries, const QList<TrackerEntry> &newEntries);
|
||||
void handleTorrentUrlSeedsAdded(TorrentImpl *torrent, const QList<QUrl> &newUrlSeeds);
|
||||
void handleTorrentUrlSeedsRemoved(TorrentImpl *torrent, const QList<QUrl> &urlSeeds);
|
||||
void handleTorrentResumeDataReady(TorrentImpl *torrent, LoadTorrentParams data);
|
||||
|
||||
@ -38,6 +38,7 @@
|
||||
#include "base/pathfwd.h"
|
||||
#include "base/tagset.h"
|
||||
#include "sharelimitaction.h"
|
||||
#include "torrentannouncestatus.h"
|
||||
#include "torrentcontenthandler.h"
|
||||
|
||||
class QBitArray;
|
||||
@ -290,6 +291,7 @@ namespace BitTorrent
|
||||
virtual int connectionsCount() const = 0;
|
||||
virtual int connectionsLimit() const = 0;
|
||||
virtual qlonglong nextAnnounce() const = 0;
|
||||
virtual TorrentAnnounceStatus announceStatus() const = 0;
|
||||
|
||||
virtual void setName(const QString &name) = 0;
|
||||
virtual void setSequentialDownload(bool enable) = 0;
|
||||
|
||||
47
src/base/bittorrent/torrentannouncestatus.h
Normal file
47
src/base/bittorrent/torrentannouncestatus.h
Normal file
@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2025 Vladimir Golovnev <glassez@yandex.ru>
|
||||
*
|
||||
* 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 <QFlags>
|
||||
|
||||
namespace BitTorrent
|
||||
{
|
||||
enum class TorrentAnnounceStatusFlag
|
||||
{
|
||||
HasNoProblem = 0,
|
||||
|
||||
HasWarning = 1,
|
||||
HasTrackerError = 2,
|
||||
HasOtherError = 4
|
||||
};
|
||||
|
||||
Q_DECLARE_FLAGS(TorrentAnnounceStatus, TorrentAnnounceStatusFlag);
|
||||
}
|
||||
|
||||
Q_DECLARE_OPERATORS_FOR_FLAGS(BitTorrent::TorrentAnnounceStatus);
|
||||
@ -718,9 +718,9 @@ void TorrentImpl::replaceTrackers(QList<TrackerEntry> trackers)
|
||||
|
||||
std::vector<lt::announce_entry> nativeTrackers;
|
||||
nativeTrackers.reserve(trackers.size());
|
||||
m_trackerEntryStatuses.clear();
|
||||
const auto oldEntries = std::exchange(m_trackerEntryStatuses, {});
|
||||
|
||||
for (const TrackerEntry &tracker : trackers)
|
||||
for (const TrackerEntry &tracker : asConst(trackers))
|
||||
{
|
||||
nativeTrackers.emplace_back(makeNativeAnnounceEntry(tracker.url, tracker.tier));
|
||||
m_trackerEntryStatuses.append({tracker.url, tracker.tier});
|
||||
@ -734,7 +734,7 @@ void TorrentImpl::replaceTrackers(QList<TrackerEntry> trackers)
|
||||
clearPeers();
|
||||
|
||||
deferredRequestResumeData();
|
||||
m_session->handleTorrentTrackersChanged(this);
|
||||
m_session->handleTorrentTrackersReset(this, oldEntries, trackers);
|
||||
}
|
||||
|
||||
QList<QUrl> TorrentImpl::urlSeeds() const
|
||||
@ -1557,6 +1557,46 @@ qlonglong TorrentImpl::nextAnnounce() const
|
||||
return lt::total_seconds(m_nativeStatus.next_announce);
|
||||
}
|
||||
|
||||
TorrentAnnounceStatus TorrentImpl::announceStatus() const
|
||||
{
|
||||
if (m_announceStatus)
|
||||
return *m_announceStatus;
|
||||
|
||||
TorrentAnnounceStatus announceStatus = TorrentAnnounceStatusFlag::HasNoProblem;
|
||||
for (const TrackerEntryStatus &trackerEntryStatus : asConst(m_trackerEntryStatuses))
|
||||
{
|
||||
switch (trackerEntryStatus.state)
|
||||
{
|
||||
case BitTorrent::TrackerEndpointState::Working:
|
||||
if (!announceStatus.testFlag(TorrentAnnounceStatusFlag::HasWarning))
|
||||
{
|
||||
const bool hasWarningMessage = std::ranges::any_of(trackerEntryStatus.endpoints
|
||||
, [](const TrackerEndpointStatus &endpointEntry)
|
||||
{
|
||||
return !endpointEntry.message.isEmpty() && (endpointEntry.state == BitTorrent::TrackerEndpointState::Working);
|
||||
});
|
||||
announceStatus.setFlag(TorrentAnnounceStatusFlag::HasWarning, hasWarningMessage);
|
||||
}
|
||||
break;
|
||||
|
||||
case BitTorrent::TrackerEndpointState::NotWorking:
|
||||
case BitTorrent::TrackerEndpointState::Unreachable:
|
||||
announceStatus.setFlag(TorrentAnnounceStatusFlag::HasOtherError);
|
||||
break;
|
||||
|
||||
case BitTorrent::TrackerEndpointState::TrackerError:
|
||||
announceStatus.setFlag(TorrentAnnounceStatusFlag::HasTrackerError);
|
||||
break;
|
||||
|
||||
case BitTorrent::TrackerEndpointState::NotContacted:
|
||||
break;
|
||||
};
|
||||
}
|
||||
|
||||
m_announceStatus = announceStatus;
|
||||
return *m_announceStatus;
|
||||
}
|
||||
|
||||
qreal TorrentImpl::popularity() const
|
||||
{
|
||||
// in order to produce floating-point numbers using `std::chrono::duration_cast`,
|
||||
@ -1743,6 +1783,7 @@ TrackerEntryStatus TorrentImpl::updateTrackerEntryStatus(const lt::announce_entr
|
||||
#endif
|
||||
|
||||
::updateTrackerEntryStatus(*it, announceEntry, btProtocols, updateInfo);
|
||||
m_announceStatus.reset();
|
||||
|
||||
return *it;
|
||||
}
|
||||
@ -1758,6 +1799,8 @@ void TorrentImpl::resetTrackerEntryStatuses()
|
||||
status.url = tempUrl;
|
||||
status.tier = tempTier;
|
||||
}
|
||||
|
||||
m_announceStatus = TorrentAnnounceStatusFlag::HasNoProblem;
|
||||
}
|
||||
|
||||
std::shared_ptr<const libtorrent::torrent_info> TorrentImpl::nativeTorrentInfo() const
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2015-2023 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2015-2025 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
@ -217,6 +217,7 @@ namespace BitTorrent
|
||||
int connectionsCount() const override;
|
||||
int connectionsLimit() const override;
|
||||
qlonglong nextAnnounce() const override;
|
||||
TorrentAnnounceStatus announceStatus() const override;
|
||||
|
||||
void setName(const QString &name) override;
|
||||
void setSequentialDownload(bool enable) override;
|
||||
@ -349,6 +350,7 @@ namespace BitTorrent
|
||||
MaintenanceJob m_maintenanceJob = MaintenanceJob::None;
|
||||
|
||||
QList<TrackerEntryStatus> m_trackerEntryStatuses;
|
||||
mutable std::optional<TorrentAnnounceStatus> m_announceStatus;
|
||||
QList<QUrl> m_urlSeeds;
|
||||
FileErrorInfo m_lastFileError;
|
||||
|
||||
|
||||
@ -1904,6 +1904,32 @@ void Preferences::setTrackerFilterState(const bool checked)
|
||||
setValue(u"TransferListFilters/trackerFilterState"_s, checked);
|
||||
}
|
||||
|
||||
bool Preferences::getTrackerStatusFilterState() const
|
||||
{
|
||||
return value(u"TransferListFilters/TrackerStatusFilterState"_s, true);
|
||||
}
|
||||
|
||||
void Preferences::setTrackerStatusFilterState(const bool checked)
|
||||
{
|
||||
if (checked == getTrackerStatusFilterState())
|
||||
return;
|
||||
|
||||
setValue(u"TransferListFilters/TrackerStatusFilterState"_s, checked);
|
||||
}
|
||||
|
||||
bool Preferences::useSeparateTrackerStatusFilter() const
|
||||
{
|
||||
return value(u"TransferListFilters/SeparateTrackerStatusFilter"_s, false);
|
||||
}
|
||||
|
||||
void Preferences::setUseSeparateTrackerStatusFilter(const bool value)
|
||||
{
|
||||
if (value == useSeparateTrackerStatusFilter())
|
||||
return;
|
||||
|
||||
setValue(u"TransferListFilters/SeparateTrackerStatusFilter"_s, value);
|
||||
}
|
||||
|
||||
int Preferences::getTransSelFilter() const
|
||||
{
|
||||
return value<int>(u"TransferListFilters/selectedFilterIndex"_s, 0);
|
||||
|
||||
@ -402,6 +402,9 @@ public:
|
||||
bool getCategoryFilterState() const;
|
||||
bool getTagFilterState() const;
|
||||
bool getTrackerFilterState() const;
|
||||
bool getTrackerStatusFilterState() const;
|
||||
bool useSeparateTrackerStatusFilter() const;
|
||||
void setUseSeparateTrackerStatusFilter(bool value);
|
||||
int getTransSelFilter() const;
|
||||
void setTransSelFilter(int index);
|
||||
bool getHideZeroStatusFilters() const;
|
||||
@ -451,6 +454,7 @@ public slots:
|
||||
void setCategoryFilterState(bool checked);
|
||||
void setTagFilterState(bool checked);
|
||||
void setTrackerFilterState(bool checked);
|
||||
void setTrackerStatusFilterState(bool checked);
|
||||
|
||||
void apply();
|
||||
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2014 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2014-2025 Vladimir Golovnev <glassez@yandex.ru>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
@ -28,94 +28,57 @@
|
||||
|
||||
#include "torrentfilter.h"
|
||||
|
||||
#include "bittorrent/infohash.h"
|
||||
#include "bittorrent/torrent.h"
|
||||
#include <algorithm>
|
||||
|
||||
#include <QUrl>
|
||||
|
||||
#include "base/bittorrent/infohash.h"
|
||||
#include "base/bittorrent/torrent.h"
|
||||
#include "base/bittorrent/trackerentrystatus.h"
|
||||
#include "base/global.h"
|
||||
|
||||
using namespace BitTorrent;
|
||||
|
||||
const std::optional<QString> TorrentFilter::AnyCategory;
|
||||
const std::optional<TorrentIDSet> TorrentFilter::AnyID;
|
||||
const std::optional<QString> TorrentFilter::AnyCategory;
|
||||
const std::optional<Tag> TorrentFilter::AnyTag;
|
||||
const std::optional<QString> TorrentFilter::AnyTrackerHost;
|
||||
const std::optional<TorrentAnnounceStatus> TorrentFilter::AnyAnnounceStatus;
|
||||
|
||||
const TorrentFilter TorrentFilter::DownloadingTorrent(TorrentFilter::Downloading);
|
||||
const TorrentFilter TorrentFilter::SeedingTorrent(TorrentFilter::Seeding);
|
||||
const TorrentFilter TorrentFilter::CompletedTorrent(TorrentFilter::Completed);
|
||||
const TorrentFilter TorrentFilter::StoppedTorrent(TorrentFilter::Stopped);
|
||||
const TorrentFilter TorrentFilter::RunningTorrent(TorrentFilter::Running);
|
||||
const TorrentFilter TorrentFilter::ActiveTorrent(TorrentFilter::Active);
|
||||
const TorrentFilter TorrentFilter::InactiveTorrent(TorrentFilter::Inactive);
|
||||
const TorrentFilter TorrentFilter::StalledTorrent(TorrentFilter::Stalled);
|
||||
const TorrentFilter TorrentFilter::StalledUploadingTorrent(TorrentFilter::StalledUploading);
|
||||
const TorrentFilter TorrentFilter::StalledDownloadingTorrent(TorrentFilter::StalledDownloading);
|
||||
const TorrentFilter TorrentFilter::CheckingTorrent(TorrentFilter::Checking);
|
||||
const TorrentFilter TorrentFilter::MovingTorrent(TorrentFilter::Moving);
|
||||
const TorrentFilter TorrentFilter::ErroredTorrent(TorrentFilter::Errored);
|
||||
QString getTrackerHost(const QString &url)
|
||||
{
|
||||
// We want the hostname.
|
||||
if (const QString host = QUrl(url).host(); !host.isEmpty())
|
||||
return host;
|
||||
|
||||
using BitTorrent::Torrent;
|
||||
// If failed to parse the domain, original input should be returned
|
||||
return url;
|
||||
}
|
||||
|
||||
TorrentFilter::TorrentFilter(const Type type, const std::optional<TorrentIDSet> &idSet
|
||||
, const std::optional<QString> &category, const std::optional<Tag> &tag, const std::optional<bool> isPrivate)
|
||||
: m_type {type}
|
||||
TorrentFilter::TorrentFilter(const Status status, const std::optional<TorrentIDSet> &idSet, const std::optional<QString> &category
|
||||
, const std::optional<Tag> &tag, const std::optional<bool> &isPrivate, const std::optional<QString> &trackerHost
|
||||
, const std::optional<TorrentAnnounceStatus> &announceStatus)
|
||||
: m_status {status}
|
||||
, m_category {category}
|
||||
, m_tag {tag}
|
||||
, m_idSet {idSet}
|
||||
, m_private {isPrivate}
|
||||
, m_trackerHost {trackerHost}
|
||||
, m_announceStatus {announceStatus}
|
||||
{
|
||||
}
|
||||
|
||||
TorrentFilter::TorrentFilter(const QString &filter, const std::optional<TorrentIDSet> &idSet
|
||||
, const std::optional<QString> &category, const std::optional<Tag> &tag, const std::optional<bool> isPrivate)
|
||||
: m_category {category}
|
||||
, m_tag {tag}
|
||||
, m_idSet {idSet}
|
||||
, m_private {isPrivate}
|
||||
bool TorrentFilter::setStatus(const Status status)
|
||||
{
|
||||
setTypeByName(filter);
|
||||
}
|
||||
|
||||
bool TorrentFilter::setType(Type type)
|
||||
{
|
||||
if (m_type != type)
|
||||
if (m_status != status)
|
||||
{
|
||||
m_type = type;
|
||||
m_status = status;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool TorrentFilter::setTypeByName(const QString &filter)
|
||||
{
|
||||
Type type = All;
|
||||
|
||||
if (filter == u"downloading")
|
||||
type = Downloading;
|
||||
else if (filter == u"seeding")
|
||||
type = Seeding;
|
||||
else if (filter == u"completed")
|
||||
type = Completed;
|
||||
else if (filter == u"stopped")
|
||||
type = Stopped;
|
||||
else if (filter == u"running")
|
||||
type = Running;
|
||||
else if (filter == u"active")
|
||||
type = Active;
|
||||
else if (filter == u"inactive")
|
||||
type = Inactive;
|
||||
else if (filter == u"stalled")
|
||||
type = Stalled;
|
||||
else if (filter == u"stalled_uploading")
|
||||
type = StalledUploading;
|
||||
else if (filter == u"stalled_downloading")
|
||||
type = StalledDownloading;
|
||||
else if (filter == u"checking")
|
||||
type = Checking;
|
||||
else if (filter == u"moving")
|
||||
type = Moving;
|
||||
else if (filter == u"errored")
|
||||
type = Errored;
|
||||
|
||||
return setType(type);
|
||||
}
|
||||
|
||||
bool TorrentFilter::setTorrentIDSet(const std::optional<TorrentIDSet> &idSet)
|
||||
{
|
||||
if (m_idSet != idSet)
|
||||
@ -160,18 +123,43 @@ bool TorrentFilter::setPrivate(const std::optional<bool> isPrivate)
|
||||
return false;
|
||||
}
|
||||
|
||||
bool TorrentFilter::match(const Torrent *const torrent) const
|
||||
bool TorrentFilter::setTrackerHost(const std::optional<QString> &trackerHost)
|
||||
{
|
||||
if (!torrent) return false;
|
||||
if (m_trackerHost != trackerHost)
|
||||
{
|
||||
m_trackerHost = trackerHost;
|
||||
return true;
|
||||
}
|
||||
|
||||
return (matchState(torrent) && matchHash(torrent) && matchCategory(torrent) && matchTag(torrent) && matchPrivate(torrent));
|
||||
return false;
|
||||
}
|
||||
|
||||
bool TorrentFilter::matchState(const BitTorrent::Torrent *const torrent) const
|
||||
bool TorrentFilter::setAnnounceStatus(const std::optional<TorrentAnnounceStatus> &announceStatus)
|
||||
{
|
||||
const BitTorrent::TorrentState state = torrent->state();
|
||||
if (m_announceStatus != announceStatus)
|
||||
{
|
||||
m_announceStatus = announceStatus;
|
||||
return true;
|
||||
}
|
||||
|
||||
switch (m_type)
|
||||
return false;
|
||||
}
|
||||
|
||||
bool TorrentFilter::match(const Torrent *const torrent) const
|
||||
{
|
||||
Q_ASSERT(torrent);
|
||||
if (!torrent) [[unlikely]]
|
||||
return false;
|
||||
|
||||
return (matchStatus(torrent) && matchHash(torrent) && matchCategory(torrent)
|
||||
&& matchTag(torrent) && matchPrivate(torrent) && matchTracker(torrent));
|
||||
}
|
||||
|
||||
bool TorrentFilter::matchStatus(const Torrent *const torrent) const
|
||||
{
|
||||
const TorrentState state = torrent->state();
|
||||
|
||||
switch (m_status)
|
||||
{
|
||||
case All:
|
||||
return true;
|
||||
@ -190,16 +178,16 @@ bool TorrentFilter::matchState(const BitTorrent::Torrent *const torrent) const
|
||||
case Inactive:
|
||||
return torrent->isInactive();
|
||||
case Stalled:
|
||||
return (state == BitTorrent::TorrentState::StalledUploading)
|
||||
|| (state == BitTorrent::TorrentState::StalledDownloading);
|
||||
return (state == TorrentState::StalledUploading)
|
||||
|| (state == TorrentState::StalledDownloading);
|
||||
case StalledUploading:
|
||||
return state == BitTorrent::TorrentState::StalledUploading;
|
||||
return state == TorrentState::StalledUploading;
|
||||
case StalledDownloading:
|
||||
return state == BitTorrent::TorrentState::StalledDownloading;
|
||||
return state == TorrentState::StalledDownloading;
|
||||
case Checking:
|
||||
return (state == BitTorrent::TorrentState::CheckingUploading)
|
||||
|| (state == BitTorrent::TorrentState::CheckingDownloading)
|
||||
|| (state == BitTorrent::TorrentState::CheckingResumeData);
|
||||
return (state == TorrentState::CheckingUploading)
|
||||
|| (state == TorrentState::CheckingDownloading)
|
||||
|| (state == TorrentState::CheckingResumeData);
|
||||
case Moving:
|
||||
return torrent->isMoving();
|
||||
case Errored:
|
||||
@ -212,7 +200,7 @@ bool TorrentFilter::matchState(const BitTorrent::Torrent *const torrent) const
|
||||
return false;
|
||||
}
|
||||
|
||||
bool TorrentFilter::matchHash(const BitTorrent::Torrent *const torrent) const
|
||||
bool TorrentFilter::matchHash(const Torrent *const torrent) const
|
||||
{
|
||||
if (!m_idSet)
|
||||
return true;
|
||||
@ -220,7 +208,7 @@ bool TorrentFilter::matchHash(const BitTorrent::Torrent *const torrent) const
|
||||
return m_idSet->contains(torrent->id());
|
||||
}
|
||||
|
||||
bool TorrentFilter::matchCategory(const BitTorrent::Torrent *const torrent) const
|
||||
bool TorrentFilter::matchCategory(const Torrent *const torrent) const
|
||||
{
|
||||
if (!m_category)
|
||||
return true;
|
||||
@ -228,7 +216,7 @@ bool TorrentFilter::matchCategory(const BitTorrent::Torrent *const torrent) cons
|
||||
return (torrent->belongsToCategory(*m_category));
|
||||
}
|
||||
|
||||
bool TorrentFilter::matchTag(const BitTorrent::Torrent *const torrent) const
|
||||
bool TorrentFilter::matchTag(const Torrent *const torrent) const
|
||||
{
|
||||
if (!m_tag)
|
||||
return true;
|
||||
@ -240,10 +228,65 @@ bool TorrentFilter::matchTag(const BitTorrent::Torrent *const torrent) const
|
||||
return torrent->hasTag(*m_tag);
|
||||
}
|
||||
|
||||
bool TorrentFilter::matchPrivate(const BitTorrent::Torrent *const torrent) const
|
||||
bool TorrentFilter::matchPrivate(const Torrent *const torrent) const
|
||||
{
|
||||
if (!m_private)
|
||||
return true;
|
||||
|
||||
return m_private == torrent->isPrivate();
|
||||
}
|
||||
|
||||
bool TorrentFilter::matchTracker(const Torrent *torrent) const
|
||||
{
|
||||
if (!m_trackerHost)
|
||||
{
|
||||
if (!m_announceStatus)
|
||||
return true;
|
||||
|
||||
const TorrentAnnounceStatus announceStatus = torrent->announceStatus();
|
||||
const TorrentAnnounceStatus &testAnnounceStatus = *m_announceStatus;
|
||||
if (!testAnnounceStatus)
|
||||
return !announceStatus;
|
||||
|
||||
return announceStatus.testAnyFlags(testAnnounceStatus);
|
||||
}
|
||||
|
||||
// Trackerless torrent
|
||||
if (m_trackerHost->isEmpty())
|
||||
return torrent->trackers().isEmpty() && !m_announceStatus;
|
||||
|
||||
return std::ranges::any_of(asConst(torrent->trackers())
|
||||
, [trackerHost = m_trackerHost, announceStatus = m_announceStatus](const TrackerEntryStatus &trackerEntryStatus)
|
||||
{
|
||||
if (getTrackerHost(trackerEntryStatus.url) != trackerHost)
|
||||
return false;
|
||||
|
||||
if (!announceStatus)
|
||||
return true;
|
||||
|
||||
switch (trackerEntryStatus.state)
|
||||
{
|
||||
case TrackerEndpointState::Working:
|
||||
{
|
||||
const bool hasWarningMessage = std::ranges::any_of(trackerEntryStatus.endpoints
|
||||
, [](const TrackerEndpointStatus &endpointEntry)
|
||||
{
|
||||
return !endpointEntry.message.isEmpty() && (endpointEntry.state == TrackerEndpointState::Working);
|
||||
});
|
||||
return hasWarningMessage ? announceStatus->testFlag(TorrentAnnounceStatusFlag::HasWarning) : !*announceStatus;
|
||||
}
|
||||
|
||||
case TrackerEndpointState::NotWorking:
|
||||
case TrackerEndpointState::Unreachable:
|
||||
return announceStatus->testFlag(TorrentAnnounceStatusFlag::HasOtherError);
|
||||
|
||||
case TrackerEndpointState::TrackerError:
|
||||
return announceStatus->testFlag(TorrentAnnounceStatusFlag::HasTrackerError);
|
||||
|
||||
case TrackerEndpointState::NotContacted:
|
||||
return false;
|
||||
};
|
||||
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2014 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2014-2025 Vladimir Golovnev <glassez@yandex.ru>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
@ -34,6 +34,7 @@
|
||||
#include <QString>
|
||||
|
||||
#include "base/bittorrent/infohash.h"
|
||||
#include "base/bittorrent/torrentannouncestatus.h"
|
||||
#include "base/tag.h"
|
||||
|
||||
namespace BitTorrent
|
||||
@ -46,7 +47,7 @@ using TorrentIDSet = QSet<BitTorrent::TorrentID>;
|
||||
class TorrentFilter
|
||||
{
|
||||
public:
|
||||
enum Type
|
||||
enum Status
|
||||
{
|
||||
All,
|
||||
Downloading,
|
||||
@ -67,57 +68,47 @@ public:
|
||||
};
|
||||
|
||||
// These mean any permutation, including no category / tag.
|
||||
static const std::optional<QString> AnyCategory;
|
||||
static const std::optional<TorrentIDSet> AnyID;
|
||||
static const std::optional<QString> AnyCategory;
|
||||
static const std::optional<Tag> AnyTag;
|
||||
|
||||
static const TorrentFilter DownloadingTorrent;
|
||||
static const TorrentFilter SeedingTorrent;
|
||||
static const TorrentFilter CompletedTorrent;
|
||||
static const TorrentFilter StoppedTorrent;
|
||||
static const TorrentFilter RunningTorrent;
|
||||
static const TorrentFilter ActiveTorrent;
|
||||
static const TorrentFilter InactiveTorrent;
|
||||
static const TorrentFilter StalledTorrent;
|
||||
static const TorrentFilter StalledUploadingTorrent;
|
||||
static const TorrentFilter StalledDownloadingTorrent;
|
||||
static const TorrentFilter CheckingTorrent;
|
||||
static const TorrentFilter MovingTorrent;
|
||||
static const TorrentFilter ErroredTorrent;
|
||||
static const std::optional<QString> AnyTrackerHost;
|
||||
static const std::optional<BitTorrent::TorrentAnnounceStatus> AnyAnnounceStatus;
|
||||
|
||||
TorrentFilter() = default;
|
||||
// category & tags: pass empty string for uncategorized / untagged torrents.
|
||||
TorrentFilter(Type type
|
||||
TorrentFilter(Status status
|
||||
, const std::optional<TorrentIDSet> &idSet = AnyID
|
||||
, const std::optional<QString> &category = AnyCategory
|
||||
, const std::optional<Tag> &tag = AnyTag
|
||||
, std::optional<bool> isPrivate = {});
|
||||
TorrentFilter(const QString &filter
|
||||
, const std::optional<TorrentIDSet> &idSet = AnyID
|
||||
, const std::optional<QString> &category = AnyCategory
|
||||
, const std::optional<Tag> &tags = AnyTag
|
||||
, std::optional<bool> isPrivate = {});
|
||||
, const std::optional<bool> &isPrivate = {}
|
||||
, const std::optional<QString> &trackerHost = AnyTrackerHost
|
||||
, const std::optional<BitTorrent::TorrentAnnounceStatus> &announceStatus = AnyAnnounceStatus);
|
||||
|
||||
|
||||
bool setType(Type type);
|
||||
bool setTypeByName(const QString &filter);
|
||||
bool setStatus(Status status);
|
||||
bool setTorrentIDSet(const std::optional<TorrentIDSet> &idSet);
|
||||
bool setCategory(const std::optional<QString> &category);
|
||||
bool setTag(const std::optional<Tag> &tag);
|
||||
bool setPrivate(std::optional<bool> isPrivate);
|
||||
bool setTrackerHost(const std::optional<QString> &trackerHost);
|
||||
bool setAnnounceStatus(const std::optional<BitTorrent::TorrentAnnounceStatus> &announceStatus);
|
||||
|
||||
bool match(const BitTorrent::Torrent *torrent) const;
|
||||
|
||||
private:
|
||||
bool matchState(const BitTorrent::Torrent *torrent) const;
|
||||
bool matchStatus(const BitTorrent::Torrent *torrent) const;
|
||||
bool matchHash(const BitTorrent::Torrent *torrent) const;
|
||||
bool matchCategory(const BitTorrent::Torrent *torrent) const;
|
||||
bool matchTag(const BitTorrent::Torrent *torrent) const;
|
||||
bool matchPrivate(const BitTorrent::Torrent *torrent) const;
|
||||
bool matchTracker(const BitTorrent::Torrent *torrent) const;
|
||||
|
||||
Type m_type {All};
|
||||
Status m_status {All};
|
||||
std::optional<QString> m_category;
|
||||
std::optional<Tag> m_tag;
|
||||
std::optional<TorrentIDSet> m_idSet;
|
||||
std::optional<bool> m_private;
|
||||
std::optional<QString> m_trackerHost;
|
||||
std::optional<BitTorrent::TorrentAnnounceStatus> m_announceStatus;
|
||||
};
|
||||
|
||||
QString getTrackerHost(const QString &url);
|
||||
|
||||
@ -129,7 +129,9 @@ add_library(qbt_gui STATIC
|
||||
transferlistfilters/tagfilterproxymodel.h
|
||||
transferlistfilters/tagfilterwidget.h
|
||||
transferlistfilters/trackersfilterwidget.h
|
||||
transferlistfilters/trackerstatusfilterwidget.h
|
||||
transferlistfilterswidget.h
|
||||
transferlistfilterswidgetitem.h
|
||||
transferlistmodel.h
|
||||
transferlistsortmodel.h
|
||||
transferlistwidget.h
|
||||
@ -230,7 +232,9 @@ add_library(qbt_gui STATIC
|
||||
transferlistfilters/tagfilterproxymodel.cpp
|
||||
transferlistfilters/tagfilterwidget.cpp
|
||||
transferlistfilters/trackersfilterwidget.cpp
|
||||
transferlistfilters/trackerstatusfilterwidget.cpp
|
||||
transferlistfilterswidget.cpp
|
||||
transferlistfilterswidgetitem.cpp
|
||||
transferlistmodel.cpp
|
||||
transferlistsortmodel.cpp
|
||||
transferlistwidget.cpp
|
||||
|
||||
@ -492,7 +492,7 @@ MainWindow::MainWindow(IGUIApplication *app, const WindowState initialState, con
|
||||
m_transferListWidget->applyStatusFilter(pref->getTransSelFilter());
|
||||
m_transferListWidget->applyCategoryFilter(QString());
|
||||
m_transferListWidget->applyTagFilter(std::nullopt);
|
||||
m_transferListWidget->applyTrackerFilterAll();
|
||||
m_transferListWidget->applyTrackerFilter({});
|
||||
}
|
||||
|
||||
// Start watching the executable for updates
|
||||
@ -1361,11 +1361,6 @@ void MainWindow::showFiltersSidebar(const bool show)
|
||||
if (show && !m_transferListFiltersWidget)
|
||||
{
|
||||
m_transferListFiltersWidget = new TransferListFiltersWidget(m_splitter, m_transferListWidget, isDownloadTrackerFavicon());
|
||||
connect(BitTorrent::Session::instance(), &BitTorrent::Session::trackersAdded, m_transferListFiltersWidget, &TransferListFiltersWidget::addTrackers);
|
||||
connect(BitTorrent::Session::instance(), &BitTorrent::Session::trackersRemoved, m_transferListFiltersWidget, &TransferListFiltersWidget::removeTrackers);
|
||||
connect(BitTorrent::Session::instance(), &BitTorrent::Session::trackersChanged, m_transferListFiltersWidget, &TransferListFiltersWidget::refreshTrackers);
|
||||
connect(BitTorrent::Session::instance(), &BitTorrent::Session::trackerEntryStatusesUpdated, m_transferListFiltersWidget, &TransferListFiltersWidget::trackerEntryStatusesUpdated);
|
||||
|
||||
m_splitter->insertWidget(0, m_transferListFiltersWidget);
|
||||
m_splitter->setCollapsible(0, true);
|
||||
// From https://doc.qt.io/qt-5/qsplitter.html#setSizes:
|
||||
|
||||
@ -297,6 +297,7 @@ void OptionsDialog::loadBehaviorTabOptions()
|
||||
m_ui->actionTorrentFnOnDblClBox->setCurrentIndex(m_ui->actionTorrentFnOnDblClBox->findData(actionSeeding));
|
||||
|
||||
m_ui->checkBoxHideZeroStatusFilters->setChecked(pref->getHideZeroStatusFilters());
|
||||
m_ui->checkBoxUseSeparateTrackerStatusFilter->setChecked(pref->useSeparateTrackerStatusFilter());
|
||||
|
||||
m_ui->checkTorrentContentDrag->setChecked(pref->isTorrentContentDragEnabled());
|
||||
|
||||
@ -407,6 +408,7 @@ void OptionsDialog::loadBehaviorTabOptions()
|
||||
connect(m_ui->actionTorrentDlOnDblClBox, qComboBoxCurrentIndexChanged, this, &ThisType::enableApplyButton);
|
||||
connect(m_ui->actionTorrentFnOnDblClBox, qComboBoxCurrentIndexChanged, this, &ThisType::enableApplyButton);
|
||||
connect(m_ui->checkBoxHideZeroStatusFilters, &QAbstractButton::toggled, this, &ThisType::enableApplyButton);
|
||||
connect(m_ui->checkBoxUseSeparateTrackerStatusFilter, &QAbstractButton::toggled, this, &ThisType::enableApplyButton);
|
||||
|
||||
connect(m_ui->checkTorrentContentDrag, &QAbstractButton::toggled, this, &ThisType::enableApplyButton);
|
||||
|
||||
@ -504,6 +506,7 @@ void OptionsDialog::saveBehaviorTabOptions() const
|
||||
pref->setActionOnDblClOnTorrentFn(m_ui->actionTorrentFnOnDblClBox->currentData().toInt());
|
||||
|
||||
pref->setHideZeroStatusFilters(m_ui->checkBoxHideZeroStatusFilters->isChecked());
|
||||
pref->setUseSeparateTrackerStatusFilter(m_ui->checkBoxUseSeparateTrackerStatusFilter->isChecked());
|
||||
|
||||
pref->setTorrentContentDragEnabled(m_ui->checkTorrentContentDrag->isChecked());
|
||||
|
||||
|
||||
@ -456,6 +456,16 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="checkBoxUseSeparateTrackerStatusFilter">
|
||||
<property name="toolTip">
|
||||
<string>Use separate "Tracker status" filter. Otherwise it gets merged with "Trackers" filter.</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Use separate "Tracker status" filter</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
|
||||
@ -267,7 +267,7 @@ TrackerListModel::TrackerListModel(BitTorrent::Session *btSession, QObject *pare
|
||||
if (torrent == m_torrent)
|
||||
onTrackersRemoved(deletedTrackers);
|
||||
});
|
||||
connect(m_btSession, &BitTorrent::Session::trackersChanged, this
|
||||
connect(m_btSession, &BitTorrent::Session::trackersReset, this
|
||||
, [this](BitTorrent::Torrent *torrent)
|
||||
{
|
||||
if (torrent == m_torrent)
|
||||
|
||||
@ -99,8 +99,6 @@ StatusFilterWidget::StatusFilterWidget(QWidget *parent, TransferListWidget *tran
|
||||
setCurrentRow(TorrentFilter::All, QItemSelectionModel::SelectCurrent);
|
||||
else
|
||||
setCurrentRow(storedRow, QItemSelectionModel::SelectCurrent);
|
||||
|
||||
toggleFilter(pref->getStatusFilterState());
|
||||
}
|
||||
|
||||
StatusFilterWidget::~StatusFilterWidget()
|
||||
@ -128,7 +126,7 @@ void StatusFilterWidget::updateTorrentStatus(const BitTorrent::Torrent *torrent)
|
||||
{
|
||||
TorrentFilterBitset &torrentStatus = m_torrentsStatus[torrent];
|
||||
|
||||
const auto update = [torrent, &torrentStatus](const TorrentFilter::Type status, int &counter)
|
||||
const auto update = [torrent, &torrentStatus](const TorrentFilter::Status status, int &counter)
|
||||
{
|
||||
const bool hasStatus = torrentStatus[status];
|
||||
const bool needStatus = TorrentFilter(status).match(torrent);
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2023 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2023-2025 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
@ -36,13 +36,13 @@
|
||||
#include <QMessageBox>
|
||||
#include <QUrl>
|
||||
|
||||
#include "base/algorithm.h"
|
||||
#include "base/bittorrent/session.h"
|
||||
#include "base/bittorrent/trackerentry.h"
|
||||
#include "base/bittorrent/trackerentrystatus.h"
|
||||
#include "base/global.h"
|
||||
#include "base/net/downloadmanager.h"
|
||||
#include "base/preferences.h"
|
||||
#include "base/torrentfilter.h"
|
||||
#include "base/utils/compare.h"
|
||||
#include "base/utils/fs.h"
|
||||
#include "gui/transferlistwidget.h"
|
||||
@ -69,16 +69,35 @@ namespace
|
||||
return !scheme.isEmpty() ? scheme : u"http"_s;
|
||||
}
|
||||
|
||||
QString getHost(const QString &url)
|
||||
template <typename T>
|
||||
concept HasUrlMember = requires (T t) { { t.url } -> std::convertible_to<QString>; };
|
||||
|
||||
template <HasUrlMember T>
|
||||
QString getTrackerHost(const T &t)
|
||||
{
|
||||
// We want the hostname.
|
||||
// If failed to parse the domain, original input should be returned
|
||||
return getTrackerHost(t.url);
|
||||
}
|
||||
|
||||
const QString host = QUrl(url).host();
|
||||
if (host.isEmpty())
|
||||
return url;
|
||||
template <typename T>
|
||||
QSet<QString> extractTrackerHosts(const T &trackerEntries)
|
||||
{
|
||||
QSet<QString> trackerHosts;
|
||||
trackerHosts.reserve(trackerEntries.size());
|
||||
for (const auto &trackerEntry : trackerEntries)
|
||||
trackerHosts.insert(getTrackerHost(trackerEntry));
|
||||
|
||||
return host;
|
||||
return trackerHosts;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
QSet<QString> extractTrackerURLs(const T &trackerEntries)
|
||||
{
|
||||
QSet<QString> trackerURLs;
|
||||
trackerURLs.reserve(trackerEntries.size());
|
||||
for (const auto &trackerEntry : trackerEntries)
|
||||
trackerURLs.insert(trackerEntry.url);
|
||||
|
||||
return trackerURLs;
|
||||
}
|
||||
|
||||
QString getFaviconHost(const QString &trackerHost)
|
||||
@ -123,7 +142,7 @@ namespace
|
||||
|
||||
TrackersFilterWidget::TrackersFilterWidget(QWidget *parent, TransferListWidget *transferList, const bool downloadFavicon)
|
||||
: BaseFilterWidget(parent, transferList)
|
||||
, m_downloadTrackerFavicon(downloadFavicon)
|
||||
, m_downloadTrackerFavicon {downloadFavicon}
|
||||
{
|
||||
auto *allTrackersItem = new QListWidgetItem(this);
|
||||
allTrackersItem->setData(Qt::DisplayRole, formatItemText(ALL_ROW, 0));
|
||||
@ -131,22 +150,34 @@ TrackersFilterWidget::TrackersFilterWidget(QWidget *parent, TransferListWidget *
|
||||
auto *trackerlessItem = new QListWidgetItem(this);
|
||||
trackerlessItem->setData(Qt::DisplayRole, formatItemText(TRACKERLESS_ROW, 0));
|
||||
trackerlessItem->setData(Qt::DecorationRole, UIThemeManager::instance()->getIcon(u"trackerless"_s, u"network-server"_s));
|
||||
auto *trackerErrorItem = new QListWidgetItem(this);
|
||||
trackerErrorItem->setData(Qt::DisplayRole, formatItemText(TRACKERERROR_ROW, 0));
|
||||
trackerErrorItem->setData(Qt::DecorationRole, UIThemeManager::instance()->getIcon(u"tracker-error"_s, u"dialog-error"_s));
|
||||
auto *otherErrorItem = new QListWidgetItem(this);
|
||||
otherErrorItem->setData(Qt::DisplayRole, formatItemText(OTHERERROR_ROW, 0));
|
||||
otherErrorItem->setData(Qt::DecorationRole, UIThemeManager::instance()->getIcon(u"tracker-error"_s, u"dialog-error"_s));
|
||||
auto *warningItem = new QListWidgetItem(this);
|
||||
warningItem->setData(Qt::DisplayRole, formatItemText(WARNING_ROW, 0));
|
||||
warningItem->setData(Qt::DecorationRole, UIThemeManager::instance()->getIcon(u"tracker-warning"_s, u"dialog-warning"_s));
|
||||
|
||||
m_trackers[NULL_HOST] = {{}, trackerlessItem};
|
||||
m_trackers[NULL_HOST] = {0, trackerlessItem};
|
||||
|
||||
handleTorrentsLoaded(BitTorrent::Session::instance()->torrents());
|
||||
const auto *pref = Preferences::instance();
|
||||
const bool useSeparateTrackerStatusFilter = pref->useSeparateTrackerStatusFilter();
|
||||
if (useSeparateTrackerStatusFilter == m_handleTrackerStatuses)
|
||||
enableTrackerStatusItems(!useSeparateTrackerStatusFilter);
|
||||
connect(pref, &Preferences::changed, this, [this, pref]
|
||||
{
|
||||
const bool useSeparateTrackerStatusFilter = pref->useSeparateTrackerStatusFilter();
|
||||
if (useSeparateTrackerStatusFilter == m_handleTrackerStatuses)
|
||||
{
|
||||
enableTrackerStatusItems(!useSeparateTrackerStatusFilter);
|
||||
updateGeometry();
|
||||
if (m_handleTrackerStatuses)
|
||||
applyFilter(currentRow());
|
||||
}
|
||||
});
|
||||
|
||||
const auto *btSession = BitTorrent::Session::instance();
|
||||
handleTorrentsLoaded(btSession->torrents());
|
||||
|
||||
connect(btSession, &BitTorrent::Session::trackersAdded, this, &TrackersFilterWidget::handleTorrentTrackersAdded);
|
||||
connect(btSession, &BitTorrent::Session::trackersRemoved, this, &TrackersFilterWidget::handleTorrentTrackersRemoved);
|
||||
connect(btSession, &BitTorrent::Session::trackersReset, this, &TrackersFilterWidget::handleTorrentTrackersReset);
|
||||
connect(btSession, &BitTorrent::Session::trackerEntryStatusesUpdated, this, &TrackersFilterWidget::handleTorrentTrackerStatusesUpdated);
|
||||
|
||||
setCurrentRow(0, QItemSelectionModel::SelectCurrent);
|
||||
toggleFilter(Preferences::instance()->getTrackerFilterState());
|
||||
}
|
||||
|
||||
TrackersFilterWidget::~TrackersFilterWidget()
|
||||
@ -155,83 +186,64 @@ TrackersFilterWidget::~TrackersFilterWidget()
|
||||
Utils::Fs::removeFile(iconPath);
|
||||
}
|
||||
|
||||
void TrackersFilterWidget::addTrackers(const BitTorrent::Torrent *torrent, const QList<BitTorrent::TrackerEntry> &trackers)
|
||||
void TrackersFilterWidget::handleTorrentTrackersAdded(const BitTorrent::Torrent *torrent, const QList<BitTorrent::TrackerEntry> &trackers)
|
||||
{
|
||||
const BitTorrent::TorrentID torrentID = torrent->id();
|
||||
const QSet<QString> prevTrackerURLs = extractTrackerURLs(torrent->trackers()).subtract(extractTrackerURLs(trackers));
|
||||
const QSet<QString> addedTrackerHosts = extractTrackerHosts(trackers).subtract(extractTrackerHosts(prevTrackerURLs));
|
||||
|
||||
for (const BitTorrent::TrackerEntry &tracker : trackers)
|
||||
addItems(tracker.url, {torrentID});
|
||||
for (const QString &trackerHost : addedTrackerHosts)
|
||||
increaseTorrentsCount(trackerHost, 1);
|
||||
|
||||
removeItem(NULL_HOST, torrentID);
|
||||
if (prevTrackerURLs.isEmpty())
|
||||
decreaseTorrentsCount(NULL_HOST); // torrent was trackerless previously
|
||||
}
|
||||
|
||||
void TrackersFilterWidget::removeTrackers(const BitTorrent::Torrent *torrent, const QStringList &trackers)
|
||||
void TrackersFilterWidget::handleTorrentTrackersRemoved(const BitTorrent::Torrent *torrent, const QStringList &trackers)
|
||||
{
|
||||
const BitTorrent::TorrentID torrentID = torrent->id();
|
||||
const QList<BitTorrent::TrackerEntryStatus> currentTrackerEntries = torrent->trackers();
|
||||
const QSet<QString> removedTrackerHosts = extractTrackerHosts(trackers).subtract(extractTrackerHosts(currentTrackerEntries));
|
||||
for (const QString &trackerHost : removedTrackerHosts)
|
||||
decreaseTorrentsCount(trackerHost);
|
||||
|
||||
for (const QString &tracker : trackers)
|
||||
removeItem(tracker, torrentID);
|
||||
if (currentTrackerEntries.isEmpty())
|
||||
increaseTorrentsCount(NULL_HOST, 1);
|
||||
|
||||
if (torrent->trackers().isEmpty())
|
||||
addItems(NULL_HOST, {torrentID});
|
||||
if (m_handleTrackerStatuses)
|
||||
refreshStatusItems(torrent);
|
||||
}
|
||||
|
||||
void TrackersFilterWidget::refreshTrackers(const BitTorrent::Torrent *torrent)
|
||||
void TrackersFilterWidget::handleTorrentTrackersReset(const BitTorrent::Torrent *torrent
|
||||
, const QList<BitTorrent::TrackerEntryStatus> &oldEntries, const QList<BitTorrent::TrackerEntry> &newEntries)
|
||||
{
|
||||
const BitTorrent::TorrentID torrentID = torrent->id();
|
||||
|
||||
m_errors.remove(torrentID);
|
||||
m_trackerErrors.remove(torrentID);
|
||||
m_warnings.remove(torrentID);
|
||||
|
||||
Algorithm::removeIf(m_trackers, [this, &torrentID](const QString &host, TrackerData &trackerData)
|
||||
if (oldEntries.isEmpty())
|
||||
{
|
||||
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(formatItemText(host, torrentIDs.size()));
|
||||
return false;
|
||||
});
|
||||
|
||||
const QList<BitTorrent::TrackerEntryStatus> trackers = torrent->trackers();
|
||||
if (trackers.isEmpty())
|
||||
{
|
||||
addItems(NULL_HOST, {torrentID});
|
||||
decreaseTorrentsCount(NULL_HOST);
|
||||
}
|
||||
else
|
||||
{
|
||||
for (const BitTorrent::TrackerEntryStatus &status : trackers)
|
||||
addItems(status.url, {torrentID});
|
||||
for (const QString &trackerHost : asConst(extractTrackerHosts(oldEntries)))
|
||||
decreaseTorrentsCount(trackerHost);
|
||||
}
|
||||
|
||||
item(OTHERERROR_ROW)->setText(formatItemText(OTHERERROR_ROW, m_errors.size()));
|
||||
item(TRACKERERROR_ROW)->setText(formatItemText(TRACKERERROR_ROW, m_trackerErrors.size()));
|
||||
item(WARNING_ROW)->setText(formatItemText(WARNING_ROW, m_warnings.size()));
|
||||
|
||||
if (const int row = currentRow(); (row == OTHERERROR_ROW)
|
||||
|| (row == TRACKERERROR_ROW) || (row == WARNING_ROW))
|
||||
if (newEntries.isEmpty())
|
||||
{
|
||||
applyFilter(row);
|
||||
increaseTorrentsCount(NULL_HOST, 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
for (const QString &trackerHost : asConst(extractTrackerHosts(newEntries)))
|
||||
increaseTorrentsCount(trackerHost, 1);
|
||||
}
|
||||
|
||||
if (m_handleTrackerStatuses)
|
||||
refreshStatusItems(torrent);
|
||||
|
||||
updateGeometry();
|
||||
}
|
||||
|
||||
void TrackersFilterWidget::addItems(const QString &trackerURL, const QList<BitTorrent::TorrentID> &torrents)
|
||||
void TrackersFilterWidget::increaseTorrentsCount(const QString &trackerHost, const qsizetype torrentsCount)
|
||||
{
|
||||
const QString host = getHost(trackerURL);
|
||||
auto trackersIt = m_trackers.find(host);
|
||||
auto trackersIt = m_trackers.find(trackerHost);
|
||||
const bool exists = (trackersIt != m_trackers.end());
|
||||
QListWidgetItem *trackerItem = nullptr;
|
||||
|
||||
@ -244,33 +256,27 @@ void TrackersFilterWidget::addItems(const QString &trackerURL, const QList<BitTo
|
||||
trackerItem = new QListWidgetItem();
|
||||
trackerItem->setData(Qt::DecorationRole, UIThemeManager::instance()->getIcon(u"trackers"_s, u"network-server"_s));
|
||||
|
||||
const TrackerData trackerData {{}, trackerItem};
|
||||
trackersIt = m_trackers.insert(host, trackerData);
|
||||
const TrackerData trackerData {0, trackerItem};
|
||||
trackersIt = m_trackers.insert(trackerHost, trackerData);
|
||||
|
||||
const QString scheme = getScheme(trackerURL);
|
||||
downloadFavicon(host, u"%1://%2/favicon.ico"_s.arg((scheme.startsWith(u"http") ? scheme : u"http"_s), getFaviconHost(host)));
|
||||
const QString scheme = getScheme(trackerHost);
|
||||
downloadFavicon(trackerHost, u"%1://%2/favicon.ico"_s.arg((scheme.startsWith(u"http") ? scheme : u"http"_s), getFaviconHost(trackerHost)));
|
||||
}
|
||||
|
||||
Q_ASSERT(trackerItem);
|
||||
|
||||
QSet<BitTorrent::TorrentID> &torrentIDs = trackersIt->torrents;
|
||||
for (const BitTorrent::TorrentID &torrentID : torrents)
|
||||
torrentIDs.insert(torrentID);
|
||||
trackersIt->torrentsCount += torrentsCount;
|
||||
|
||||
trackerItem->setText(formatItemText(host, torrentIDs.size()));
|
||||
trackerItem->setText(formatItemText(trackerHost, trackersIt->torrentsCount));
|
||||
if (exists)
|
||||
{
|
||||
if (item(currentRow()) == trackerItem)
|
||||
applyFilter(currentRow());
|
||||
return;
|
||||
}
|
||||
|
||||
Q_ASSERT(count() >= NUM_SPECIAL_ROWS);
|
||||
Q_ASSERT(count() >= numSpecialRows());
|
||||
const Utils::Compare::NaturalLessThan<Qt::CaseSensitive> naturalLessThan {};
|
||||
int insPos = count();
|
||||
for (int i = NUM_SPECIAL_ROWS; i < count(); ++i)
|
||||
for (int i = numSpecialRows(); i < count(); ++i)
|
||||
{
|
||||
if (naturalLessThan(host, item(i)->text()))
|
||||
if (naturalLessThan(trackerHost, item(i)->text()))
|
||||
{
|
||||
insPos = i;
|
||||
break;
|
||||
@ -280,88 +286,59 @@ void TrackersFilterWidget::addItems(const QString &trackerURL, const QList<BitTo
|
||||
updateGeometry();
|
||||
}
|
||||
|
||||
void TrackersFilterWidget::removeItem(const QString &trackerURL, const BitTorrent::TorrentID &id)
|
||||
void TrackersFilterWidget::decreaseTorrentsCount(const QString &trackerHost)
|
||||
{
|
||||
const QString host = getHost(trackerURL);
|
||||
const auto iter = m_trackers.find(trackerHost);
|
||||
Q_ASSERT(iter != m_trackers.end());
|
||||
if (iter == m_trackers.end()) [[unlikely]]
|
||||
return;
|
||||
|
||||
QSet<BitTorrent::TorrentID> torrentIDs = m_trackers.value(host).torrents;
|
||||
torrentIDs.remove(id);
|
||||
TrackerData &trackerData = iter.value();
|
||||
Q_ASSERT(trackerData.torrentsCount > 0);
|
||||
if (trackerData.torrentsCount <= 0) [[unlikely]]
|
||||
return;
|
||||
|
||||
QListWidgetItem *trackerItem = nullptr;
|
||||
--trackerData.torrentsCount;
|
||||
|
||||
if (!host.isEmpty())
|
||||
if (trackerData.torrentsCount == 0)
|
||||
{
|
||||
// Remove from 'Error', 'Tracker error' and 'Warning' view
|
||||
if (const auto errorHashesIt = m_errors.find(id)
|
||||
; errorHashesIt != m_errors.end())
|
||||
{
|
||||
QSet<QString> &errored = *errorHashesIt;
|
||||
errored.remove(trackerURL);
|
||||
if (errored.isEmpty())
|
||||
{
|
||||
m_errors.erase(errorHashesIt);
|
||||
item(OTHERERROR_ROW)->setText(formatItemText(OTHERERROR_ROW, m_errors.size()));
|
||||
if (currentRow() == OTHERERROR_ROW)
|
||||
applyFilter(OTHERERROR_ROW);
|
||||
}
|
||||
}
|
||||
|
||||
if (const auto trackerErrorHashesIt = m_trackerErrors.find(id)
|
||||
; trackerErrorHashesIt != m_trackerErrors.end())
|
||||
{
|
||||
QSet<QString> &errored = *trackerErrorHashesIt;
|
||||
errored.remove(trackerURL);
|
||||
if (errored.isEmpty())
|
||||
{
|
||||
m_trackerErrors.erase(trackerErrorHashesIt);
|
||||
item(TRACKERERROR_ROW)->setText(formatItemText(TRACKERERROR_ROW, m_trackerErrors.size()));
|
||||
if (currentRow() == TRACKERERROR_ROW)
|
||||
applyFilter(TRACKERERROR_ROW);
|
||||
}
|
||||
}
|
||||
|
||||
if (const auto warningHashesIt = m_warnings.find(id)
|
||||
; warningHashesIt != m_warnings.end())
|
||||
{
|
||||
QSet<QString> &warned = *warningHashesIt;
|
||||
warned.remove(trackerURL);
|
||||
if (warned.isEmpty())
|
||||
{
|
||||
m_warnings.erase(warningHashesIt);
|
||||
item(WARNING_ROW)->setText(formatItemText(WARNING_ROW, 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)"_s.arg(host, QString::number(torrentIDs.size())));
|
||||
if (currentItem() == trackerData.item)
|
||||
setCurrentRow(0, QItemSelectionModel::SelectCurrent);
|
||||
delete trackerData.item;
|
||||
m_trackers.erase(iter);
|
||||
updateGeometry();
|
||||
}
|
||||
else
|
||||
{
|
||||
trackerItem = item(TRACKERLESS_ROW);
|
||||
trackerItem->setText(formatItemText(TRACKERLESS_ROW, torrentIDs.size()));
|
||||
trackerData.item->setText(formatItemText(trackerHost, trackerData.torrentsCount));
|
||||
}
|
||||
|
||||
m_trackers.insert(host, {torrentIDs, trackerItem});
|
||||
|
||||
if (currentItem() == trackerItem)
|
||||
applyFilter(currentRow());
|
||||
}
|
||||
|
||||
void TrackersFilterWidget::setDownloadTrackerFavicon(bool value)
|
||||
void TrackersFilterWidget::refreshStatusItems(const BitTorrent::Torrent *torrent)
|
||||
{
|
||||
const BitTorrent::TorrentAnnounceStatus announceStatus = torrent->announceStatus();
|
||||
|
||||
if (announceStatus.testFlag(BitTorrent::TorrentAnnounceStatusFlag::HasWarning))
|
||||
m_warnings.insert(torrent);
|
||||
else
|
||||
m_warnings.remove(torrent);
|
||||
|
||||
if (announceStatus.testFlag(BitTorrent::TorrentAnnounceStatusFlag::HasTrackerError))
|
||||
m_trackerErrors.insert(torrent);
|
||||
else
|
||||
m_trackerErrors.remove(torrent);
|
||||
|
||||
if (announceStatus.testFlag(BitTorrent::TorrentAnnounceStatusFlag::HasOtherError))
|
||||
m_errors.insert(torrent);
|
||||
else
|
||||
m_errors.remove(torrent);
|
||||
|
||||
item(OTHERERROR_ROW)->setText(formatItemText(OTHERERROR_ROW, m_errors.size()));
|
||||
item(TRACKERERROR_ROW)->setText(formatItemText(TRACKERERROR_ROW, m_trackerErrors.size()));
|
||||
item(WARNING_ROW)->setText(formatItemText(WARNING_ROW, m_warnings.size()));
|
||||
}
|
||||
|
||||
void TrackersFilterWidget::setDownloadTrackerFavicon(const bool value)
|
||||
{
|
||||
if (value == m_downloadTrackerFavicon) return;
|
||||
m_downloadTrackerFavicon = value;
|
||||
@ -381,107 +358,11 @@ void TrackersFilterWidget::setDownloadTrackerFavicon(bool value)
|
||||
}
|
||||
}
|
||||
|
||||
void TrackersFilterWidget::handleTrackerStatusesUpdated(const BitTorrent::Torrent *torrent
|
||||
, const QHash<QString, BitTorrent::TrackerEntryStatus> &updatedTrackers)
|
||||
void TrackersFilterWidget::handleTorrentTrackerStatusesUpdated(const BitTorrent::Torrent *torrent
|
||||
, [[maybe_unused]] const QHash<QString, BitTorrent::TrackerEntryStatus> &updatedTrackers)
|
||||
{
|
||||
const BitTorrent::TorrentID id = torrent->id();
|
||||
|
||||
auto errorHashesIt = m_errors.find(id);
|
||||
auto trackerErrorHashesIt = m_trackerErrors.find(id);
|
||||
auto warningHashesIt = m_warnings.find(id);
|
||||
|
||||
for (const BitTorrent::TrackerEntryStatus &trackerEntryStatus : updatedTrackers)
|
||||
{
|
||||
switch (trackerEntryStatus.state)
|
||||
{
|
||||
case BitTorrent::TrackerEndpointState::Working:
|
||||
{
|
||||
// remove tracker from "error" and "tracker error" categories
|
||||
if (errorHashesIt != m_errors.end())
|
||||
errorHashesIt->remove(trackerEntryStatus.url);
|
||||
if (trackerErrorHashesIt != m_trackerErrors.end())
|
||||
trackerErrorHashesIt->remove(trackerEntryStatus.url);
|
||||
|
||||
const bool hasNoWarningMessages = std::ranges::all_of(trackerEntryStatus.endpoints
|
||||
, [](const BitTorrent::TrackerEndpointStatus &endpointEntry)
|
||||
{
|
||||
return endpointEntry.message.isEmpty() || (endpointEntry.state != BitTorrent::TrackerEndpointState::Working);
|
||||
});
|
||||
if (hasNoWarningMessages)
|
||||
{
|
||||
if (warningHashesIt != m_warnings.end())
|
||||
{
|
||||
warningHashesIt->remove(trackerEntryStatus.url);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (warningHashesIt == m_warnings.end())
|
||||
warningHashesIt = m_warnings.insert(id, {});
|
||||
warningHashesIt->insert(trackerEntryStatus.url);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case BitTorrent::TrackerEndpointState::NotWorking:
|
||||
case BitTorrent::TrackerEndpointState::Unreachable:
|
||||
{
|
||||
// remove tracker from "tracker error" and "warning" categories
|
||||
if (warningHashesIt != m_warnings.end())
|
||||
warningHashesIt->remove(trackerEntryStatus.url);
|
||||
if (trackerErrorHashesIt != m_trackerErrors.end())
|
||||
trackerErrorHashesIt->remove(trackerEntryStatus.url);
|
||||
|
||||
if (errorHashesIt == m_errors.end())
|
||||
errorHashesIt = m_errors.insert(id, {});
|
||||
errorHashesIt->insert(trackerEntryStatus.url);
|
||||
}
|
||||
break;
|
||||
|
||||
case BitTorrent::TrackerEndpointState::TrackerError:
|
||||
{
|
||||
// remove tracker from "error" and "warning" categories
|
||||
if (warningHashesIt != m_warnings.end())
|
||||
warningHashesIt->remove(trackerEntryStatus.url);
|
||||
if (errorHashesIt != m_errors.end())
|
||||
errorHashesIt->remove(trackerEntryStatus.url);
|
||||
|
||||
if (trackerErrorHashesIt == m_trackerErrors.end())
|
||||
trackerErrorHashesIt = m_trackerErrors.insert(id, {});
|
||||
trackerErrorHashesIt->insert(trackerEntryStatus.url);
|
||||
}
|
||||
break;
|
||||
|
||||
case BitTorrent::TrackerEndpointState::NotContacted:
|
||||
{
|
||||
// remove tracker from "error", "tracker error" and "warning" categories
|
||||
if (warningHashesIt != m_warnings.end())
|
||||
warningHashesIt->remove(trackerEntryStatus.url);
|
||||
if (errorHashesIt != m_errors.end())
|
||||
errorHashesIt->remove(trackerEntryStatus.url);
|
||||
if (trackerErrorHashesIt != m_trackerErrors.end())
|
||||
trackerErrorHashesIt->remove(trackerEntryStatus.url);
|
||||
}
|
||||
break;
|
||||
};
|
||||
}
|
||||
|
||||
if ((errorHashesIt != m_errors.end()) && errorHashesIt->isEmpty())
|
||||
m_errors.erase(errorHashesIt);
|
||||
if ((trackerErrorHashesIt != m_trackerErrors.end()) && trackerErrorHashesIt->isEmpty())
|
||||
m_trackerErrors.erase(trackerErrorHashesIt);
|
||||
if ((warningHashesIt != m_warnings.end()) && warningHashesIt->isEmpty())
|
||||
m_warnings.erase(warningHashesIt);
|
||||
|
||||
item(OTHERERROR_ROW)->setText(formatItemText(OTHERERROR_ROW, m_errors.size()));
|
||||
item(TRACKERERROR_ROW)->setText(formatItemText(TRACKERERROR_ROW, m_trackerErrors.size()));
|
||||
item(WARNING_ROW)->setText(formatItemText(WARNING_ROW, m_warnings.size()));
|
||||
|
||||
if (const int row = currentRow(); (row == OTHERERROR_ROW)
|
||||
|| (row == TRACKERERROR_ROW) || (row == WARNING_ROW))
|
||||
{
|
||||
applyFilter(row);
|
||||
}
|
||||
if (m_handleTrackerStatuses)
|
||||
refreshStatusItems(torrent);
|
||||
}
|
||||
|
||||
void TrackersFilterWidget::downloadFavicon(const QString &trackerHost, const QString &faviconURL)
|
||||
@ -500,19 +381,14 @@ void TrackersFilterWidget::downloadFavicon(const QString &trackerHost, const QSt
|
||||
downloadingFaviconNode.insert(trackerHost);
|
||||
}
|
||||
|
||||
void TrackersFilterWidget::removeTracker(const QString &tracker)
|
||||
void TrackersFilterWidget::removeTracker(const QString &trackerHost)
|
||||
{
|
||||
for (const BitTorrent::TorrentID &torrentID : asConst(m_trackers.value(tracker).torrents))
|
||||
for (BitTorrent::Torrent *torrent : asConst(BitTorrent::Session::instance()->torrents()))
|
||||
{
|
||||
auto *torrent = BitTorrent::Session::instance()->getTorrent(torrentID);
|
||||
Q_ASSERT(torrent);
|
||||
if (!torrent) [[unlikely]]
|
||||
continue;
|
||||
|
||||
QStringList trackersToRemove;
|
||||
for (const BitTorrent::TrackerEntryStatus &trackerEntryStatus : asConst(torrent->trackers()))
|
||||
{
|
||||
if ((trackerEntryStatus.url == tracker) || (QUrl(trackerEntryStatus.url).host() == tracker))
|
||||
if (getTrackerHost(trackerEntryStatus) == trackerHost)
|
||||
trackersToRemove.append(trackerEntryStatus.url);
|
||||
}
|
||||
|
||||
@ -522,6 +398,72 @@ void TrackersFilterWidget::removeTracker(const QString &tracker)
|
||||
updateGeometry();
|
||||
}
|
||||
|
||||
void TrackersFilterWidget::enableTrackerStatusItems(const bool value)
|
||||
{
|
||||
m_handleTrackerStatuses = value;
|
||||
if (m_handleTrackerStatuses)
|
||||
{
|
||||
auto *trackerErrorItem = new QListWidgetItem;
|
||||
trackerErrorItem->setData(Qt::DisplayRole, formatItemText(TRACKERERROR_ROW, 0));
|
||||
trackerErrorItem->setData(Qt::DecorationRole, UIThemeManager::instance()->getIcon(u"tracker-error"_s, u"dialog-error"_s));
|
||||
insertItem(TRACKERERROR_ROW, trackerErrorItem);
|
||||
|
||||
auto *otherErrorItem = new QListWidgetItem;
|
||||
otherErrorItem->setData(Qt::DisplayRole, formatItemText(OTHERERROR_ROW, 0));
|
||||
otherErrorItem->setData(Qt::DecorationRole, UIThemeManager::instance()->getIcon(u"tracker-error"_s, u"dialog-error"_s));
|
||||
insertItem(OTHERERROR_ROW, otherErrorItem);
|
||||
|
||||
auto *warningItem = new QListWidgetItem;
|
||||
warningItem->setData(Qt::DisplayRole, formatItemText(WARNING_ROW, 0));
|
||||
warningItem->setData(Qt::DecorationRole, UIThemeManager::instance()->getIcon(u"tracker-warning"_s, u"dialog-warning"_s));
|
||||
insertItem(WARNING_ROW, warningItem);
|
||||
|
||||
const QList<BitTorrent::Torrent *> torrents = BitTorrent::Session::instance()->torrents();
|
||||
for (const BitTorrent::Torrent *torrent : torrents)
|
||||
{
|
||||
const BitTorrent::TorrentAnnounceStatus announceStatus = torrent->announceStatus();
|
||||
|
||||
if (announceStatus.testFlag(BitTorrent::TorrentAnnounceStatusFlag::HasWarning))
|
||||
m_warnings.insert(torrent);
|
||||
|
||||
if (announceStatus.testFlag(BitTorrent::TorrentAnnounceStatusFlag::HasTrackerError))
|
||||
m_trackerErrors.insert(torrent);
|
||||
|
||||
if (announceStatus.testFlag(BitTorrent::TorrentAnnounceStatusFlag::HasOtherError))
|
||||
m_errors.insert(torrent);
|
||||
}
|
||||
|
||||
warningItem->setText(formatItemText(WARNING_ROW, m_warnings.size()));
|
||||
trackerErrorItem->setText(formatItemText(TRACKERERROR_ROW, m_trackerErrors.size()));
|
||||
otherErrorItem->setText(formatItemText(OTHERERROR_ROW, m_errors.size()));
|
||||
}
|
||||
else
|
||||
{
|
||||
if (const int row = currentRow();
|
||||
(row == WARNING_ROW) || (row == TRACKERERROR_ROW) || (row == OTHERERROR_ROW))
|
||||
{
|
||||
setCurrentRow(0, QItemSelectionModel::ClearAndSelect);
|
||||
}
|
||||
|
||||
// Need to be removed in reversed order
|
||||
takeItem(WARNING_ROW);
|
||||
takeItem(OTHERERROR_ROW);
|
||||
takeItem(TRACKERERROR_ROW);
|
||||
|
||||
m_warnings.clear();
|
||||
m_trackerErrors.clear();
|
||||
m_errors.clear();
|
||||
}
|
||||
}
|
||||
|
||||
qsizetype TrackersFilterWidget::numSpecialRows() const
|
||||
{
|
||||
if (m_handleTrackerStatuses)
|
||||
return NUM_SPECIAL_ROWS;
|
||||
|
||||
return NUM_SPECIAL_ROWS - 3;
|
||||
}
|
||||
|
||||
void TrackersFilterWidget::handleFavicoDownloadFinished(const Net::DownloadResult &result)
|
||||
{
|
||||
const QSet<QString> trackerHosts = m_downloadingFavicons.take(result.url);
|
||||
@ -590,7 +532,7 @@ void TrackersFilterWidget::showMenu()
|
||||
QMenu *menu = new QMenu(this);
|
||||
menu->setAttribute(Qt::WA_DeleteOnClose);
|
||||
|
||||
if (currentRow() >= NUM_SPECIAL_ROWS)
|
||||
if (currentRow() >= numSpecialRows())
|
||||
{
|
||||
menu->addAction(UIThemeManager::instance()->getIcon(u"edit-clear"_s, u"list-remove"_s), tr("Remove tracker")
|
||||
, this, &TrackersFilterWidget::onRemoveTrackerTriggered);
|
||||
@ -609,30 +551,80 @@ void TrackersFilterWidget::showMenu()
|
||||
|
||||
void TrackersFilterWidget::applyFilter(const int row)
|
||||
{
|
||||
if (row == ALL_ROW)
|
||||
transferList()->applyTrackerFilterAll();
|
||||
else if (isVisible())
|
||||
transferList()->applyTrackerFilter(getTorrentIDs(row));
|
||||
if (m_handleTrackerStatuses)
|
||||
{
|
||||
switch (row)
|
||||
{
|
||||
case ALL_ROW:
|
||||
transferList()->applyTrackerFilter(std::nullopt);
|
||||
transferList()->applyAnnounceStatusFilter(std::nullopt);
|
||||
break;
|
||||
|
||||
case TRACKERLESS_ROW:
|
||||
transferList()->applyTrackerFilter(NULL_HOST);
|
||||
transferList()->applyAnnounceStatusFilter(std::nullopt);
|
||||
break;
|
||||
|
||||
case OTHERERROR_ROW:
|
||||
transferList()->applyAnnounceStatusFilter(BitTorrent::TorrentAnnounceStatusFlag::HasOtherError);
|
||||
transferList()->applyTrackerFilter(std::nullopt);
|
||||
break;
|
||||
|
||||
case TRACKERERROR_ROW:
|
||||
transferList()->applyAnnounceStatusFilter(BitTorrent::TorrentAnnounceStatusFlag::HasTrackerError);
|
||||
transferList()->applyTrackerFilter(std::nullopt);
|
||||
break;
|
||||
|
||||
case WARNING_ROW:
|
||||
transferList()->applyAnnounceStatusFilter(BitTorrent::TorrentAnnounceStatusFlag::HasWarning);
|
||||
transferList()->applyTrackerFilter(std::nullopt);
|
||||
break;
|
||||
|
||||
default:
|
||||
transferList()->applyTrackerFilter(trackerFromRow(row));
|
||||
transferList()->applyAnnounceStatusFilter(std::nullopt);
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (row)
|
||||
{
|
||||
case ALL_ROW:
|
||||
transferList()->applyTrackerFilter(std::nullopt);
|
||||
break;
|
||||
|
||||
case TRACKERLESS_ROW:
|
||||
transferList()->applyTrackerFilter(NULL_HOST);
|
||||
break;
|
||||
|
||||
default:
|
||||
transferList()->applyTrackerFilter(trackerFromRow(row));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TrackersFilterWidget::handleTorrentsLoaded(const QList<BitTorrent::Torrent *> &torrents)
|
||||
{
|
||||
QHash<QString, QList<BitTorrent::TorrentID>> torrentsPerTracker;
|
||||
QHash<QString, qsizetype> torrentsPerTrackerHost;
|
||||
for (const BitTorrent::Torrent *torrent : torrents)
|
||||
{
|
||||
const BitTorrent::TorrentID torrentID = torrent->id();
|
||||
const QList<BitTorrent::TrackerEntryStatus> trackers = torrent->trackers();
|
||||
for (const BitTorrent::TrackerEntryStatus &tracker : trackers)
|
||||
torrentsPerTracker[tracker.url].append(torrentID);
|
||||
|
||||
// Check for trackerless torrent
|
||||
if (trackers.isEmpty())
|
||||
torrentsPerTracker[NULL_HOST].append(torrentID);
|
||||
if (const QList<BitTorrent::TrackerEntryStatus> trackers = torrent->trackers(); trackers.isEmpty())
|
||||
{
|
||||
++torrentsPerTrackerHost[NULL_HOST];
|
||||
}
|
||||
else
|
||||
{
|
||||
for (const QString &trackerHost : asConst(extractTrackerHosts(trackers)))
|
||||
++torrentsPerTrackerHost[trackerHost];
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto &[trackerURL, torrents] : asConst(torrentsPerTracker).asKeyValueRange())
|
||||
for (const auto &[trackerHost, torrentsCount] : asConst(torrentsPerTrackerHost).asKeyValueRange())
|
||||
{
|
||||
addItems(trackerURL, torrents);
|
||||
increaseTorrentsCount(trackerHost, torrentsCount);
|
||||
}
|
||||
|
||||
m_totalTorrents += torrents.count();
|
||||
@ -641,22 +633,35 @@ void TrackersFilterWidget::handleTorrentsLoaded(const QList<BitTorrent::Torrent
|
||||
|
||||
void TrackersFilterWidget::torrentAboutToBeDeleted(BitTorrent::Torrent *const torrent)
|
||||
{
|
||||
const BitTorrent::TorrentID torrentID = torrent->id();
|
||||
const QList<BitTorrent::TrackerEntryStatus> trackers = torrent->trackers();
|
||||
for (const BitTorrent::TrackerEntryStatus &tracker : trackers)
|
||||
removeItem(tracker.url, torrentID);
|
||||
|
||||
// Check for trackerless torrent
|
||||
if (trackers.isEmpty())
|
||||
removeItem(NULL_HOST, torrentID);
|
||||
if (const QList<BitTorrent::TrackerEntryStatus> trackers = torrent->trackers(); trackers.isEmpty())
|
||||
{
|
||||
decreaseTorrentsCount(NULL_HOST);
|
||||
}
|
||||
else
|
||||
{
|
||||
for (const QString &trackerHost : asConst(extractTrackerHosts(trackers)))
|
||||
decreaseTorrentsCount(trackerHost);
|
||||
}
|
||||
|
||||
item(ALL_ROW)->setText(formatItemText(ALL_ROW, --m_totalTorrents));
|
||||
|
||||
if (m_handleTrackerStatuses)
|
||||
{
|
||||
m_warnings.remove(torrent);
|
||||
m_trackerErrors.remove(torrent);
|
||||
m_errors.remove(torrent);
|
||||
|
||||
item(WARNING_ROW)->setText(formatItemText(WARNING_ROW, m_warnings.size()));
|
||||
item(TRACKERERROR_ROW)->setText(formatItemText(TRACKERERROR_ROW, m_trackerErrors.size()));
|
||||
item(OTHERERROR_ROW)->setText(formatItemText(OTHERERROR_ROW, m_errors.size()));
|
||||
}
|
||||
}
|
||||
|
||||
void TrackersFilterWidget::onRemoveTrackerTriggered()
|
||||
{
|
||||
const int row = currentRow();
|
||||
if (row < NUM_SPECIAL_ROWS)
|
||||
if (row < numSpecialRows())
|
||||
return;
|
||||
|
||||
const QString &tracker = trackerFromRow(row);
|
||||
@ -694,27 +699,10 @@ QString TrackersFilterWidget::trackerFromRow(int row) const
|
||||
int TrackersFilterWidget::rowFromTracker(const QString &tracker) const
|
||||
{
|
||||
Q_ASSERT(!tracker.isEmpty());
|
||||
for (int i = NUM_SPECIAL_ROWS; i < count(); ++i)
|
||||
for (int i = numSpecialRows(); 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 OTHERERROR_ROW:
|
||||
return {m_errors.keyBegin(), m_errors.keyEnd()};
|
||||
case TRACKERERROR_ROW:
|
||||
return {m_trackerErrors.keyBegin(), m_trackerErrors.keyEnd()};
|
||||
case WARNING_ROW:
|
||||
return {m_warnings.keyBegin(), m_warnings.keyEnd()};
|
||||
default:
|
||||
return m_trackers.value(trackerFromRow(row)).torrents;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2023 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2023-2025 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
@ -57,11 +57,6 @@ public:
|
||||
TrackersFilterWidget(QWidget *parent, TransferListWidget *transferList, bool downloadFavicon);
|
||||
~TrackersFilterWidget() override;
|
||||
|
||||
void addTrackers(const BitTorrent::Torrent *torrent, const QList<BitTorrent::TrackerEntry> &trackers);
|
||||
void removeTrackers(const BitTorrent::Torrent *torrent, const QStringList &trackers);
|
||||
void refreshTrackers(const BitTorrent::Torrent *torrent);
|
||||
void handleTrackerStatusesUpdated(const BitTorrent::Torrent *torrent
|
||||
, const QHash<QString, BitTorrent::TrackerEntryStatus> &updatedTrackers);
|
||||
void setDownloadTrackerFavicon(bool value);
|
||||
|
||||
private slots:
|
||||
@ -75,28 +70,40 @@ private:
|
||||
void handleTorrentsLoaded(const QList<BitTorrent::Torrent *> &torrents) override;
|
||||
void torrentAboutToBeDeleted(BitTorrent::Torrent *torrent) override;
|
||||
|
||||
void handleTorrentTrackersAdded(const BitTorrent::Torrent *torrent, const QList<BitTorrent::TrackerEntry> &trackers);
|
||||
void handleTorrentTrackersRemoved(const BitTorrent::Torrent *torrent, const QStringList &trackers);
|
||||
void handleTorrentTrackersReset(const BitTorrent::Torrent *torrent, const QList<BitTorrent::TrackerEntryStatus> &oldEntries
|
||||
, const QList<BitTorrent::TrackerEntry> &newEntries);
|
||||
void handleTorrentTrackerStatusesUpdated(const BitTorrent::Torrent *torrent
|
||||
, const QHash<QString, BitTorrent::TrackerEntryStatus> &updatedTrackers);
|
||||
|
||||
void onRemoveTrackerTriggered();
|
||||
|
||||
void addItems(const QString &trackerURL, const QList<BitTorrent::TorrentID> &torrents);
|
||||
void removeItem(const QString &trackerURL, const BitTorrent::TorrentID &id);
|
||||
void increaseTorrentsCount(const QString &trackerHost, qsizetype torrentsCount);
|
||||
void decreaseTorrentsCount(const QString &trackerHost);
|
||||
void refreshStatusItems(const BitTorrent::Torrent *torrent);
|
||||
QString trackerFromRow(int row) const;
|
||||
int rowFromTracker(const QString &tracker) const;
|
||||
QSet<BitTorrent::TorrentID> getTorrentIDs(int row) const;
|
||||
void downloadFavicon(const QString &trackerHost, const QString &faviconURL);
|
||||
void removeTracker(const QString &tracker);
|
||||
void removeTracker(const QString &trackerHost);
|
||||
|
||||
void enableTrackerStatusItems(bool value);
|
||||
|
||||
qsizetype numSpecialRows() const;
|
||||
|
||||
struct TrackerData
|
||||
{
|
||||
QSet<BitTorrent::TorrentID> torrents;
|
||||
qsizetype torrentsCount = 0;
|
||||
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_trackerErrors; // <torrent ID, tracker hosts>
|
||||
QHash<BitTorrent::TorrentID, QSet<QString>> m_warnings; // <torrent ID, tracker hosts>
|
||||
QSet<const BitTorrent::Torrent *> m_errors;
|
||||
QSet<const BitTorrent::Torrent *> m_trackerErrors;
|
||||
QSet<const BitTorrent::Torrent *> m_warnings;
|
||||
PathList m_iconPaths;
|
||||
int m_totalTorrents = 0;
|
||||
bool m_downloadTrackerFavicon = false;
|
||||
bool m_handleTrackerStatuses = false;
|
||||
QHash<QString, QSet<QString>> m_downloadingFavicons; // <favicon URL, tracker hosts>
|
||||
};
|
||||
|
||||
218
src/gui/transferlistfilters/trackerstatusfilterwidget.cpp
Normal file
218
src/gui/transferlistfilters/trackerstatusfilterwidget.cpp
Normal file
@ -0,0 +1,218 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2023-2025 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 "trackerstatusfilterwidget.h"
|
||||
|
||||
#include <QCheckBox>
|
||||
#include <QIcon>
|
||||
#include <QListWidgetItem>
|
||||
#include <QMenu>
|
||||
|
||||
#include "base/bittorrent/session.h"
|
||||
#include "base/global.h"
|
||||
#include "base/preferences.h"
|
||||
#include "gui/transferlistwidget.h"
|
||||
#include "gui/uithememanager.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
enum TRACKERSTATUS_FILTER_ROW
|
||||
{
|
||||
ANY_ROW,
|
||||
WARNING_ROW,
|
||||
TRACKERERROR_ROW,
|
||||
OTHERERROR_ROW,
|
||||
|
||||
NUM_SPECIAL_ROWS
|
||||
};
|
||||
|
||||
QString getFormatStringForRow(const int row)
|
||||
{
|
||||
switch (row)
|
||||
{
|
||||
case ANY_ROW:
|
||||
return TrackerStatusFilterWidget::tr("All (%1)", "this is for the tracker filter");
|
||||
case WARNING_ROW:
|
||||
return TrackerStatusFilterWidget::tr("Warning (%1)");
|
||||
case TRACKERERROR_ROW:
|
||||
return TrackerStatusFilterWidget::tr("Tracker error (%1)");
|
||||
case OTHERERROR_ROW:
|
||||
return TrackerStatusFilterWidget::tr("Other error (%1)");
|
||||
default:
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
QString formatItemText(const int row, const int torrentsCount)
|
||||
{
|
||||
return getFormatStringForRow(row).arg(torrentsCount);
|
||||
}
|
||||
}
|
||||
|
||||
TrackerStatusFilterWidget::TrackerStatusFilterWidget(QWidget *parent, TransferListWidget *transferList)
|
||||
: BaseFilterWidget(parent, transferList)
|
||||
{
|
||||
auto *anyStatusItem = new QListWidgetItem(this);
|
||||
anyStatusItem->setData(Qt::DecorationRole, UIThemeManager::instance()->getIcon(u"trackers"_s, u"network-server"_s));
|
||||
|
||||
auto *warningItem = new QListWidgetItem(this);
|
||||
warningItem->setData(Qt::DecorationRole, UIThemeManager::instance()->getIcon(u"tracker-warning"_s, u"dialog-warning"_s));
|
||||
|
||||
auto *trackerErrorItem = new QListWidgetItem(this);
|
||||
trackerErrorItem->setData(Qt::DecorationRole, UIThemeManager::instance()->getIcon(u"tracker-error"_s, u"dialog-error"_s));
|
||||
|
||||
auto *otherErrorItem = new QListWidgetItem(this);
|
||||
otherErrorItem->setData(Qt::DecorationRole, UIThemeManager::instance()->getIcon(u"tracker-error"_s, u"dialog-error"_s));
|
||||
|
||||
const auto *btSession = BitTorrent::Session::instance();
|
||||
|
||||
const QList<BitTorrent::Torrent *> torrents = btSession->torrents();
|
||||
m_totalTorrents += torrents.count();
|
||||
|
||||
for (const BitTorrent::Torrent *torrent : torrents)
|
||||
{
|
||||
const BitTorrent::TorrentAnnounceStatus announceStatus = torrent->announceStatus();
|
||||
|
||||
if (announceStatus.testFlag(BitTorrent::TorrentAnnounceStatusFlag::HasWarning))
|
||||
m_warnings.insert(torrent);
|
||||
|
||||
if (announceStatus.testFlag(BitTorrent::TorrentAnnounceStatusFlag::HasTrackerError))
|
||||
m_trackerErrors.insert(torrent);
|
||||
|
||||
if (announceStatus.testFlag(BitTorrent::TorrentAnnounceStatusFlag::HasOtherError))
|
||||
m_errors.insert(torrent);
|
||||
}
|
||||
|
||||
connect(btSession, &BitTorrent::Session::trackersRemoved, this, &TrackerStatusFilterWidget::handleTorrentTrackersRemoved);
|
||||
connect(btSession, &BitTorrent::Session::trackersReset, this, &TrackerStatusFilterWidget::handleTorrentTrackersReset);
|
||||
connect(btSession, &BitTorrent::Session::trackerEntryStatusesUpdated, this, &TrackerStatusFilterWidget::handleTorrentTrackerStatusesUpdated);
|
||||
|
||||
anyStatusItem->setText(formatItemText(ANY_ROW, m_totalTorrents));
|
||||
warningItem->setText(formatItemText(WARNING_ROW, m_warnings.size()));
|
||||
trackerErrorItem->setText(formatItemText(TRACKERERROR_ROW, m_trackerErrors.size()));
|
||||
otherErrorItem->setText(formatItemText(OTHERERROR_ROW, m_errors.size()));
|
||||
|
||||
setCurrentRow(0, QItemSelectionModel::SelectCurrent);
|
||||
setVisible(Preferences::instance()->getTrackerStatusFilterState());
|
||||
}
|
||||
|
||||
void TrackerStatusFilterWidget::handleTorrentTrackersRemoved(const BitTorrent::Torrent *torrent)
|
||||
{
|
||||
refreshItems(torrent);
|
||||
}
|
||||
|
||||
void TrackerStatusFilterWidget::handleTorrentTrackersReset(const BitTorrent::Torrent *torrent
|
||||
, [[maybe_unused]] const QList<BitTorrent::TrackerEntryStatus> &oldEntries, [[maybe_unused]] const QList<BitTorrent::TrackerEntry> &newEntries)
|
||||
{
|
||||
refreshItems(torrent);
|
||||
}
|
||||
|
||||
void TrackerStatusFilterWidget::handleTorrentTrackerStatusesUpdated(const BitTorrent::Torrent *torrent)
|
||||
{
|
||||
refreshItems(torrent);
|
||||
}
|
||||
|
||||
void TrackerStatusFilterWidget::showMenu()
|
||||
{
|
||||
QMenu *menu = new QMenu(this);
|
||||
menu->setAttribute(Qt::WA_DeleteOnClose);
|
||||
|
||||
menu->addAction(UIThemeManager::instance()->getIcon(u"torrent-start"_s, u"media-playback-start"_s), tr("Start torrents")
|
||||
, transferList(), &TransferListWidget::startVisibleTorrents);
|
||||
menu->addAction(UIThemeManager::instance()->getIcon(u"torrent-stop"_s, u"media-playback-pause"_s), tr("Stop torrents")
|
||||
, transferList(), &TransferListWidget::stopVisibleTorrents);
|
||||
menu->addAction(UIThemeManager::instance()->getIcon(u"list-remove"_s), tr("Remove torrents")
|
||||
, transferList(), &TransferListWidget::deleteVisibleTorrents);
|
||||
|
||||
menu->popup(QCursor::pos());
|
||||
}
|
||||
|
||||
void TrackerStatusFilterWidget::applyFilter(const int row)
|
||||
{
|
||||
switch (row)
|
||||
{
|
||||
case ANY_ROW:
|
||||
transferList()->applyAnnounceStatusFilter(std::nullopt);
|
||||
break;
|
||||
|
||||
case WARNING_ROW:
|
||||
transferList()->applyAnnounceStatusFilter(BitTorrent::TorrentAnnounceStatusFlag::HasWarning);
|
||||
break;
|
||||
|
||||
case TRACKERERROR_ROW:
|
||||
transferList()->applyAnnounceStatusFilter(BitTorrent::TorrentAnnounceStatusFlag::HasTrackerError);
|
||||
break;
|
||||
|
||||
case OTHERERROR_ROW:
|
||||
transferList()->applyAnnounceStatusFilter(BitTorrent::TorrentAnnounceStatusFlag::HasOtherError);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void TrackerStatusFilterWidget::handleTorrentsLoaded(const QList<BitTorrent::Torrent *> &torrents)
|
||||
{
|
||||
m_totalTorrents += torrents.count();
|
||||
item(ANY_ROW)->setText(formatItemText(ANY_ROW, m_totalTorrents));
|
||||
}
|
||||
|
||||
void TrackerStatusFilterWidget::refreshItems(const BitTorrent::Torrent *torrent)
|
||||
{
|
||||
const BitTorrent::TorrentAnnounceStatus announceStatus = torrent->announceStatus();
|
||||
|
||||
if (announceStatus.testFlag(BitTorrent::TorrentAnnounceStatusFlag::HasWarning))
|
||||
m_warnings.insert(torrent);
|
||||
else
|
||||
m_warnings.remove(torrent);
|
||||
|
||||
if (announceStatus.testFlag(BitTorrent::TorrentAnnounceStatusFlag::HasTrackerError))
|
||||
m_trackerErrors.insert(torrent);
|
||||
else
|
||||
m_trackerErrors.remove(torrent);
|
||||
|
||||
if (announceStatus.testFlag(BitTorrent::TorrentAnnounceStatusFlag::HasOtherError))
|
||||
m_errors.insert(torrent);
|
||||
else
|
||||
m_errors.remove(torrent);
|
||||
|
||||
item(OTHERERROR_ROW)->setText(formatItemText(OTHERERROR_ROW, m_errors.size()));
|
||||
item(TRACKERERROR_ROW)->setText(formatItemText(TRACKERERROR_ROW, m_trackerErrors.size()));
|
||||
item(WARNING_ROW)->setText(formatItemText(WARNING_ROW, m_warnings.size()));
|
||||
}
|
||||
|
||||
void TrackerStatusFilterWidget::torrentAboutToBeDeleted(BitTorrent::Torrent *const torrent)
|
||||
{
|
||||
m_warnings.remove(torrent);
|
||||
m_trackerErrors.remove(torrent);
|
||||
m_errors.remove(torrent);
|
||||
|
||||
item(ANY_ROW)->setText(formatItemText(ANY_ROW, --m_totalTorrents));
|
||||
item(WARNING_ROW)->setText(formatItemText(WARNING_ROW, m_warnings.size()));
|
||||
item(TRACKERERROR_ROW)->setText(formatItemText(TRACKERERROR_ROW, m_trackerErrors.size()));
|
||||
item(OTHERERROR_ROW)->setText(formatItemText(OTHERERROR_ROW, m_errors.size()));
|
||||
}
|
||||
66
src/gui/transferlistfilters/trackerstatusfilterwidget.h
Normal file
66
src/gui/transferlistfilters/trackerstatusfilterwidget.h
Normal file
@ -0,0 +1,66 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2023-2025 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 <QSet>
|
||||
|
||||
#include "basefilterwidget.h"
|
||||
|
||||
class TransferListWidget;
|
||||
|
||||
class TrackerStatusFilterWidget final : public BaseFilterWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_DISABLE_COPY_MOVE(TrackerStatusFilterWidget)
|
||||
|
||||
public:
|
||||
TrackerStatusFilterWidget(QWidget *parent, TransferListWidget *transferList);
|
||||
|
||||
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 QList<BitTorrent::Torrent *> &torrents) override;
|
||||
void torrentAboutToBeDeleted(BitTorrent::Torrent *torrent) override;
|
||||
|
||||
void handleTorrentTrackersRemoved(const BitTorrent::Torrent *torrent);
|
||||
void handleTorrentTrackersReset(const BitTorrent::Torrent *torrent, const QList<BitTorrent::TrackerEntryStatus> &oldEntries
|
||||
, const QList<BitTorrent::TrackerEntry> &newEntries);
|
||||
void handleTorrentTrackerStatusesUpdated(const BitTorrent::Torrent *torrent);
|
||||
|
||||
void refreshItems(const BitTorrent::Torrent *torrent);
|
||||
|
||||
QSet<const BitTorrent::Torrent *> m_errors;
|
||||
QSet<const BitTorrent::Torrent *> m_trackerErrors;
|
||||
QSet<const BitTorrent::Torrent *> m_warnings;
|
||||
int m_totalTorrents = 0;
|
||||
};
|
||||
@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2023 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2023-2025 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
@ -29,58 +29,35 @@
|
||||
|
||||
#include "transferlistfilterswidget.h"
|
||||
|
||||
#include <QCheckBox>
|
||||
#include <QIcon>
|
||||
#include <QListWidgetItem>
|
||||
#include <QMenu>
|
||||
#include <QPainter>
|
||||
#include <QScrollArea>
|
||||
#include <QStyleOptionButton>
|
||||
#include <QUrl>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
#include "base/bittorrent/session.h"
|
||||
#include "base/bittorrent/torrent.h"
|
||||
#include "base/bittorrent/trackerentrystatus.h"
|
||||
#include "base/global.h"
|
||||
#include "base/logger.h"
|
||||
#include "base/net/downloadmanager.h"
|
||||
#include "base/preferences.h"
|
||||
#include "base/torrentfilter.h"
|
||||
#include "base/utils/compare.h"
|
||||
#include "base/utils/fs.h"
|
||||
#include "transferlistfilters/categoryfilterwidget.h"
|
||||
#include "transferlistfilters/statusfilterwidget.h"
|
||||
#include "transferlistfilters/tagfilterwidget.h"
|
||||
#include "transferlistfilters/trackersfilterwidget.h"
|
||||
#include "transferlistfilters/trackerstatusfilterwidget.h"
|
||||
#include "transferlistfilterswidgetitem.h"
|
||||
#include "transferlistwidget.h"
|
||||
#include "uithememanager.h"
|
||||
#include "utils.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
class ArrowCheckBox final : public QCheckBox
|
||||
enum ItemPos
|
||||
{
|
||||
public:
|
||||
using QCheckBox::QCheckBox;
|
||||
|
||||
private:
|
||||
void paintEvent(QPaintEvent *) override
|
||||
{
|
||||
QPainter painter(this);
|
||||
|
||||
QStyleOptionViewItem indicatorOption;
|
||||
indicatorOption.initFrom(this);
|
||||
indicatorOption.rect = style()->subElementRect(QStyle::SE_CheckBoxIndicator, &indicatorOption, this);
|
||||
indicatorOption.state |= (QStyle::State_Children
|
||||
| (isChecked() ? QStyle::State_Open : QStyle::State_None));
|
||||
style()->drawPrimitive(QStyle::PE_IndicatorBranch, &indicatorOption, &painter, this);
|
||||
|
||||
QStyleOptionButton labelOption;
|
||||
initStyleOption(&labelOption);
|
||||
labelOption.rect = style()->subElementRect(QStyle::SE_CheckBoxContents, &labelOption, this);
|
||||
style()->drawControl(QStyle::CE_CheckBoxLabel, &labelOption, &painter, this);
|
||||
}
|
||||
StatusItemPos,
|
||||
CategoryItemPos,
|
||||
TagItemPos,
|
||||
TrackerStatusItemPos,
|
||||
TrackersItemPos
|
||||
};
|
||||
}
|
||||
|
||||
@ -99,66 +76,63 @@ TransferListFiltersWidget::TransferListFiltersWidget(QWidget *parent, TransferLi
|
||||
mainWidgetLayout->setSpacing(2);
|
||||
mainWidgetLayout->setAlignment(Qt::AlignLeft | Qt::AlignTop);
|
||||
|
||||
QFont font;
|
||||
font.setBold(true);
|
||||
font.setCapitalization(QFont::AllUppercase);
|
||||
{
|
||||
auto *item = new TransferListFiltersWidgetItem(tr("Status"), new StatusFilterWidget(this, transferList), this);
|
||||
item->setChecked(pref->getStatusFilterState());
|
||||
connect(item, &TransferListFiltersWidgetItem::toggled, pref, &Preferences::setStatusFilterState);
|
||||
mainWidgetLayout->insertWidget(StatusItemPos, item);
|
||||
}
|
||||
|
||||
QCheckBox *statusLabel = new ArrowCheckBox(tr("Status"), this);
|
||||
statusLabel->setChecked(pref->getStatusFilterState());
|
||||
statusLabel->setFont(font);
|
||||
connect(statusLabel, &QCheckBox::toggled, pref, &Preferences::setStatusFilterState);
|
||||
mainWidgetLayout->addWidget(statusLabel);
|
||||
{
|
||||
auto *categoryFilterWidget = new CategoryFilterWidget(this);
|
||||
connect(categoryFilterWidget, &CategoryFilterWidget::actionDeleteTorrentsTriggered
|
||||
, transferList, &TransferListWidget::deleteVisibleTorrents);
|
||||
connect(categoryFilterWidget, &CategoryFilterWidget::actionStopTorrentsTriggered
|
||||
, transferList, &TransferListWidget::stopVisibleTorrents);
|
||||
connect(categoryFilterWidget, &CategoryFilterWidget::actionStartTorrentsTriggered
|
||||
, transferList, &TransferListWidget::startVisibleTorrents);
|
||||
connect(categoryFilterWidget, &CategoryFilterWidget::categoryChanged
|
||||
, transferList, &TransferListWidget::applyCategoryFilter);
|
||||
|
||||
auto *statusFilters = new StatusFilterWidget(this, transferList);
|
||||
connect(statusLabel, &QCheckBox::toggled, statusFilters, &StatusFilterWidget::toggleFilter);
|
||||
mainWidgetLayout->addWidget(statusFilters);
|
||||
auto *item = new TransferListFiltersWidgetItem(tr("Categories"), categoryFilterWidget, this);
|
||||
item->setChecked(pref->getCategoryFilterState());
|
||||
connect(item, &TransferListFiltersWidgetItem::toggled, this, [this, categoryFilterWidget](const bool enabled)
|
||||
{
|
||||
m_transferList->applyCategoryFilter(enabled ? categoryFilterWidget->currentCategory() : QString());
|
||||
});
|
||||
connect(item, &TransferListFiltersWidgetItem::toggled, pref, &Preferences::setCategoryFilterState);
|
||||
mainWidgetLayout->insertWidget(CategoryItemPos, item);
|
||||
}
|
||||
|
||||
QCheckBox *categoryLabel = new ArrowCheckBox(tr("Categories"), this);
|
||||
categoryLabel->setChecked(pref->getCategoryFilterState());
|
||||
categoryLabel->setFont(font);
|
||||
connect(categoryLabel, &QCheckBox::toggled, this
|
||||
, &TransferListFiltersWidget::onCategoryFilterStateChanged);
|
||||
mainWidgetLayout->addWidget(categoryLabel);
|
||||
{
|
||||
auto *tagFilterWidget = new TagFilterWidget(this);
|
||||
connect(tagFilterWidget, &TagFilterWidget::actionDeleteTorrentsTriggered
|
||||
, transferList, &TransferListWidget::deleteVisibleTorrents);
|
||||
connect(tagFilterWidget, &TagFilterWidget::actionStopTorrentsTriggered
|
||||
, transferList, &TransferListWidget::stopVisibleTorrents);
|
||||
connect(tagFilterWidget, &TagFilterWidget::actionStartTorrentsTriggered
|
||||
, transferList, &TransferListWidget::startVisibleTorrents);
|
||||
connect(tagFilterWidget, &TagFilterWidget::tagChanged
|
||||
, transferList, &TransferListWidget::applyTagFilter);
|
||||
|
||||
m_categoryFilterWidget = new CategoryFilterWidget(this);
|
||||
connect(m_categoryFilterWidget, &CategoryFilterWidget::actionDeleteTorrentsTriggered
|
||||
, transferList, &TransferListWidget::deleteVisibleTorrents);
|
||||
connect(m_categoryFilterWidget, &CategoryFilterWidget::actionStopTorrentsTriggered
|
||||
, transferList, &TransferListWidget::stopVisibleTorrents);
|
||||
connect(m_categoryFilterWidget, &CategoryFilterWidget::actionStartTorrentsTriggered
|
||||
, transferList, &TransferListWidget::startVisibleTorrents);
|
||||
connect(m_categoryFilterWidget, &CategoryFilterWidget::categoryChanged
|
||||
, transferList, &TransferListWidget::applyCategoryFilter);
|
||||
toggleCategoryFilter(pref->getCategoryFilterState());
|
||||
mainWidgetLayout->addWidget(m_categoryFilterWidget);
|
||||
auto *item = new TransferListFiltersWidgetItem(tr("Tags"), tagFilterWidget, this);
|
||||
item->setChecked(pref->getTagFilterState());
|
||||
connect(item, &TransferListFiltersWidgetItem::toggled, this, [this, tagFilterWidget](const bool enabled)
|
||||
{
|
||||
m_transferList->applyTagFilter(enabled ? tagFilterWidget->currentTag() : std::nullopt);
|
||||
});
|
||||
connect(item, &TransferListFiltersWidgetItem::toggled, pref, &Preferences::setTagFilterState);
|
||||
mainWidgetLayout->insertWidget(TagItemPos, item);
|
||||
}
|
||||
|
||||
QCheckBox *tagsLabel = new ArrowCheckBox(tr("Tags"), this);
|
||||
tagsLabel->setChecked(pref->getTagFilterState());
|
||||
tagsLabel->setFont(font);
|
||||
connect(tagsLabel, &QCheckBox::toggled, this, &TransferListFiltersWidget::onTagFilterStateChanged);
|
||||
mainWidgetLayout->addWidget(tagsLabel);
|
||||
{
|
||||
m_trackersFilterWidget = new TrackersFilterWidget(this, transferList, downloadFavicon);
|
||||
|
||||
m_tagFilterWidget = new TagFilterWidget(this);
|
||||
connect(m_tagFilterWidget, &TagFilterWidget::actionDeleteTorrentsTriggered
|
||||
, transferList, &TransferListWidget::deleteVisibleTorrents);
|
||||
connect(m_tagFilterWidget, &TagFilterWidget::actionStopTorrentsTriggered
|
||||
, transferList, &TransferListWidget::stopVisibleTorrents);
|
||||
connect(m_tagFilterWidget, &TagFilterWidget::actionStartTorrentsTriggered
|
||||
, transferList, &TransferListWidget::startVisibleTorrents);
|
||||
connect(m_tagFilterWidget, &TagFilterWidget::tagChanged
|
||||
, transferList, &TransferListWidget::applyTagFilter);
|
||||
toggleTagFilter(pref->getTagFilterState());
|
||||
mainWidgetLayout->addWidget(m_tagFilterWidget);
|
||||
|
||||
QCheckBox *trackerLabel = new ArrowCheckBox(tr("Trackers"), this);
|
||||
trackerLabel->setChecked(pref->getTrackerFilterState());
|
||||
trackerLabel->setFont(font);
|
||||
connect(trackerLabel, &QCheckBox::toggled, pref, &Preferences::setTrackerFilterState);
|
||||
mainWidgetLayout->addWidget(trackerLabel);
|
||||
|
||||
m_trackersFilterWidget = new TrackersFilterWidget(this, transferList, downloadFavicon);
|
||||
connect(trackerLabel, &QCheckBox::toggled, m_trackersFilterWidget, &TrackersFilterWidget::toggleFilter);
|
||||
mainWidgetLayout->addWidget(m_trackersFilterWidget);
|
||||
auto *item = new TransferListFiltersWidgetItem(tr("Trackers"), m_trackersFilterWidget, this);
|
||||
item->setChecked(pref->getTrackerFilterState());
|
||||
connect(item, &TransferListFiltersWidgetItem::toggled, pref, &Preferences::setTrackerFilterState);
|
||||
mainWidgetLayout->insertWidget(TrackersItemPos, item);
|
||||
}
|
||||
|
||||
auto *scroll = new QScrollArea(this);
|
||||
scroll->setWidgetResizable(true);
|
||||
@ -169,54 +143,35 @@ TransferListFiltersWidget::TransferListFiltersWidget(QWidget *parent, TransferLi
|
||||
auto *vLayout = new QVBoxLayout(this);
|
||||
vLayout->setContentsMargins(0, 0, 0, 0);
|
||||
vLayout->addWidget(scroll);
|
||||
|
||||
const auto createTrackerStatusItem = [this, mainWidgetLayout, pref]
|
||||
{
|
||||
auto *item = new TransferListFiltersWidgetItem(tr("Tracker status"), new TrackerStatusFilterWidget(this, m_transferList), this);
|
||||
item->setChecked(pref->getTrackerStatusFilterState());
|
||||
connect(item, &TransferListFiltersWidgetItem::toggled, pref, &Preferences::setTrackerStatusFilterState);
|
||||
mainWidgetLayout->insertWidget(TrackerStatusItemPos, item);
|
||||
};
|
||||
|
||||
const auto removeTrackerStatusItem = [mainWidgetLayout]
|
||||
{
|
||||
QLayoutItem *layoutItem = mainWidgetLayout->takeAt(TrackerStatusItemPos);
|
||||
delete layoutItem->widget();
|
||||
delete layoutItem;
|
||||
};
|
||||
|
||||
if (pref->useSeparateTrackerStatusFilter())
|
||||
createTrackerStatusItem();
|
||||
|
||||
connect(pref, &Preferences::changed, this, [pref, createTrackerStatusItem, removeTrackerStatusItem]
|
||||
{
|
||||
if (pref->useSeparateTrackerStatusFilter())
|
||||
createTrackerStatusItem();
|
||||
else
|
||||
removeTrackerStatusItem();
|
||||
});
|
||||
}
|
||||
|
||||
void TransferListFiltersWidget::setDownloadTrackerFavicon(bool value)
|
||||
{
|
||||
m_trackersFilterWidget->setDownloadTrackerFavicon(value);
|
||||
}
|
||||
|
||||
void TransferListFiltersWidget::addTrackers(const BitTorrent::Torrent *torrent, const QList<BitTorrent::TrackerEntry> &trackers)
|
||||
{
|
||||
m_trackersFilterWidget->addTrackers(torrent, trackers);
|
||||
}
|
||||
|
||||
void TransferListFiltersWidget::removeTrackers(const BitTorrent::Torrent *torrent, const QStringList &trackers)
|
||||
{
|
||||
m_trackersFilterWidget->removeTrackers(torrent, trackers);
|
||||
}
|
||||
|
||||
void TransferListFiltersWidget::refreshTrackers(const BitTorrent::Torrent *torrent)
|
||||
{
|
||||
m_trackersFilterWidget->refreshTrackers(torrent);
|
||||
}
|
||||
|
||||
void TransferListFiltersWidget::trackerEntryStatusesUpdated(const BitTorrent::Torrent *torrent
|
||||
, const QHash<QString, BitTorrent::TrackerEntryStatus> &updatedTrackers)
|
||||
{
|
||||
m_trackersFilterWidget->handleTrackerStatusesUpdated(torrent, updatedTrackers);
|
||||
}
|
||||
|
||||
void TransferListFiltersWidget::onCategoryFilterStateChanged(bool enabled)
|
||||
{
|
||||
toggleCategoryFilter(enabled);
|
||||
Preferences::instance()->setCategoryFilterState(enabled);
|
||||
}
|
||||
|
||||
void TransferListFiltersWidget::toggleCategoryFilter(bool enabled)
|
||||
{
|
||||
m_categoryFilterWidget->setVisible(enabled);
|
||||
m_transferList->applyCategoryFilter(enabled ? m_categoryFilterWidget->currentCategory() : QString());
|
||||
}
|
||||
|
||||
void TransferListFiltersWidget::onTagFilterStateChanged(bool enabled)
|
||||
{
|
||||
toggleTagFilter(enabled);
|
||||
Preferences::instance()->setTagFilterState(enabled);
|
||||
}
|
||||
|
||||
void TransferListFiltersWidget::toggleTagFilter(bool enabled)
|
||||
{
|
||||
m_tagFilterWidget->setVisible(enabled);
|
||||
m_transferList->applyTagFilter(enabled ? m_tagFilterWidget->currentTag() : std::nullopt);
|
||||
}
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2023 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2023-2025 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
@ -32,11 +32,6 @@
|
||||
#include <QtContainerFwd>
|
||||
#include <QWidget>
|
||||
|
||||
#include "base/bittorrent/trackerentry.h"
|
||||
|
||||
class CategoryFilterWidget;
|
||||
class StatusFilterWidget;
|
||||
class TagFilterWidget;
|
||||
class TrackersFilterWidget;
|
||||
class TransferListWidget;
|
||||
|
||||
@ -55,23 +50,7 @@ public:
|
||||
TransferListFiltersWidget(QWidget *parent, TransferListWidget *transferList, bool downloadFavicon);
|
||||
void setDownloadTrackerFavicon(bool value);
|
||||
|
||||
public slots:
|
||||
void addTrackers(const BitTorrent::Torrent *torrent, const QList<BitTorrent::TrackerEntry> &trackers);
|
||||
void removeTrackers(const BitTorrent::Torrent *torrent, const QStringList &trackers);
|
||||
void refreshTrackers(const BitTorrent::Torrent *torrent);
|
||||
void trackerEntryStatusesUpdated(const BitTorrent::Torrent *torrent
|
||||
, const QHash<QString, BitTorrent::TrackerEntryStatus> &updatedTrackers);
|
||||
|
||||
private slots:
|
||||
void onCategoryFilterStateChanged(bool enabled);
|
||||
void onTagFilterStateChanged(bool enabled);
|
||||
|
||||
private:
|
||||
void toggleCategoryFilter(bool enabled);
|
||||
void toggleTagFilter(bool enabled);
|
||||
|
||||
TransferListWidget *m_transferList = nullptr;
|
||||
TrackersFilterWidget *m_trackersFilterWidget = nullptr;
|
||||
CategoryFilterWidget *m_categoryFilterWidget = nullptr;
|
||||
TagFilterWidget *m_tagFilterWidget = nullptr;
|
||||
};
|
||||
|
||||
96
src/gui/transferlistfilterswidgetitem.cpp
Normal file
96
src/gui/transferlistfilterswidgetitem.cpp
Normal file
@ -0,0 +1,96 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2025 Vladimir Golovnev <glassez@yandex.ru>
|
||||
*
|
||||
* 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 "transferlistfilterswidgetitem.h"
|
||||
|
||||
#include <QCheckBox>
|
||||
#include <QFont>
|
||||
#include <QPainter>
|
||||
#include <QString>
|
||||
#include <QStyleOptionViewItem>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
namespace
|
||||
{
|
||||
class ArrowCheckBox final : public QCheckBox
|
||||
{
|
||||
public:
|
||||
using QCheckBox::QCheckBox;
|
||||
|
||||
private:
|
||||
void paintEvent(QPaintEvent *) override
|
||||
{
|
||||
QPainter painter {this};
|
||||
|
||||
QStyleOptionViewItem indicatorOption;
|
||||
indicatorOption.initFrom(this);
|
||||
indicatorOption.rect = style()->subElementRect(QStyle::SE_CheckBoxIndicator, &indicatorOption, this);
|
||||
indicatorOption.state |= (QStyle::State_Children
|
||||
| (isChecked() ? QStyle::State_Open : QStyle::State_None));
|
||||
style()->drawPrimitive(QStyle::PE_IndicatorBranch, &indicatorOption, &painter, this);
|
||||
|
||||
QStyleOptionButton labelOption;
|
||||
initStyleOption(&labelOption);
|
||||
labelOption.rect = style()->subElementRect(QStyle::SE_CheckBoxContents, &labelOption, this);
|
||||
style()->drawControl(QStyle::CE_CheckBoxLabel, &labelOption, &painter, this);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
TransferListFiltersWidgetItem::TransferListFiltersWidgetItem(const QString &caption, QWidget *filterWidget, QWidget *parent)
|
||||
: QWidget(parent)
|
||||
, m_caption {new ArrowCheckBox(caption, this)}
|
||||
, m_filterWidget {filterWidget}
|
||||
{
|
||||
QFont font;
|
||||
font.setBold(true);
|
||||
font.setCapitalization(QFont::AllUppercase);
|
||||
m_caption->setFont(font);
|
||||
|
||||
auto *layout = new QVBoxLayout(this);
|
||||
layout->setContentsMargins(0, 2, 0, 0);
|
||||
layout->setSpacing(2);
|
||||
layout->setAlignment(Qt::AlignLeft | Qt::AlignTop);
|
||||
layout->addWidget(m_caption);
|
||||
layout->addWidget(m_filterWidget);
|
||||
|
||||
m_filterWidget->setVisible(m_caption->isChecked());
|
||||
|
||||
connect(m_caption, &QCheckBox::toggled, m_filterWidget, &QWidget::setVisible);
|
||||
connect(m_caption, &QCheckBox::toggled, this, &TransferListFiltersWidgetItem::toggled);
|
||||
}
|
||||
|
||||
bool TransferListFiltersWidgetItem::isChecked() const
|
||||
{
|
||||
return m_caption->isChecked();
|
||||
}
|
||||
|
||||
void TransferListFiltersWidgetItem::setChecked(const bool value)
|
||||
{
|
||||
m_caption->setChecked(value);
|
||||
}
|
||||
53
src/gui/transferlistfilterswidgetitem.h
Normal file
53
src/gui/transferlistfilterswidgetitem.h
Normal file
@ -0,0 +1,53 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2025 Vladimir Golovnev <glassez@yandex.ru>
|
||||
*
|
||||
* 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 <QWidget>
|
||||
|
||||
class QCheckBox;
|
||||
class QString;
|
||||
|
||||
class TransferListFiltersWidgetItem final : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_DISABLE_COPY_MOVE(TransferListFiltersWidgetItem)
|
||||
|
||||
public:
|
||||
TransferListFiltersWidgetItem(const QString &caption, QWidget *filterWidget, QWidget *parent = nullptr);
|
||||
|
||||
bool isChecked() const;
|
||||
void setChecked(bool value);
|
||||
|
||||
signals:
|
||||
void toggled(bool checked);
|
||||
|
||||
private:
|
||||
QCheckBox *m_caption = nullptr;
|
||||
QWidget *m_filterWidget = nullptr;
|
||||
};
|
||||
@ -91,27 +91,25 @@ namespace
|
||||
|
||||
TransferListModel::TransferListModel(QObject *parent)
|
||||
: QAbstractListModel {parent}
|
||||
, m_statusStrings
|
||||
{
|
||||
{BitTorrent::TorrentState::Downloading, tr("Downloading")},
|
||||
{BitTorrent::TorrentState::StalledDownloading, tr("Stalled", "Torrent is waiting for download to begin")},
|
||||
{BitTorrent::TorrentState::DownloadingMetadata, tr("Downloading metadata", "Used when loading a magnet link")},
|
||||
{BitTorrent::TorrentState::ForcedDownloadingMetadata, tr("[F] Downloading metadata", "Used when forced to load a magnet link. You probably shouldn't translate the F.")},
|
||||
{BitTorrent::TorrentState::ForcedDownloading, tr("[F] Downloading", "Used when the torrent is forced started. You probably shouldn't translate the F.")},
|
||||
{BitTorrent::TorrentState::Uploading, tr("Seeding", "Torrent is complete and in upload-only mode")},
|
||||
{BitTorrent::TorrentState::StalledUploading, tr("Seeding", "Torrent is complete and in upload-only mode")},
|
||||
{BitTorrent::TorrentState::ForcedUploading, tr("[F] Seeding", "Used when the torrent is forced started. You probably shouldn't translate the F.")},
|
||||
{BitTorrent::TorrentState::QueuedDownloading, tr("Queued", "Torrent is queued")},
|
||||
{BitTorrent::TorrentState::QueuedUploading, tr("Queued", "Torrent is queued")},
|
||||
{BitTorrent::TorrentState::CheckingDownloading, tr("Checking", "Torrent local data is being checked")},
|
||||
{BitTorrent::TorrentState::CheckingUploading, tr("Checking", "Torrent local data is being checked")},
|
||||
{BitTorrent::TorrentState::CheckingResumeData, tr("Checking resume data", "Used when loading the torrents from disk after qbt is launched. It checks the correctness of the .fastresume file. Normally it is completed in a fraction of a second, unless loading many many torrents.")},
|
||||
{BitTorrent::TorrentState::StoppedDownloading, tr("Stopped")},
|
||||
{BitTorrent::TorrentState::StoppedUploading, tr("Completed")},
|
||||
{BitTorrent::TorrentState::Moving, tr("Moving", "Torrent local data are being moved/relocated")},
|
||||
{BitTorrent::TorrentState::MissingFiles, tr("Missing Files")},
|
||||
{BitTorrent::TorrentState::Error, tr("Errored", "Torrent status, the torrent has an error")}
|
||||
}
|
||||
, m_statusStrings {
|
||||
{BitTorrent::TorrentState::Downloading, tr("Downloading")},
|
||||
{BitTorrent::TorrentState::StalledDownloading, tr("Stalled", "Torrent is waiting for download to begin")},
|
||||
{BitTorrent::TorrentState::DownloadingMetadata, tr("Downloading metadata", "Used when loading a magnet link")},
|
||||
{BitTorrent::TorrentState::ForcedDownloadingMetadata, tr("[F] Downloading metadata", "Used when forced to load a magnet link. You probably shouldn't translate the F.")},
|
||||
{BitTorrent::TorrentState::ForcedDownloading, tr("[F] Downloading", "Used when the torrent is forced started. You probably shouldn't translate the F.")},
|
||||
{BitTorrent::TorrentState::Uploading, tr("Seeding", "Torrent is complete and in upload-only mode")},
|
||||
{BitTorrent::TorrentState::StalledUploading, tr("Seeding", "Torrent is complete and in upload-only mode")},
|
||||
{BitTorrent::TorrentState::ForcedUploading, tr("[F] Seeding", "Used when the torrent is forced started. You probably shouldn't translate the F.")},
|
||||
{BitTorrent::TorrentState::QueuedDownloading, tr("Queued", "Torrent is queued")},
|
||||
{BitTorrent::TorrentState::QueuedUploading, tr("Queued", "Torrent is queued")},
|
||||
{BitTorrent::TorrentState::CheckingDownloading, tr("Checking", "Torrent local data is being checked")},
|
||||
{BitTorrent::TorrentState::CheckingUploading, tr("Checking", "Torrent local data is being checked")},
|
||||
{BitTorrent::TorrentState::CheckingResumeData, tr("Checking resume data", "Used when loading the torrents from disk after qbt is launched. It checks the correctness of the .fastresume file. Normally it is completed in a fraction of a second, unless loading many many torrents.")},
|
||||
{BitTorrent::TorrentState::StoppedDownloading, tr("Stopped")},
|
||||
{BitTorrent::TorrentState::StoppedUploading, tr("Completed")},
|
||||
{BitTorrent::TorrentState::Moving, tr("Moving", "Torrent local data are being moved/relocated")},
|
||||
{BitTorrent::TorrentState::MissingFiles, tr("Missing Files")},
|
||||
{BitTorrent::TorrentState::Error, tr("Errored", "Torrent status, the torrent has an error")}}
|
||||
{
|
||||
configure();
|
||||
connect(Preferences::instance(), &Preferences::changed, this, &TransferListModel::configure);
|
||||
@ -137,6 +135,8 @@ TransferListModel::TransferListModel(QObject *parent)
|
||||
connect(Session::instance(), &Session::torrentStarted, this, &TransferListModel::handleTorrentStatusUpdated);
|
||||
connect(Session::instance(), &Session::torrentStopped, this, &TransferListModel::handleTorrentStatusUpdated);
|
||||
connect(Session::instance(), &Session::torrentFinishedChecking, this, &TransferListModel::handleTorrentStatusUpdated);
|
||||
|
||||
connect(Session::instance(), &Session::trackerEntryStatusesUpdated, this, &TransferListModel::handleTorrentStatusUpdated);
|
||||
}
|
||||
|
||||
int TransferListModel::rowCount(const QModelIndex &) const
|
||||
|
||||
@ -124,14 +124,14 @@ void TransferListSortModel::sort(const int column, const Qt::SortOrder order)
|
||||
QSortFilterProxyModel::sort(column, order);
|
||||
}
|
||||
|
||||
void TransferListSortModel::setStatusFilter(const TorrentFilter::Type filter)
|
||||
void TransferListSortModel::setStatusFilter(const TorrentFilter::Status status)
|
||||
{
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 10, 0)
|
||||
beginFilterChange();
|
||||
m_filter.setType(filter);
|
||||
m_filter.setStatus(status);
|
||||
endFilterChange(Direction::Rows);
|
||||
#else
|
||||
if (m_filter.setType(filter))
|
||||
if (m_filter.setStatus(status))
|
||||
invalidateRowsFilter();
|
||||
#endif
|
||||
}
|
||||
@ -184,26 +184,26 @@ void TransferListSortModel::disableTagFilter()
|
||||
#endif
|
||||
}
|
||||
|
||||
void TransferListSortModel::setTrackerFilter(const QSet<BitTorrent::TorrentID> &torrentIDs)
|
||||
void TransferListSortModel::setTrackerFilter(const std::optional<QString> &trackerHost)
|
||||
{
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 10, 0)
|
||||
beginFilterChange();
|
||||
m_filter.setTorrentIDSet(torrentIDs);
|
||||
m_filter.setTrackerHost(trackerHost);
|
||||
endFilterChange(Direction::Rows);
|
||||
#else
|
||||
if (m_filter.setTorrentIDSet(torrentIDs))
|
||||
if (m_filter.setTrackerHost(trackerHost))
|
||||
invalidateRowsFilter();
|
||||
#endif
|
||||
}
|
||||
|
||||
void TransferListSortModel::disableTrackerFilter()
|
||||
void TransferListSortModel::setAnnounceStatusFilter(const std::optional<BitTorrent::TorrentAnnounceStatus> &announceStatus)
|
||||
{
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 10, 0)
|
||||
beginFilterChange();
|
||||
m_filter.setTorrentIDSet(TorrentFilter::AnyID);
|
||||
m_filter.setAnnounceStatus(announceStatus);
|
||||
endFilterChange(Direction::Rows);
|
||||
#else
|
||||
if (m_filter.setTorrentIDSet(TorrentFilter::AnyID))
|
||||
if (m_filter.setAnnounceStatus(announceStatus))
|
||||
invalidateRowsFilter();
|
||||
#endif
|
||||
}
|
||||
|
||||
@ -49,13 +49,13 @@ public:
|
||||
|
||||
void sort(int column, Qt::SortOrder order = Qt::AscendingOrder) override;
|
||||
|
||||
void setStatusFilter(TorrentFilter::Type filter);
|
||||
void setStatusFilter(TorrentFilter::Status status);
|
||||
void setCategoryFilter(const QString &category);
|
||||
void disableCategoryFilter();
|
||||
void setTagFilter(const Tag &tag);
|
||||
void disableTagFilter();
|
||||
void setTrackerFilter(const QSet<BitTorrent::TorrentID> &torrentIDs);
|
||||
void disableTrackerFilter();
|
||||
void setTrackerFilter(const std::optional<QString> &trackerHost);
|
||||
void setAnnounceStatusFilter(const std::optional<BitTorrent::TorrentAnnounceStatus> &announceStatus);
|
||||
|
||||
private:
|
||||
int compare(const QModelIndex &left, const QModelIndex &right) const;
|
||||
|
||||
@ -1342,14 +1342,14 @@ void TransferListWidget::applyTagFilter(const std::optional<Tag> &tag)
|
||||
m_sortFilterModel->setTagFilter(*tag);
|
||||
}
|
||||
|
||||
void TransferListWidget::applyTrackerFilterAll()
|
||||
void TransferListWidget::applyTrackerFilter(const std::optional<QString> &trackerHost)
|
||||
{
|
||||
m_sortFilterModel->disableTrackerFilter();
|
||||
m_sortFilterModel->setTrackerFilter(trackerHost);
|
||||
}
|
||||
|
||||
void TransferListWidget::applyTrackerFilter(const QSet<BitTorrent::TorrentID> &torrentIDs)
|
||||
void TransferListWidget::applyAnnounceStatusFilter(const std::optional<BitTorrent::TorrentAnnounceStatus> &announceStatus)
|
||||
{
|
||||
m_sortFilterModel->setTrackerFilter(torrentIDs);
|
||||
m_sortFilterModel->setAnnounceStatusFilter(announceStatus);
|
||||
}
|
||||
|
||||
void TransferListWidget::applyFilter(const QString &name, const TransferListModel::Column &type)
|
||||
@ -1362,7 +1362,7 @@ void TransferListWidget::applyFilter(const QString &name, const TransferListMode
|
||||
|
||||
void TransferListWidget::applyStatusFilter(const int filterIndex)
|
||||
{
|
||||
const auto filterType = static_cast<TorrentFilter::Type>(filterIndex);
|
||||
const auto filterType = static_cast<TorrentFilter::Status>(filterIndex);
|
||||
m_sortFilterModel->setStatusFilter(((filterType >= TorrentFilter::All) && (filterType < TorrentFilter::_Count)) ? filterType : TorrentFilter::All);
|
||||
// Select first item if nothing is selected
|
||||
if (selectionModel()->selectedRows(0).empty() && (m_sortFilterModel->rowCount() > 0))
|
||||
|
||||
@ -100,8 +100,8 @@ public slots:
|
||||
void applyStatusFilter(int filterIndex);
|
||||
void applyCategoryFilter(const QString &category);
|
||||
void applyTagFilter(const std::optional<Tag> &tag);
|
||||
void applyTrackerFilterAll();
|
||||
void applyTrackerFilter(const QSet<BitTorrent::TorrentID> &torrentIDs);
|
||||
void applyTrackerFilter(const std::optional<QString> &trackerHost);
|
||||
void applyAnnounceStatusFilter(const std::optional<BitTorrent::TorrentAnnounceStatus> &announceStatus);
|
||||
void previewFile(const Path &filePath);
|
||||
void renameSelectedTorrent();
|
||||
|
||||
|
||||
@ -547,7 +547,7 @@ void SyncController::maindataAction()
|
||||
connect(btSession, &BitTorrent::Session::torrentsUpdated, this, &SyncController::onTorrentsUpdated);
|
||||
connect(btSession, &BitTorrent::Session::trackersAdded, this, &SyncController::onTorrentTrackersChanged);
|
||||
connect(btSession, &BitTorrent::Session::trackersRemoved, this, &SyncController::onTorrentTrackersChanged);
|
||||
connect(btSession, &BitTorrent::Session::trackersChanged, this, &SyncController::onTorrentTrackersChanged);
|
||||
connect(btSession, &BitTorrent::Session::trackersReset, this, &SyncController::onTorrentTrackersChanged);
|
||||
connect(btSession, &BitTorrent::Session::trackerEntryStatusesUpdated, this, &SyncController::onTorrentTrackerEntryStatusesUpdated);
|
||||
}
|
||||
|
||||
|
||||
@ -31,7 +31,6 @@
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
#include <concepts>
|
||||
#include <functional>
|
||||
|
||||
#include <QBitArray>
|
||||
#include <QFileInfo>
|
||||
@ -506,6 +505,50 @@ namespace
|
||||
return nonstd::make_unexpected(TorrentsController::tr("Priority is not valid"));
|
||||
return priority;
|
||||
}
|
||||
|
||||
TorrentFilter::Status parseTorrentStatus(const QString &statusStr)
|
||||
{
|
||||
if (statusStr == u"downloading")
|
||||
return TorrentFilter::Downloading;
|
||||
|
||||
if (statusStr == u"seeding")
|
||||
return TorrentFilter::Seeding;
|
||||
|
||||
if (statusStr == u"completed")
|
||||
return TorrentFilter::Completed;
|
||||
|
||||
if (statusStr == u"stopped")
|
||||
return TorrentFilter::Stopped;
|
||||
|
||||
if (statusStr == u"running")
|
||||
return TorrentFilter::Running;
|
||||
|
||||
if (statusStr == u"active")
|
||||
return TorrentFilter::Active;
|
||||
|
||||
if (statusStr == u"inactive")
|
||||
return TorrentFilter::Inactive;
|
||||
|
||||
if (statusStr == u"stalled")
|
||||
return TorrentFilter::Stalled;
|
||||
|
||||
if (statusStr == u"stalled_uploading")
|
||||
return TorrentFilter::StalledUploading;
|
||||
|
||||
if (statusStr == u"stalled_downloading")
|
||||
return TorrentFilter::StalledDownloading;
|
||||
|
||||
if (statusStr == u"checking")
|
||||
return TorrentFilter::Checking;
|
||||
|
||||
if (statusStr == u"moving")
|
||||
return TorrentFilter::Moving;
|
||||
|
||||
if (statusStr == u"errored")
|
||||
return TorrentFilter::Errored;
|
||||
|
||||
return TorrentFilter::All;
|
||||
}
|
||||
}
|
||||
|
||||
TorrentsController::TorrentsController(IApplication *app, QObject *parent)
|
||||
@ -574,7 +617,7 @@ void TorrentsController::infoAction()
|
||||
idSet->insert(BitTorrent::TorrentID::fromString(hash));
|
||||
}
|
||||
|
||||
const TorrentFilter torrentFilter {filter, idSet, category, tag, isPrivate};
|
||||
const TorrentFilter torrentFilter {parseTorrentStatus(filter), idSet, category, tag, isPrivate};
|
||||
QVariantList torrentList;
|
||||
for (const BitTorrent::Torrent *torrent : asConst(BitTorrent::Session::instance()->torrents()))
|
||||
{
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user