2 * Bittorrent Client using Qt and libtorrent.
3 * Copyright (C) 2023 Vladimir Golovnev <glassez@yandex.ru>
4 * Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License
8 * as published by the Free Software Foundation; either version 2
9 * of the License, or (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20 * In addition, as a special exception, the copyright holders give permission to
21 * link this program with the OpenSSL project's "OpenSSL" library (or with
22 * modified versions of it that use the same license as the "OpenSSL" library),
23 * and distribute the linked executables. You must obey the GNU General Public
24 * License in all respects for all of the code used other than "OpenSSL". If you
25 * modify file(s), you may extend this exception to your version of the file(s),
26 * but you are not obligated to do so. If you do not wish to do so, delete this
27 * exception statement from your version.
30 #include "trackerlistmodel.h"
34 #include <boost/multi_index_container.hpp>
35 #include <boost/multi_index/composite_key.hpp>
36 #include <boost/multi_index/hashed_index.hpp>
37 #include <boost/multi_index/indexed_by.hpp>
38 #include <boost/multi_index/member.hpp>
39 #include <boost/multi_index/random_access_index.hpp>
40 #include <boost/multi_index/tag.hpp>
45 #include <QScopeGuard>
48 #include "base/bittorrent/peerinfo.h"
49 #include "base/bittorrent/session.h"
50 #include "base/bittorrent/torrent.h"
51 #include "base/bittorrent/trackerentry.h"
52 #include "base/global.h"
53 #include "base/utils/misc.h"
55 using namespace std::chrono_literals
;
56 using namespace boost::multi_index
;
60 const std::chrono::milliseconds ANNOUNCE_TIME_REFRESH_INTERVAL
= 4s
;
62 const char STR_WORKING
[] = QT_TRANSLATE_NOOP("TrackerListModel", "Working");
63 const char STR_DISABLED
[] = QT_TRANSLATE_NOOP("TrackerListModel", "Disabled");
64 const char STR_TORRENT_DISABLED
[] = QT_TRANSLATE_NOOP("TrackerListModel", "Disabled for this torrent");
65 const char STR_PRIVATE_MSG
[] = QT_TRANSLATE_NOOP("TrackerListModel", "This torrent is private");
67 QString
prettyCount(const int val
)
69 return (val
> -1) ? QString::number(val
) : TrackerListModel::tr("N/A");
72 QString
toString(const BitTorrent::TrackerEndpointState state
)
76 case BitTorrent::TrackerEndpointState::Working
:
77 return TrackerListModel::tr(STR_WORKING
);
78 case BitTorrent::TrackerEndpointState::Updating
:
79 return TrackerListModel::tr("Updating...");
80 case BitTorrent::TrackerEndpointState::NotWorking
:
81 return TrackerListModel::tr("Not working");
82 case BitTorrent::TrackerEndpointState::TrackerError
:
83 return TrackerListModel::tr("Tracker error");
84 case BitTorrent::TrackerEndpointState::Unreachable
:
85 return TrackerListModel::tr("Unreachable");
86 case BitTorrent::TrackerEndpointState::NotContacted
:
87 return TrackerListModel::tr("Not contacted yet");
89 return TrackerListModel::tr("Invalid state!");
92 QString
statusDHT(const BitTorrent::Torrent
*torrent
)
94 if (!torrent
->session()->isDHTEnabled())
95 return TrackerListModel::tr(STR_DISABLED
);
97 if (torrent
->isPrivate() || torrent
->isDHTDisabled())
98 return TrackerListModel::tr(STR_TORRENT_DISABLED
);
100 return TrackerListModel::tr(STR_WORKING
);
103 QString
statusPeX(const BitTorrent::Torrent
*torrent
)
105 if (!torrent
->session()->isPeXEnabled())
106 return TrackerListModel::tr(STR_DISABLED
);
108 if (torrent
->isPrivate() || torrent
->isPEXDisabled())
109 return TrackerListModel::tr(STR_TORRENT_DISABLED
);
111 return TrackerListModel::tr(STR_WORKING
);
114 QString
statusLSD(const BitTorrent::Torrent
*torrent
)
116 if (!torrent
->session()->isLSDEnabled())
117 return TrackerListModel::tr(STR_DISABLED
);
119 if (torrent
->isPrivate() || torrent
->isLSDDisabled())
120 return TrackerListModel::tr(STR_TORRENT_DISABLED
);
122 return TrackerListModel::tr(STR_WORKING
);
126 std::size_t hash_value(const QString
&string
)
128 return qHash(string
);
131 struct TrackerListModel::Item final
136 BitTorrent::TrackerEndpointState status
= BitTorrent::TrackerEndpointState::NotContacted
;
142 int numDownloaded
= -1;
144 QDateTime nextAnnounceTime
{};
145 QDateTime minAnnounceTime
{};
147 qint64 secsToNextAnnounce
= 0;
148 qint64 secsToMinAnnounce
= 0;
149 QDateTime announceTimestamp
;
151 std::weak_ptr
<Item
> parentItem
{};
153 multi_index_container
<std::shared_ptr
<Item
>, indexed_by
<
155 hashed_unique
<tag
<struct ByID
>, composite_key
<
157 member
<Item
, QString
, &Item::name
>,
158 member
<Item
, int, &Item::btVersion
>
162 Item(QStringView name
, QStringView message
);
163 explicit Item(const BitTorrent::TrackerEntryStatus
&trackerEntryStatus
);
164 Item(const std::shared_ptr
<Item
> &parentItem
, const BitTorrent::TrackerEndpointStatus
&endpointStatus
);
166 void fillFrom(const BitTorrent::TrackerEntryStatus
&trackerEntryStatus
);
167 void fillFrom(const BitTorrent::TrackerEndpointStatus
&endpointStatus
);
170 class TrackerListModel::Items final
: public multi_index_container
<
171 std::shared_ptr
<Item
>,
174 hashed_unique
<tag
<struct ByName
>, member
<Item
, QString
, &Item::name
>>>>
178 TrackerListModel::Item::Item(const QStringView name
, const QStringView message
)
179 : name
{name
.toString()}
180 , message
{message
.toString()}
184 TrackerListModel::Item::Item(const BitTorrent::TrackerEntryStatus
&trackerEntryStatus
)
185 : name
{trackerEntryStatus
.url
}
187 fillFrom(trackerEntryStatus
);
190 TrackerListModel::Item::Item(const std::shared_ptr
<Item
> &parentItem
, const BitTorrent::TrackerEndpointStatus
&endpointStatus
)
191 : name
{endpointStatus
.name
}
192 , btVersion
{endpointStatus
.btVersion
}
193 , parentItem
{parentItem
}
195 fillFrom(endpointStatus
);
198 void TrackerListModel::Item::fillFrom(const BitTorrent::TrackerEntryStatus
&trackerEntryStatus
)
200 Q_ASSERT(parentItem
.expired());
201 Q_ASSERT(trackerEntryStatus
.url
== name
);
203 tier
= trackerEntryStatus
.tier
;
204 status
= trackerEntryStatus
.state
;
205 message
= trackerEntryStatus
.message
;
206 numPeers
= trackerEntryStatus
.numPeers
;
207 numSeeds
= trackerEntryStatus
.numSeeds
;
208 numLeeches
= trackerEntryStatus
.numLeeches
;
209 numDownloaded
= trackerEntryStatus
.numDownloaded
;
210 nextAnnounceTime
= trackerEntryStatus
.nextAnnounceTime
;
211 minAnnounceTime
= trackerEntryStatus
.minAnnounceTime
;
212 secsToNextAnnounce
= 0;
213 secsToMinAnnounce
= 0;
214 announceTimestamp
= QDateTime();
217 void TrackerListModel::Item::fillFrom(const BitTorrent::TrackerEndpointStatus
&endpointStatus
)
219 Q_ASSERT(!parentItem
.expired());
220 Q_ASSERT(endpointStatus
.name
== name
);
221 Q_ASSERT(endpointStatus
.btVersion
== btVersion
);
223 status
= endpointStatus
.state
;
224 message
= endpointStatus
.message
;
225 numPeers
= endpointStatus
.numPeers
;
226 numSeeds
= endpointStatus
.numSeeds
;
227 numLeeches
= endpointStatus
.numLeeches
;
228 numDownloaded
= endpointStatus
.numDownloaded
;
229 nextAnnounceTime
= endpointStatus
.nextAnnounceTime
;
230 minAnnounceTime
= endpointStatus
.minAnnounceTime
;
231 secsToNextAnnounce
= 0;
232 secsToMinAnnounce
= 0;
233 announceTimestamp
= QDateTime();
236 TrackerListModel::TrackerListModel(BitTorrent::Session
*btSession
, QObject
*parent
)
237 : QAbstractItemModel(parent
)
238 , m_btSession
{btSession
}
239 , m_items
{std::make_unique
<Items
>()}
240 , m_announceRefreshTimer
{new QTimer(this)}
242 Q_ASSERT(m_btSession
);
244 m_announceRefreshTimer
->setSingleShot(true);
245 connect(m_announceRefreshTimer
, &QTimer::timeout
, this, &TrackerListModel::refreshAnnounceTimes
);
247 connect(m_btSession
, &BitTorrent::Session::trackersAdded
, this
248 , [this](BitTorrent::Torrent
*torrent
, const QList
<BitTorrent::TrackerEntry
> &newTrackers
)
250 if (torrent
== m_torrent
)
251 onTrackersAdded(newTrackers
);
253 connect(m_btSession
, &BitTorrent::Session::trackersRemoved
, this
254 , [this](BitTorrent::Torrent
*torrent
, const QStringList
&deletedTrackers
)
256 if (torrent
== m_torrent
)
257 onTrackersRemoved(deletedTrackers
);
259 connect(m_btSession
, &BitTorrent::Session::trackersChanged
, this
260 , [this](BitTorrent::Torrent
*torrent
)
262 if (torrent
== m_torrent
)
265 connect(m_btSession
, &BitTorrent::Session::trackerEntryStatusesUpdated
, this
266 , [this](BitTorrent::Torrent
*torrent
, const QHash
<QString
, BitTorrent::TrackerEntryStatus
> &updatedTrackers
)
268 if (torrent
== m_torrent
)
269 onTrackersUpdated(updatedTrackers
);
273 TrackerListModel::~TrackerListModel() = default;
275 void TrackerListModel::setTorrent(BitTorrent::Torrent
*torrent
)
278 [[maybe_unused
]] const auto modelResetGuard
= qScopeGuard([this] { endResetModel(); });
288 m_announceRefreshTimer
->stop();
291 BitTorrent::Torrent
*TrackerListModel::torrent() const
296 void TrackerListModel::populate()
300 const QList
<BitTorrent::TrackerEntryStatus
> trackers
= m_torrent
->trackers();
301 m_items
->reserve(trackers
.size() + STICKY_ROW_COUNT
);
303 const QString
&privateTorrentMessage
= m_torrent
->isPrivate() ? tr(STR_PRIVATE_MSG
) : u
""_s
;
304 m_items
->emplace_back(std::make_shared
<Item
>(u
"** [DHT] **", privateTorrentMessage
));
305 m_items
->emplace_back(std::make_shared
<Item
>(u
"** [PeX] **", privateTorrentMessage
));
306 m_items
->emplace_back(std::make_shared
<Item
>(u
"** [LSD] **", privateTorrentMessage
));
308 using TorrentPtr
= QPointer
<const BitTorrent::Torrent
>;
309 m_torrent
->fetchPeerInfo([this, torrent
= TorrentPtr(m_torrent
)](const QList
<BitTorrent::PeerInfo
> &peers
)
311 if (torrent
!= m_torrent
)
314 // XXX: libtorrent should provide this info...
315 // Count peers from DHT, PeX, LSD
316 uint seedsDHT
= 0, seedsPeX
= 0, seedsLSD
= 0, peersDHT
= 0, peersPeX
= 0, peersLSD
= 0;
317 for (const BitTorrent::PeerInfo
&peer
: peers
)
319 if (peer
.isConnecting())
346 auto &itemsByPos
= m_items
->get
<0>();
347 itemsByPos
.modify((itemsByPos
.begin() + ROW_DHT
), [&seedsDHT
, &peersDHT
](std::shared_ptr
<Item
> &item
)
349 item
->numSeeds
= seedsDHT
;
350 item
->numLeeches
= peersDHT
;
353 itemsByPos
.modify((itemsByPos
.begin() + ROW_PEX
), [&seedsPeX
, &peersPeX
](std::shared_ptr
<Item
> &item
)
355 item
->numSeeds
= seedsPeX
;
356 item
->numLeeches
= peersPeX
;
359 itemsByPos
.modify((itemsByPos
.begin() + ROW_LSD
), [&seedsLSD
, &peersLSD
](std::shared_ptr
<Item
> &item
)
361 item
->numSeeds
= seedsLSD
;
362 item
->numLeeches
= peersLSD
;
366 emit
dataChanged(index(ROW_DHT
, COL_SEEDS
), index(ROW_LSD
, COL_LEECHES
));
369 for (const BitTorrent::TrackerEntryStatus
&status
: trackers
)
370 addTrackerItem(status
);
372 m_announceTimestamp
= QDateTime::currentDateTime();
373 m_announceRefreshTimer
->start(ANNOUNCE_TIME_REFRESH_INTERVAL
);
376 std::shared_ptr
<TrackerListModel::Item
> TrackerListModel::createTrackerItem(const BitTorrent::TrackerEntryStatus
&trackerEntryStatus
)
378 const auto item
= std::make_shared
<Item
>(trackerEntryStatus
);
379 for (const auto &[id
, endpointStatus
] : trackerEntryStatus
.endpoints
.asKeyValueRange())
380 item
->childItems
.emplace_back(std::make_shared
<Item
>(item
, endpointStatus
));
385 void TrackerListModel::addTrackerItem(const BitTorrent::TrackerEntryStatus
&trackerEntryStatus
)
387 [[maybe_unused
]] const auto &[iter
, res
] = m_items
->emplace_back(createTrackerItem(trackerEntryStatus
));
391 void TrackerListModel::updateTrackerItem(const std::shared_ptr
<Item
> &item
, const BitTorrent::TrackerEntryStatus
&trackerEntryStatus
)
393 QSet
<std::pair
<QString
, int>> endpointItemIDs
;
394 QList
<std::shared_ptr
<Item
>> newEndpointItems
;
395 for (const auto &[id
, endpointStatus
] : trackerEntryStatus
.endpoints
.asKeyValueRange())
397 endpointItemIDs
.insert(id
);
399 auto &itemsByID
= item
->childItems
.get
<ByID
>();
400 if (const auto &iter
= itemsByID
.find(std::make_tuple(id
.first
, id
.second
)); iter
!= itemsByID
.end())
402 (*iter
)->fillFrom(endpointStatus
);
406 newEndpointItems
.emplace_back(std::make_shared
<Item
>(item
, endpointStatus
));
410 const auto &itemsByPos
= m_items
->get
<0>();
411 const auto trackerRow
= std::distance(itemsByPos
.begin(), itemsByPos
.iterator_to(item
));
412 const auto trackerIndex
= index(trackerRow
, 0);
414 auto it
= item
->childItems
.begin();
415 while (it
!= item
->childItems
.end())
417 if (const auto endpointItemID
= std::make_pair((*it
)->name
, (*it
)->btVersion
)
418 ; endpointItemIDs
.contains(endpointItemID
))
424 const auto row
= std::distance(item
->childItems
.begin(), it
);
425 beginRemoveRows(trackerIndex
, row
, row
);
426 it
= item
->childItems
.erase(it
);
431 const int numRows
= rowCount(trackerIndex
);
432 emit
dataChanged(index(0, 0, trackerIndex
), index((numRows
- 1), (columnCount(trackerIndex
) - 1), trackerIndex
));
434 if (!newEndpointItems
.isEmpty())
436 beginInsertRows(trackerIndex
, numRows
, (numRows
+ newEndpointItems
.size() - 1));
437 for (const auto &newEndpointItem
: asConst(newEndpointItems
))
438 item
->childItems
.get
<0>().push_back(newEndpointItem
);
442 item
->fillFrom(trackerEntryStatus
);
443 emit
dataChanged(trackerIndex
, index(trackerRow
, (columnCount() - 1)));
446 void TrackerListModel::refreshAnnounceTimes()
451 m_announceTimestamp
= QDateTime::currentDateTime();
452 emit
dataChanged(index(0, COL_NEXT_ANNOUNCE
), index((rowCount() - 1), COL_MIN_ANNOUNCE
));
453 for (int i
= 0; i
< rowCount(); ++i
)
455 const QModelIndex parentIndex
= index(i
, 0);
456 emit
dataChanged(index(0, COL_NEXT_ANNOUNCE
, parentIndex
), index((rowCount(parentIndex
) - 1), COL_MIN_ANNOUNCE
, parentIndex
));
459 m_announceRefreshTimer
->start(ANNOUNCE_TIME_REFRESH_INTERVAL
);
462 int TrackerListModel::columnCount([[maybe_unused
]] const QModelIndex
&parent
) const
467 int TrackerListModel::rowCount(const QModelIndex
&parent
) const
469 if (!parent
.isValid())
470 return m_items
->size();
472 const auto *item
= static_cast<Item
*>(parent
.internalPointer());
474 if (!item
) [[unlikely
]]
477 return item
->childItems
.size();
480 QVariant
TrackerListModel::headerData(const int section
, const Qt::Orientation orientation
, const int role
) const
482 if (orientation
!= Qt::Horizontal
)
487 case Qt::DisplayRole
:
491 return tr("URL/Announce endpoint");
495 return tr("Protocol");
503 return tr("Leeches");
504 case COL_TIMES_DOWNLOADED
:
505 return tr("Times Downloaded");
507 return tr("Message");
508 case COL_NEXT_ANNOUNCE
:
509 return tr("Next announce");
510 case COL_MIN_ANNOUNCE
:
511 return tr("Min announce");
516 case Qt::TextAlignmentRole
:
523 case COL_TIMES_DOWNLOADED
:
524 case COL_NEXT_ANNOUNCE
:
525 case COL_MIN_ANNOUNCE
:
526 return QVariant
{Qt::AlignRight
| Qt::AlignVCenter
};
536 QVariant
TrackerListModel::data(const QModelIndex
&index
, const int role
) const
538 if (!index
.isValid())
541 auto *itemPtr
= static_cast<Item
*>(index
.internalPointer());
543 if (!itemPtr
) [[unlikely
]]
546 if (itemPtr
->announceTimestamp
!= m_announceTimestamp
)
548 itemPtr
->secsToNextAnnounce
= std::max
<qint64
>(0, m_announceTimestamp
.secsTo(itemPtr
->nextAnnounceTime
));
549 itemPtr
->secsToMinAnnounce
= std::max
<qint64
>(0, m_announceTimestamp
.secsTo(itemPtr
->minAnnounceTime
));
550 itemPtr
->announceTimestamp
= m_announceTimestamp
;
553 const bool isEndpoint
= !itemPtr
->parentItem
.expired();
557 case Qt::TextAlignmentRole
:
558 switch (index
.column())
565 case COL_TIMES_DOWNLOADED
:
566 case COL_NEXT_ANNOUNCE
:
567 case COL_MIN_ANNOUNCE
:
568 return QVariant
{Qt::AlignRight
| Qt::AlignVCenter
};
573 case Qt::ForegroundRole
:
574 // TODO: Make me configurable via UI Theme
575 if (!index
.parent().isValid() && (index
.row() < STICKY_ROW_COUNT
))
576 return QColorConstants::Svg::grey
;
579 case Qt::DisplayRole
:
580 case Qt::ToolTipRole
:
581 switch (index
.column())
584 return itemPtr
->name
;
586 return (isEndpoint
|| (index
.row() < STICKY_ROW_COUNT
)) ? QString() : QString::number(itemPtr
->tier
);
588 return isEndpoint
? tr("v%1").arg(itemPtr
->btVersion
) : QString();
591 return toString(itemPtr
->status
);
592 if (index
.row() == ROW_DHT
)
593 return statusDHT(m_torrent
);
594 if (index
.row() == ROW_PEX
)
595 return statusPeX(m_torrent
);
596 if (index
.row() == ROW_LSD
)
597 return statusLSD(m_torrent
);
598 return toString(itemPtr
->status
);
600 return prettyCount(itemPtr
->numPeers
);
602 return prettyCount(itemPtr
->numSeeds
);
604 return prettyCount(itemPtr
->numLeeches
);
605 case COL_TIMES_DOWNLOADED
:
606 return prettyCount(itemPtr
->numDownloaded
);
608 return itemPtr
->message
;
609 case COL_NEXT_ANNOUNCE
:
610 return Utils::Misc::userFriendlyDuration(itemPtr
->secsToNextAnnounce
, -1, Utils::Misc::TimeResolution::Seconds
);
611 case COL_MIN_ANNOUNCE
:
612 return Utils::Misc::userFriendlyDuration(itemPtr
->secsToMinAnnounce
, -1, Utils::Misc::TimeResolution::Seconds
);
618 switch (index
.column())
621 return itemPtr
->name
;
623 return isEndpoint
? -1 : itemPtr
->tier
;
625 return isEndpoint
? itemPtr
->btVersion
: -1;
627 return toString(itemPtr
->status
);
629 return itemPtr
->numPeers
;
631 return itemPtr
->numSeeds
;
633 return itemPtr
->numLeeches
;
634 case COL_TIMES_DOWNLOADED
:
635 return itemPtr
->numDownloaded
;
637 return itemPtr
->message
;
638 case COL_NEXT_ANNOUNCE
:
639 return itemPtr
->secsToNextAnnounce
;
640 case COL_MIN_ANNOUNCE
:
641 return itemPtr
->secsToMinAnnounce
;
653 QModelIndex
TrackerListModel::index(const int row
, const int column
, const QModelIndex
&parent
) const
655 if ((column
< 0) || (column
>= columnCount()))
658 if ((row
< 0) || (row
>= rowCount(parent
)))
661 const std::shared_ptr
<Item
> item
= parent
.isValid()
662 ? m_items
->at(static_cast<std::size_t>(parent
.row()))->childItems
.at(row
)
663 : m_items
->at(static_cast<std::size_t>(row
));
664 return createIndex(row
, column
, item
.get());
667 QModelIndex
TrackerListModel::parent(const QModelIndex
&index
) const
669 if (!index
.isValid())
672 const auto *item
= static_cast<Item
*>(index
.internalPointer());
674 if (!item
) [[unlikely
]]
677 const std::shared_ptr
<Item
> parentItem
= item
->parentItem
.lock();
681 const auto &itemsByName
= m_items
->get
<ByName
>();
682 auto itemsByNameIter
= itemsByName
.find(parentItem
->name
);
683 Q_ASSERT(itemsByNameIter
!= itemsByName
.end());
684 if (itemsByNameIter
== itemsByName
.end()) [[unlikely
]]
687 const auto &itemsByPosIter
= m_items
->project
<0>(itemsByNameIter
);
688 const auto row
= std::distance(m_items
->get
<0>().begin(), itemsByPosIter
);
690 // From https://doc.qt.io/qt-6/qabstractitemmodel.html#parent:
691 // A common convention used in models that expose tree data structures is that only items
692 // in the first column have children. For that case, when reimplementing this function in
693 // a subclass the column of the returned QModelIndex would be 0.
694 return createIndex(row
, 0, parentItem
.get());
697 void TrackerListModel::onTrackersAdded(const QList
<BitTorrent::TrackerEntry
> &newTrackers
)
699 const int row
= rowCount();
700 beginInsertRows({}, row
, (row
+ newTrackers
.size() - 1));
701 for (const BitTorrent::TrackerEntry
&entry
: newTrackers
)
702 addTrackerItem({entry
.url
, entry
.tier
});
706 void TrackerListModel::onTrackersRemoved(const QStringList
&deletedTrackers
)
708 for (const QString
&trackerURL
: deletedTrackers
)
710 auto &itemsByName
= m_items
->get
<ByName
>();
711 if (auto iter
= itemsByName
.find(trackerURL
); iter
!= itemsByName
.end())
713 const auto &iterByPos
= m_items
->project
<0>(iter
);
714 const auto row
= std::distance(m_items
->get
<0>().begin(), iterByPos
);
715 beginRemoveRows({}, row
, row
);
716 itemsByName
.erase(iter
);
722 void TrackerListModel::onTrackersChanged()
724 QSet
<QString
> trackerItemIDs
;
725 for (int i
= 0; i
< STICKY_ROW_COUNT
; ++i
)
726 trackerItemIDs
.insert(m_items
->at(i
)->name
);
728 QList
<std::shared_ptr
<Item
>> newTrackerItems
;
729 for (const BitTorrent::TrackerEntryStatus
&trackerEntryStatus
: m_torrent
->trackers())
731 trackerItemIDs
.insert(trackerEntryStatus
.url
);
733 auto &itemsByName
= m_items
->get
<ByName
>();
734 if (const auto &iter
= itemsByName
.find(trackerEntryStatus
.url
); iter
!= itemsByName
.end())
736 updateTrackerItem(*iter
, trackerEntryStatus
);
740 newTrackerItems
.emplace_back(createTrackerItem(trackerEntryStatus
));
744 auto it
= m_items
->begin();
745 while (it
!= m_items
->end())
747 if (trackerItemIDs
.contains((*it
)->name
))
753 const auto row
= std::distance(m_items
->begin(), it
);
754 beginRemoveRows({}, row
, row
);
755 it
= m_items
->erase(it
);
760 if (!newTrackerItems
.isEmpty())
762 const int numRows
= rowCount();
763 beginInsertRows({}, numRows
, (numRows
+ newTrackerItems
.size() - 1));
764 for (const auto &newTrackerItem
: asConst(newTrackerItems
))
765 m_items
->get
<0>().push_back(newTrackerItem
);
770 void TrackerListModel::onTrackersUpdated(const QHash
<QString
, BitTorrent::TrackerEntryStatus
> &updatedTrackers
)
772 for (const auto &[url
, tracker
] : updatedTrackers
.asKeyValueRange())
774 auto &itemsByName
= m_items
->get
<ByName
>();
775 if (const auto &iter
= itemsByName
.find(tracker
.url
); iter
!= itemsByName
.end()) [[likely
]]
777 updateTrackerItem(*iter
, tracker
);