|
|
|
/*
|
|
|
|
* Bittorrent Client using Qt4 and libtorrent.
|
|
|
|
* Copyright (C) 2010 Christophe Dumez
|
|
|
|
*
|
|
|
|
* 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.
|
|
|
|
*
|
|
|
|
* Contact : chris@qbittorrent.org
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include <QDebug>
|
|
|
|
#include <QApplication>
|
|
|
|
#include <QPalette>
|
|
|
|
|
|
|
|
#include "torrentmodel.h"
|
|
|
|
#include "core/torrentpersistentdata.h"
|
|
|
|
#include "qbtsession.h"
|
|
|
|
#include "core/fs_utils.h"
|
|
|
|
|
|
|
|
#include <libtorrent/session.hpp>
|
|
|
|
|
|
|
|
using namespace libtorrent;
|
|
|
|
|
|
|
|
namespace {
|
|
|
|
QIcon get_paused_icon() {
|
|
|
|
static QIcon cached = QIcon(":/icons/skin/paused.png");
|
|
|
|
return cached;
|
|
|
|
}
|
|
|
|
|
|
|
|
QIcon get_queued_icon() {
|
|
|
|
static QIcon cached = QIcon(":/icons/skin/queued.png");
|
|
|
|
return cached;
|
|
|
|
}
|
|
|
|
|
|
|
|
QIcon get_downloading_icon() {
|
|
|
|
static QIcon cached = QIcon(":/icons/skin/downloading.png");
|
|
|
|
return cached;
|
|
|
|
}
|
|
|
|
|
|
|
|
QIcon get_stalled_downloading_icon() {
|
|
|
|
static QIcon cached = QIcon(":/icons/skin/stalledDL.png");
|
|
|
|
return cached;
|
|
|
|
}
|
|
|
|
|
|
|
|
QIcon get_uploading_icon() {
|
|
|
|
static QIcon cached = QIcon(":/icons/skin/uploading.png");
|
|
|
|
return cached;
|
|
|
|
}
|
|
|
|
|
|
|
|
QIcon get_stalled_uploading_icon() {
|
|
|
|
static QIcon cached = QIcon(":/icons/skin/stalledUP.png");
|
|
|
|
return cached;
|
|
|
|
}
|
|
|
|
|
|
|
|
QIcon get_completed_icon() {
|
|
|
|
static QIcon cached = QIcon(":/icons/skin/completed.png");
|
|
|
|
return cached;
|
|
|
|
}
|
|
|
|
|
|
|
|
QIcon get_checking_icon() {
|
|
|
|
static QIcon cached = QIcon(":/icons/skin/checking.png");
|
|
|
|
return cached;
|
|
|
|
}
|
|
|
|
|
|
|
|
QIcon get_error_icon() {
|
|
|
|
static QIcon cached = QIcon(":/icons/skin/error.png");
|
|
|
|
return cached;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool isDarkTheme()
|
|
|
|
{
|
|
|
|
QPalette pal = QApplication::palette();
|
|
|
|
// QPalette::Base is used for the background of the Treeview
|
|
|
|
QColor color = pal.color(QPalette::Active, QPalette::Base);
|
|
|
|
return (color.lightness() < 127);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
TorrentStatusReport::TorrentStatusReport()
|
|
|
|
: nb_downloading(0)
|
|
|
|
, nb_seeding(0)
|
|
|
|
, nb_completed(0)
|
|
|
|
, nb_active(0)
|
|
|
|
, nb_inactive(0)
|
|
|
|
, nb_paused(0)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
TorrentModelItem::TorrentModelItem(const QTorrentHandle &h)
|
|
|
|
: m_torrent(h)
|
|
|
|
, m_lastStatus(h.status(torrent_handle::query_accurate_download_counters))
|
|
|
|
, m_addedTime(TorrentPersistentData::instance()->getAddedDate(h.hash()))
|
|
|
|
, m_label(TorrentPersistentData::instance()->getLabel(h.hash()))
|
|
|
|
, m_name(TorrentPersistentData::instance()->getName(h.hash()))
|
|
|
|
, m_hash(h.hash())
|
|
|
|
{
|
|
|
|
if (m_name.isEmpty())
|
|
|
|
m_name = h.name();
|
|
|
|
// If name is empty show the hash. This happens when magnet isn't retrieved.
|
|
|
|
if (m_name.isEmpty())
|
|
|
|
m_name = h.hash();
|
|
|
|
}
|
|
|
|
|
|
|
|
void TorrentModelItem::refreshStatus(libtorrent::torrent_status const& status) {
|
|
|
|
m_lastStatus = status;
|
|
|
|
}
|
|
|
|
|
|
|
|
TorrentModelItem::State TorrentModelItem::state() const {
|
|
|
|
try {
|
|
|
|
// Pause or Queued
|
|
|
|
if (m_torrent.is_paused(m_lastStatus)) {
|
|
|
|
if (TorrentPersistentData::instance()->getHasMissingFiles(misc::toQString(m_lastStatus.info_hash)))
|
|
|
|
return STATE_PAUSED_MISSING;
|
|
|
|
else
|
|
|
|
return m_torrent.is_seed(m_lastStatus) ? STATE_PAUSED_UP : STATE_PAUSED_DL;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (m_torrent.is_queued(m_lastStatus)
|
|
|
|
&& m_lastStatus.state != torrent_status::queued_for_checking
|
|
|
|
&& m_lastStatus.state != torrent_status::checking_resume_data
|
|
|
|
&& m_lastStatus.state != torrent_status::checking_files)
|
|
|
|
return m_torrent.is_seed(m_lastStatus) ? STATE_QUEUED_UP : STATE_QUEUED_DL;
|
|
|
|
|
|
|
|
// Other states
|
|
|
|
switch(m_lastStatus.state) {
|
|
|
|
case torrent_status::allocating:
|
|
|
|
return STATE_ALLOCATING;
|
|
|
|
case torrent_status::downloading_metadata:
|
|
|
|
return STATE_DOWNLOADING_META;
|
|
|
|
case torrent_status::downloading:
|
|
|
|
if (!m_torrent.is_forced(m_lastStatus))
|
|
|
|
return m_lastStatus.download_payload_rate > 0 ? STATE_DOWNLOADING : STATE_STALLED_DL;
|
|
|
|
else
|
|
|
|
return STATE_FORCED_DL;
|
|
|
|
case torrent_status::finished:
|
|
|
|
case torrent_status::seeding:
|
|
|
|
if (!m_torrent.is_forced(m_lastStatus))
|
|
|
|
return m_lastStatus.upload_payload_rate > 0 ? STATE_SEEDING : STATE_STALLED_UP;
|
|
|
|
else
|
|
|
|
return STATE_FORCED_UP;
|
|
|
|
case torrent_status::queued_for_checking:
|
|
|
|
return STATE_QUEUED_CHECK;
|
|
|
|
case torrent_status::checking_resume_data:
|
|
|
|
return STATE_QUEUED_FASTCHECK;
|
|
|
|
case torrent_status::checking_files:
|
|
|
|
return m_torrent.is_seed(m_lastStatus) ? STATE_CHECKING_UP : STATE_CHECKING_DL;
|
|
|
|
default:
|
|
|
|
return STATE_INVALID;
|
|
|
|
}
|
|
|
|
} catch(invalid_handle&) {
|
|
|
|
return STATE_INVALID;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
QIcon TorrentModelItem::getIconByState(State state) {
|
|
|
|
switch (state) {
|
|
|
|
case STATE_DOWNLOADING:
|
|
|
|
case STATE_DOWNLOADING_META:
|
|
|
|
case STATE_FORCED_DL:
|
|
|
|
return get_downloading_icon();
|
|
|
|
case STATE_ALLOCATING:
|
|
|
|
case STATE_STALLED_DL:
|
|
|
|
return get_stalled_downloading_icon();
|
|
|
|
case STATE_STALLED_UP:
|
|
|
|
return get_stalled_uploading_icon();
|
|
|
|
case STATE_SEEDING:
|
|
|
|
case STATE_FORCED_UP:
|
|
|
|
return get_uploading_icon();
|
|
|
|
case STATE_PAUSED_DL:
|
|
|
|
return get_paused_icon();
|
|
|
|
case STATE_PAUSED_UP:
|
|
|
|
return get_completed_icon();
|
|
|
|
case STATE_QUEUED_DL:
|
|
|
|
case STATE_QUEUED_UP:
|
|
|
|
return get_queued_icon();
|
|
|
|
case STATE_CHECKING_UP:
|
|
|
|
case STATE_CHECKING_DL:
|
|
|
|
case STATE_QUEUED_CHECK:
|
|
|
|
case STATE_QUEUED_FASTCHECK:
|
|
|
|
return get_checking_icon();
|
|
|
|
case STATE_INVALID:
|
|
|
|
case STATE_PAUSED_MISSING:
|
|
|
|
return get_error_icon();
|
|
|
|
default:
|
|
|
|
Q_ASSERT(false);
|
|
|
|
return get_error_icon();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
QColor TorrentModelItem::getColorByState(State state) {
|
|
|
|
bool dark = isDarkTheme();
|
|
|
|
switch (state) {
|
|
|
|
case STATE_DOWNLOADING:
|
|
|
|
case STATE_DOWNLOADING_META:
|
|
|
|
case STATE_FORCED_DL:
|
|
|
|
return QColor(34, 139, 34); // Forest Green
|
|
|
|
case STATE_ALLOCATING:
|
|
|
|
case STATE_STALLED_DL:
|
|
|
|
case STATE_STALLED_UP:
|
|
|
|
if (!dark)
|
|
|
|
return QColor(0, 0, 0); // Black
|
|
|
|
else
|
|
|
|
return QColor(255, 255, 255); // White
|
|
|
|
case STATE_SEEDING:
|
|
|
|
case STATE_FORCED_UP:
|
|
|
|
if (!dark)
|
|
|
|
return QColor(65, 105, 225); // Royal Blue
|
|
|
|
else
|
|
|
|
return QColor(100, 149, 237); // Cornflower Blue
|
|
|
|
case STATE_PAUSED_DL:
|
|
|
|
return QColor(250, 128, 114); // Salmon
|
|
|
|
case STATE_PAUSED_UP:
|
|
|
|
if (!dark)
|
|
|
|
return QColor(0, 0, 139); // Dark Blue
|
|
|
|
else
|
|
|
|
return QColor(65, 105, 225); // Royal Blue
|
|
|
|
case STATE_PAUSED_MISSING:
|
|
|
|
return QColor(255, 0, 0); // red
|
|
|
|
case STATE_QUEUED_DL:
|
|
|
|
case STATE_QUEUED_UP:
|
|
|
|
case STATE_CHECKING_UP:
|
|
|
|
case STATE_CHECKING_DL:
|
|
|
|
case STATE_QUEUED_CHECK:
|
|
|
|
case STATE_QUEUED_FASTCHECK:
|
|
|
|
return QColor(0, 128, 128); // Teal
|
|
|
|
case STATE_INVALID:
|
|
|
|
return QColor(255, 0, 0); // red
|
|
|
|
default:
|
|
|
|
Q_ASSERT(false);
|
|
|
|
return QColor(255, 0, 0); // red
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
bool TorrentModelItem::setData(int column, const QVariant &value, int role)
|
|
|
|
{
|
|
|
|
qDebug() << Q_FUNC_INFO << column << value;
|
|
|
|
if (role != Qt::DisplayRole) return false;
|
|
|
|
// Label, seed date and Name columns can be edited
|
|
|
|
switch(column) {
|
|
|
|
case TR_NAME:
|
|
|
|
m_name = value.toString();
|
|
|
|
TorrentPersistentData::instance()->saveName(m_torrent.hash(), m_name);
|
|
|
|
return true;
|
|
|
|
case TR_LABEL: {
|
|
|
|
QString new_label = value.toString();
|
|
|
|
if (m_label != new_label) {
|
|
|
|
QString old_label = m_label;
|
|
|
|
m_label = new_label;
|
|
|
|
TorrentPersistentData::instance()->saveLabel(m_torrent.hash(), new_label);
|
|
|
|
emit labelChanged(old_label, new_label);
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
QVariant TorrentModelItem::data(int column, int role) const
|
|
|
|
{
|
|
|
|
if (role == Qt::DecorationRole && column == TR_NAME) {
|
|
|
|
return getIconByState(state());
|
|
|
|
}
|
|
|
|
if (role == Qt::ForegroundRole) {
|
|
|
|
return getColorByState(state());
|
|
|
|
}
|
|
|
|
if (role != Qt::DisplayRole && role != Qt::UserRole) return QVariant();
|
|
|
|
switch(column) {
|
|
|
|
case TR_NAME:
|
|
|
|
return m_name.isEmpty() ? m_torrent.name() : m_name;
|
|
|
|
case TR_PRIORITY: {
|
|
|
|
int pos = m_torrent.queue_position(m_lastStatus);
|
|
|
|
if (pos > -1)
|
|
|
|
return pos - HiddenData::getSize();
|
|
|
|
else
|
|
|
|
return pos;
|
|
|
|
}
|
|
|
|
case TR_SIZE:
|
|
|
|
return m_lastStatus.has_metadata ? static_cast<qlonglong>(m_lastStatus.total_wanted) : -1;
|
|
|
|
case TR_PROGRESS:
|
|
|
|
return m_torrent.progress(m_lastStatus);
|
|
|
|
case TR_STATUS:
|
|
|
|
return state();
|
|
|
|
case TR_SEEDS: {
|
|
|
|
return (role == Qt::DisplayRole) ? m_lastStatus.num_seeds : m_lastStatus.num_complete;
|
|
|
|
}
|
|
|
|
case TR_PEERS: {
|
|
|
|
return (role == Qt::DisplayRole) ? (m_lastStatus.num_peers-m_lastStatus.num_seeds) : m_lastStatus.num_incomplete;
|
|
|
|
}
|
|
|
|
case TR_DLSPEED:
|
|
|
|
return m_lastStatus.download_payload_rate;
|
|
|
|
case TR_UPSPEED:
|
|
|
|
return m_lastStatus.upload_payload_rate;
|
|
|
|
case TR_ETA: {
|
|
|
|
// XXX: Is this correct?
|
|
|
|
if (m_torrent.is_paused(m_lastStatus) || m_torrent.is_queued(m_lastStatus)) return MAX_ETA;
|
|
|
|
return QBtSession::instance()->getETA(m_hash, m_lastStatus);
|
|
|
|
}
|
|
|
|
case TR_RATIO:
|
|
|
|
return QBtSession::instance()->getRealRatio(m_lastStatus);
|
|
|
|
case TR_LABEL:
|
|
|
|
return m_label;
|
|
|
|
case TR_ADD_DATE:
|
|
|
|
return m_addedTime;
|
|
|
|
case TR_SEED_DATE:
|
|
|
|
return m_lastStatus.completed_time ? QDateTime::fromTime_t(m_lastStatus.completed_time) : QDateTime();
|
|
|
|
case TR_TRACKER:
|
|
|
|
return misc::toQString(m_lastStatus.current_tracker);
|
|
|
|
case TR_DLLIMIT:
|
|
|
|
return m_torrent.download_limit();
|
|
|
|
case TR_UPLIMIT:
|
|
|
|
return m_torrent.upload_limit();
|
|
|
|
case TR_AMOUNT_DOWNLOADED:
|
|
|
|
return static_cast<qlonglong>(m_lastStatus.all_time_download);
|
|
|
|
case TR_AMOUNT_UPLOADED:
|
|
|
|
return static_cast<qlonglong>(m_lastStatus.all_time_upload);
|
|
|
|
case TR_AMOUNT_DOWNLOADED_SESSION:
|
|
|
|
return static_cast<qlonglong>(m_lastStatus.total_payload_download);
|
|
|
|
case TR_AMOUNT_UPLOADED_SESSION:
|
|
|
|
return static_cast<qlonglong>(m_lastStatus.total_payload_upload);
|
|
|
|
case TR_AMOUNT_LEFT:
|
|
|
|
return static_cast<qlonglong>(m_lastStatus.total_wanted - m_lastStatus.total_wanted_done);
|
|
|
|
case TR_TIME_ELAPSED:
|
|
|
|
return (role == Qt::DisplayRole) ? m_lastStatus.active_time : m_lastStatus.seeding_time;
|
|
|
|
case TR_SAVE_PATH:
|
|
|
|
return fsutils::toNativePath(m_torrent.save_path_parsed());
|
|
|
|
case TR_COMPLETED:
|
|
|
|
return static_cast<qlonglong>(m_lastStatus.total_wanted_done);
|
|
|
|
case TR_RATIO_LIMIT: {
|
|
|
|
QString hash = misc::toQString(m_lastStatus.info_hash);
|
|
|
|
return QBtSession::instance()->getMaxRatioPerTorrent(hash, NULL);
|
|
|
|
}
|
|
|
|
case TR_SEEN_COMPLETE_DATE:
|
|
|
|
return m_lastStatus.last_seen_complete ? QDateTime::fromTime_t(m_lastStatus.last_seen_complete) : QDateTime();
|
|
|
|
case TR_LAST_ACTIVITY:
|
|
|
|
if (m_torrent.is_paused(m_lastStatus) || m_torrent.is_checking(m_lastStatus))
|
|
|
|
return -1;
|
|
|
|
if (m_lastStatus.time_since_upload < m_lastStatus.time_since_download)
|
|
|
|
return m_lastStatus.time_since_upload;
|
|
|
|
else
|
|
|
|
return m_lastStatus.time_since_download;
|
|
|
|
case TR_TOTAL_SIZE:
|
|
|
|
return m_lastStatus.has_metadata ? static_cast<qlonglong>(m_torrent.total_size()) : -1;
|
|
|
|
default:
|
|
|
|
return QVariant();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
QTorrentHandle TorrentModelItem::torrentHandle() const
|
|
|
|
{
|
|
|
|
return m_torrent;
|
|
|
|
}
|
|
|
|
|
|
|
|
// TORRENT MODEL
|
|
|
|
|
|
|
|
TorrentModel::TorrentModel(QObject *parent) :
|
|
|
|
QAbstractListModel(parent), m_refreshInterval(2000)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
void TorrentModel::populate() {
|
|
|
|
// Load the torrents
|
|
|
|
std::vector<torrent_handle> torrents = QBtSession::instance()->getSession()->get_torrents();
|
|
|
|
|
|
|
|
std::vector<torrent_handle>::const_iterator it = torrents.begin();
|
|
|
|
std::vector<torrent_handle>::const_iterator itend = torrents.end();
|
|
|
|
for ( ; it != itend; ++it) {
|
|
|
|
const QTorrentHandle h(*it);
|
|
|
|
if (HiddenData::hasData(h.hash()))
|
|
|
|
continue;
|
|
|
|
addTorrent(h);
|
|
|
|
}
|
|
|
|
// Refresh timer
|
|
|
|
connect(&m_refreshTimer, SIGNAL(timeout()), SLOT(forceModelRefresh()));
|
|
|
|
m_refreshTimer.start(m_refreshInterval);
|
|
|
|
// Listen for torrent changes
|
|
|
|
connect(QBtSession::instance(), SIGNAL(addedTorrent(QTorrentHandle)), SLOT(addTorrent(QTorrentHandle)));
|
|
|
|
connect(QBtSession::instance(), SIGNAL(torrentAboutToBeRemoved(QTorrentHandle)), SLOT(handleTorrentAboutToBeRemoved(QTorrentHandle)));
|
|
|
|
connect(QBtSession::instance(), SIGNAL(finishedTorrent(QTorrentHandle)), SLOT(handleFinishedTorrent(QTorrentHandle)));
|
|
|
|
connect(QBtSession::instance(), SIGNAL(metadataReceived(QTorrentHandle)), SLOT(handleTorrentUpdate(QTorrentHandle)));
|
|
|
|
connect(QBtSession::instance(), SIGNAL(resumedTorrent(QTorrentHandle)), SLOT(handleTorrentUpdate(QTorrentHandle)));
|
|
|
|
connect(QBtSession::instance(), SIGNAL(pausedTorrent(QTorrentHandle)), SLOT(handleTorrentUpdate(QTorrentHandle)));
|
|
|
|
connect(QBtSession::instance(), SIGNAL(torrentFinishedChecking(QTorrentHandle)), SLOT(handleTorrentUpdate(QTorrentHandle)));
|
|
|
|
connect(QBtSession::instance(), SIGNAL(stateUpdate(std::vector<libtorrent::torrent_status>)), SLOT(stateUpdated(std::vector<libtorrent::torrent_status>)));
|
|
|
|
}
|
|
|
|
|
|
|
|
TorrentModel::~TorrentModel() {
|
|
|
|
qDebug() << Q_FUNC_INFO << "ENTER";
|
|
|
|
qDeleteAll(m_torrents);
|
|
|
|
m_torrents.clear();
|
|
|
|
qDebug() << Q_FUNC_INFO << "EXIT";
|
|
|
|
}
|
|
|
|
|
|
|
|
QVariant TorrentModel::headerData(int section, Qt::Orientation orientation,
|
|
|
|
int role) const
|
|
|
|
{
|
|
|
|
if (orientation == Qt::Horizontal) {
|
|
|
|
if (role == Qt::DisplayRole) {
|
|
|
|
switch(section) {
|
|
|
|
case TorrentModelItem::TR_NAME: return tr("Name", "i.e: torrent name");
|
|
|
|
case TorrentModelItem::TR_PRIORITY: return "#";
|
|
|
|
case TorrentModelItem::TR_SIZE: return tr("Size", "i.e: torrent size");
|
|
|
|
case TorrentModelItem::TR_PROGRESS: return tr("Done", "% Done");
|
|
|
|
case TorrentModelItem::TR_STATUS: return tr("Status", "Torrent status (e.g. downloading, seeding, paused)");
|
|
|
|
case TorrentModelItem::TR_SEEDS: return tr("Seeds", "i.e. full sources (often untranslated)");
|
|
|
|
case TorrentModelItem::TR_PEERS: return tr("Peers", "i.e. partial sources (often untranslated)");
|
|
|
|
case TorrentModelItem::TR_DLSPEED: return tr("Down Speed", "i.e: Download speed");
|
|
|
|
case TorrentModelItem::TR_UPSPEED: return tr("Up Speed", "i.e: Upload speed");
|
|
|
|
case TorrentModelItem::TR_RATIO: return tr("Ratio", "Share ratio");
|
|
|
|
case TorrentModelItem::TR_ETA: return tr("ETA", "i.e: Estimated Time of Arrival / Time left");
|
|
|
|
case TorrentModelItem::TR_LABEL: return tr("Label");
|
|
|
|
case TorrentModelItem::TR_ADD_DATE: return tr("Added On", "Torrent was added to transfer list on 01/01/2010 08:00");
|
|
|
|
case TorrentModelItem::TR_SEED_DATE: return tr("Completed On", "Torrent was completed on 01/01/2010 08:00");
|
|
|
|
case TorrentModelItem::TR_TRACKER: return tr("Tracker");
|
|
|
|
case TorrentModelItem::TR_DLLIMIT: return tr("Down Limit", "i.e: Download limit");
|
|
|
|
case TorrentModelItem::TR_UPLIMIT: return tr("Up Limit", "i.e: Upload limit");
|
|
|
|
case TorrentModelItem::TR_AMOUNT_DOWNLOADED: return tr("Downloaded", "Amount of data downloaded (e.g. in MB)");
|
|
|
|
case TorrentModelItem::TR_AMOUNT_UPLOADED: return tr("Uploaded", "Amount of data uploaded (e.g. in MB)");
|
|
|
|
case TorrentModelItem::TR_AMOUNT_DOWNLOADED_SESSION: return tr("Session Download", "Amount of data downloaded since program open (e.g. in MB)");
|
|
|
|
case TorrentModelItem::TR_AMOUNT_UPLOADED_SESSION: return tr("Session Upload", "Amount of data uploaded since program open (e.g. in MB)");
|
|
|
|
case TorrentModelItem::TR_AMOUNT_LEFT: return tr("Remaining", "Amount of data left to download (e.g. in MB)");
|
|
|
|
case TorrentModelItem::TR_TIME_ELAPSED: return tr("Time Active", "Time (duration) the torrent is active (not paused)");
|
|
|
|
case TorrentModelItem::TR_SAVE_PATH: return tr("Save path", "Torrent save path");
|
|
|
|
case TorrentModelItem::TR_COMPLETED: return tr("Completed", "Amount of data completed (e.g. in MB)");
|
|
|
|
case TorrentModelItem::TR_RATIO_LIMIT: return tr("Ratio Limit", "Upload share ratio limit");
|
|
|
|
case TorrentModelItem::TR_SEEN_COMPLETE_DATE: return tr("Last Seen Complete", "Indicates the time when the torrent was last seen complete/whole");
|
|
|
|
case TorrentModelItem::TR_LAST_ACTIVITY: return tr("Last Activity", "Time passed since a chunk was downloaded/uploaded");
|
|
|
|
case TorrentModelItem::TR_TOTAL_SIZE: return tr("Total Size", "i.e. Size including unwanted data");
|
|
|
|
default:
|
|
|
|
return QVariant();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (role == Qt::TextAlignmentRole) {
|
|
|
|
switch(section) {
|
|
|
|
case TorrentModelItem::TR_AMOUNT_DOWNLOADED:
|
|
|
|
case TorrentModelItem::TR_AMOUNT_UPLOADED:
|
|
|
|
case TorrentModelItem::TR_AMOUNT_DOWNLOADED_SESSION:
|
|
|
|
case TorrentModelItem::TR_AMOUNT_UPLOADED_SESSION:
|
|
|
|
case TorrentModelItem::TR_AMOUNT_LEFT:
|
|
|
|
case TorrentModelItem::TR_COMPLETED:
|
|
|
|
case TorrentModelItem::TR_SIZE:
|
|
|
|
case TorrentModelItem::TR_TOTAL_SIZE:
|
|
|
|
case TorrentModelItem::TR_ETA:
|
|
|
|
case TorrentModelItem::TR_SEEDS:
|
|
|
|
case TorrentModelItem::TR_PEERS:
|
|
|
|
case TorrentModelItem::TR_UPSPEED:
|
|
|
|
case TorrentModelItem::TR_DLSPEED:
|
|
|
|
case TorrentModelItem::TR_UPLIMIT:
|
|
|
|
case TorrentModelItem::TR_DLLIMIT:
|
|
|
|
case TorrentModelItem::TR_RATIO_LIMIT:
|
|
|
|
case TorrentModelItem::TR_RATIO:
|
|
|
|
case TorrentModelItem::TR_PRIORITY:
|
|
|
|
case TorrentModelItem::TR_LAST_ACTIVITY:
|
|
|
|
return QVariant(Qt::AlignRight | Qt::AlignVCenter);
|
|
|
|
default:
|
|
|
|
return QAbstractListModel::headerData(section, orientation, role);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return QVariant();
|
|
|
|
}
|
|
|
|
|
|
|
|
QVariant TorrentModel::data(const QModelIndex &index, int role) const
|
|
|
|
{
|
|
|
|
if (!index.isValid()) return QVariant();
|
|
|
|
try {
|
|
|
|
if (index.row() >= 0 && index.row() < rowCount() && index.column() >= 0 && index.column() < columnCount())
|
|
|
|
return m_torrents[index.row()]->data(index.column(), role);
|
|
|
|
} catch(invalid_handle&) {}
|
|
|
|
return QVariant();
|
|
|
|
}
|
|
|
|
|
|
|
|
bool TorrentModel::setData(const QModelIndex &index, const QVariant &value, int role)
|
|
|
|
{
|
|
|
|
qDebug() << Q_FUNC_INFO << value;
|
|
|
|
if (!index.isValid() || role != Qt::DisplayRole) return false;
|
|
|
|
qDebug("Index is valid and role is DisplayRole");
|
|
|
|
try {
|
|
|
|
if (index.row() >= 0 && index.row() < rowCount() && index.column() >= 0 && index.column() < columnCount()) {
|
|
|
|
bool change = m_torrents[index.row()]->setData(index.column(), value, role);
|
|
|
|
if (change)
|
|
|
|
notifyTorrentChanged(index.row());
|
|
|
|
return change;
|
|
|
|
}
|
|
|
|
} catch(invalid_handle&) {}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
int TorrentModel::torrentRow(const QString &hash) const
|
|
|
|
{
|
|
|
|
int row = 0;
|
|
|
|
|
|
|
|
QList<TorrentModelItem*>::const_iterator it = m_torrents.constBegin();
|
|
|
|
QList<TorrentModelItem*>::const_iterator itend = m_torrents.constEnd();
|
|
|
|
for ( ; it != itend; ++it) {
|
|
|
|
if ((*it)->hash() == hash) return row;
|
|
|
|
++row;
|
|
|
|
}
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
void TorrentModel::addTorrent(const QTorrentHandle &h)
|
|
|
|
{
|
|
|
|
if (torrentRow(h.hash()) < 0) {
|
|
|
|
beginInsertTorrent(m_torrents.size());
|
|
|
|
TorrentModelItem *item = new TorrentModelItem(h);
|
|
|
|
connect(item, SIGNAL(labelChanged(QString,QString)), SLOT(handleTorrentLabelChange(QString,QString)));
|
|
|
|
m_torrents << item;
|
|
|
|
emit torrentAdded(item);
|
|
|
|
endInsertTorrent();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void TorrentModel::beginInsertTorrent(int row)
|
|
|
|
{
|
|
|
|
beginInsertRows(QModelIndex(), row, row);
|
|
|
|
}
|
|
|
|
|
|
|
|
void TorrentModel::endInsertTorrent()
|
|
|
|
{
|
|
|
|
endInsertRows();
|
|
|
|
}
|
|
|
|
|
|
|
|
void TorrentModel::beginRemoveTorrent(int row)
|
|
|
|
{
|
|
|
|
beginRemoveRows(QModelIndex(), row, row);
|
|
|
|
}
|
|
|
|
|
|
|
|
void TorrentModel::endRemoveTorrent()
|
|
|
|
{
|
|
|
|
endRemoveRows();
|
|
|
|
}
|
|
|
|
|
|
|
|
void TorrentModel::handleTorrentUpdate(const QTorrentHandle &h)
|
|
|
|
{
|
|
|
|
const int row = torrentRow(h.hash());
|
|
|
|
if (row >= 0) {
|
|
|
|
// This line changes the torrent name when magnet is retrieved.
|
|
|
|
// When magnet link is added, "dn" parameter is used as name, but when metadata is retrieved
|
|
|
|
// we change the name with the retrieved torrent name.
|
|
|
|
m_torrents[row]->setData(TorrentModelItem::TR_NAME, h.name(), Qt::DisplayRole);
|
|
|
|
|
|
|
|
m_torrents[row]->refreshStatus(h.status(torrent_handle::query_accurate_download_counters));
|
|
|
|
notifyTorrentChanged(row);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void TorrentModel::handleFinishedTorrent(const QTorrentHandle& h)
|
|
|
|
{
|
|
|
|
const int row = torrentRow(h.hash());
|
|
|
|
if (row < 0)
|
|
|
|
return;
|
|
|
|
|
|
|
|
// Update completion date
|
|
|
|
m_torrents[row]->refreshStatus(h.status(torrent_handle::query_accurate_download_counters));
|
|
|
|
notifyTorrentChanged(row);
|
|
|
|
}
|
|
|
|
|
|
|
|
void TorrentModel::notifyTorrentChanged(int row)
|
|
|
|
{
|
|
|
|
emit dataChanged(index(row, 0), index(row, columnCount()-1));
|
|
|
|
}
|
|
|
|
|
|
|
|
void TorrentModel::setRefreshInterval(int refreshInterval)
|
|
|
|
{
|
|
|
|
if (m_refreshInterval != refreshInterval) {
|
|
|
|
m_refreshInterval = refreshInterval;
|
|
|
|
m_refreshTimer.stop();
|
|
|
|
m_refreshTimer.start(m_refreshInterval);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void TorrentModel::forceModelRefresh()
|
|
|
|
{
|
|
|
|
QBtSession::instance()->postTorrentUpdate();
|
|
|
|
}
|
|
|
|
|
|
|
|
TorrentStatusReport TorrentModel::getTorrentStatusReport() const
|
|
|
|
{
|
|
|
|
TorrentStatusReport report;
|
|
|
|
|
|
|
|
QList<TorrentModelItem*>::const_iterator it = m_torrents.constBegin();
|
|
|
|
QList<TorrentModelItem*>::const_iterator itend = m_torrents.constEnd();
|
|
|
|
for ( ; it != itend; ++it) {
|
|
|
|
switch((*it)->state()) {
|
|
|
|
case TorrentModelItem::STATE_DOWNLOADING:
|
|
|
|
case TorrentModelItem::STATE_FORCED_DL:
|
|
|
|
++report.nb_active;
|
|
|
|
++report.nb_downloading;
|
|
|
|
break;
|
|
|
|
case TorrentModelItem::STATE_DOWNLOADING_META:
|
|
|
|
++report.nb_downloading;
|
|
|
|
break;
|
|
|
|
case TorrentModelItem::STATE_PAUSED_DL:
|
|
|
|
case TorrentModelItem::STATE_PAUSED_MISSING:
|
|
|
|
++report.nb_paused;
|
|
|
|
case TorrentModelItem::STATE_STALLED_DL:
|
|
|
|
case TorrentModelItem::STATE_CHECKING_DL:
|
|
|
|
case TorrentModelItem::STATE_QUEUED_DL: {
|
|
|
|
++report.nb_inactive;
|
|
|
|
++report.nb_downloading;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case TorrentModelItem::STATE_SEEDING:
|
|
|
|
case TorrentModelItem::STATE_FORCED_UP:
|
|
|
|
++report.nb_active;
|
|
|
|
++report.nb_seeding;
|
|
|
|
++report.nb_completed;
|
|
|
|
break;
|
|
|
|
case TorrentModelItem::STATE_STALLED_UP:
|
|
|
|
case TorrentModelItem::STATE_CHECKING_UP:
|
|
|
|
case TorrentModelItem::STATE_QUEUED_UP:
|
|
|
|
++report.nb_seeding;
|
|
|
|
case TorrentModelItem::STATE_PAUSED_UP:
|
|
|
|
++report.nb_completed;
|
|
|
|
++report.nb_inactive;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return report;
|
|
|
|
}
|
|
|
|
|
|
|
|
Qt::ItemFlags TorrentModel::flags(const QModelIndex &index) const
|
|
|
|
{
|
|
|
|
if (!index.isValid())
|
|
|
|
return 0;
|
|
|
|
// Explicitely mark as editable
|
|
|
|
return QAbstractListModel::flags(index) | Qt::ItemIsEditable;
|
|
|
|
}
|
|
|
|
|
|
|
|
void TorrentModel::handleTorrentLabelChange(QString previous, QString current)
|
|
|
|
{
|
|
|
|
emit torrentChangedLabel(static_cast<TorrentModelItem*>(sender()), previous, current);
|
|
|
|
}
|
|
|
|
|
|
|
|
QString TorrentModel::torrentHash(int row) const
|
|
|
|
{
|
|
|
|
if (row >= 0 && row < rowCount())
|
|
|
|
return m_torrents.at(row)->hash();
|
|
|
|
return QString();
|
|
|
|
}
|
|
|
|
|
|
|
|
void TorrentModel::handleTorrentAboutToBeRemoved(const QTorrentHandle &h)
|
|
|
|
{
|
|
|
|
const int row = torrentRow(h.hash());
|
|
|
|
qDebug() << Q_FUNC_INFO << row;
|
|
|
|
if (row >= 0) {
|
|
|
|
emit torrentAboutToBeRemoved(m_torrents.at(row));
|
Fix torrent removal. Closes #2132
It was reported that qbittorrent crashes on Mac OS X when user deletes
torrents from label view when label filter is active.
The callstack of the crash is the following:
1 abort + 129
2 __assert_rtn + 321
3 QTorrentHandle::total_size() const + 124
4 TorrentModelItem::data(int, int) const + 307
5 TorrentModel::data(QModelIndex const&, int) const + 255
6 QSortFilterProxyModel::data(QModelIndex const&, int) const + 109
7 QSortFilterProxyModel::data(QModelIndex const&, int) const + 109
8 QSortFilterProxyModel::data(QModelIndex const&, int) const + 109
9 QItemDelegate::rect(QStyleOptionViewItem const&, QModelIndex const&, int) const + 75
10 QItemDelegate::sizeHint(QStyleOptionViewItem const&, QModelIndex const&) const + 172
11 TransferListDelegate::sizeHint(QStyleOptionViewItem const&, QModelIndex const&) const + 14
12 QTreeView::indexRowSizeHint(QModelIndex const&) const + 887
13 QTreeViewPrivate::layout(int, bool, bool) + 462
14 QTreeView::doItemsLayout() + 356
15 QTreeViewPrivate::updateScrollBars() + 109
16 QTreeView::scrollTo(QModelIndex const&, QAbstractItemView::ScrollHint) + 124
17 TransferListWidget::currentChanged(QModelIndex const&, QModelIndex const&) + 548
18 TransferListWidget::qt_static_metacall(QObject*, QMetaObject::Call, int, void**) + 641
19 QMetaObject::activate(QObject*, QMetaObject const*, int, void**) + 2196
20 QItemSelectionModelPrivate::_q_rowsAboutToBeRemoved(QModelIndex const&, int, int) + 3729
21 QItemSelectionModel::qt_static_metacall(QObject*, QMetaObject::Call, int, void**) + 398
22 QMetaObject::activate(QObject*, QMetaObject const*, int, void**) + 2196
23 QAbstractItemModel::rowsAboutToBeRemoved(QModelIndex const&, int, int) + 78
24 QAbstractItemModel::beginRemoveRows(QModelIndex const&, int, int) + 106
25 QSortFilterProxyModelPrivate::remove_proxy_interval(QVector<int>&, QVector<int>&, int, int, QModelIndex const&, Qt::Orientation, bool) + 58
26 QSortFilterProxyModelPrivate::remove_source_items(QVector<int>&, QVector<int>&, QVector<int> const&, QModelIndex const&, Qt::Orientation, bool) + 265
27 QSortFilterProxyModelPrivate::source_items_about_to_be_removed(QModelIndex const&, int, int, Qt::Orientation) + 232
28 QMetaObject::activate(QObject*, QMetaObject const*, int, void**) + 2196
29 QAbstractItemModel::rowsAboutToBeRemoved(QModelIndex const&, int, int) + 78
30 QAbstractItemModel::beginRemoveRows(QModelIndex const&, int, int) + 106
31 QSortFilterProxyModelPrivate::remove_proxy_interval(QVector<int>&, QVector<int>&, int, int, QModelIndex const&, Qt::Orientation, bool) + 58
32 QSortFilterProxyModelPrivate::remove_source_items(QVector<int>&, QVector<int>&, QVector<int> const&, QModelIndex const&, Qt::Orientation, bool) + 265
33 QSortFilterProxyModelPrivate::source_items_about_to_be_removed(QModelIndex const&, int, int, Qt::Orientation) + 232
34 QMetaObject::activate(QObject*, QMetaObject const*, int, void**) + 2196
35 QAbstractItemModel::rowsAboutToBeRemoved(QModelIndex const&, int, int) + 78
36 QAbstractItemModel::beginRemoveRows(QModelIndex const&, int, int) + 106
37 QSortFilterProxyModelPrivate::remove_proxy_interval(QVector<int>&, QVector<int>&, int, int, QModelIndex const&, Qt::Orientation, bool) + 58
38 QSortFilterProxyModelPrivate::remove_source_items(QVector<int>&, QVector<int>&, QVector<int> const&, QModelIndex const&, Qt::Orientation, bool) + 265
39 QSortFilterProxyModelPrivate::source_items_about_to_be_removed(QModelIndex const&, int, int, Qt::Orientation) + 232
40 QMetaObject::activate(QObject*, QMetaObject const*, int, void**) + 2196
41 QAbstractItemModel::rowsAboutToBeRemoved(QModelIndex const&, int, int) + 78
42 QAbstractItemModel::beginRemoveRows(QModelIndex const&, int, int) + 106
43 TorrentModel::removeTorrent(QString const&) + 81
44 TorrentModel::qt_static_metacall(QObject*, QMetaObject::Call, int, void**) + 345
45 QMetaObject::activate(QObject*, QMetaObject const*, int, void**) + 2196
46 QBtSession::deletedTorrent(QString const&) + 56
47 QBtSession::deleteTorrent(QString const&, bool) + 2855
48 TransferListWidget::deleteSelectedTorrents() + 292
49 TransferListWidget::qt_static_metacall(QObject*, QMetaObject::Call, int, void**) + 230
50 QMetaObject::activate(QObject*, QMetaObject const*, int, void**) + 2196
51 QAction::activate(QAction::ActionEvent) + 227
52 QMenuPrivate::activateCausedStack(QList<QPointer<QWidget> > const&, QAction*, QAction::ActionEvent, bool) + 77
53 QMenuPrivate::activateAction(QAction*, QAction::ActionEvent, bool) + 470
54 QWidget::event(QEvent*) + 687
55 QMenu::event(QEvent*) + 617
56 QApplicationPrivate::notify_helper(QObject*, QEvent*) + 194
57 QApplication::notify(QObject*, QEvent*) + 2716
58 SessionApplication::notify(QObject*, QEvent*) + 21
59 QCoreApplication::notifyInternal(QObject*, QEvent*) + 118
60 QApplicationPrivate::sendMouseEvent(QWidget*, QMouseEvent*, QWidget*, QWidget*, QWidget**, QPointer<QWidget>&, bool) + 448
61 qt_mac_handleMouseEvent(NSEvent*, QEvent::Type, Qt::MouseButton, QWidget*, bool) + 1300
62 -[NSWindow _reallySendEvent:] + 759
63 -[NSWindow sendEvent:] + 368
64 -[QCocoaPanel sendEvent:] + 113
65 -[NSApplication sendEvent:] + 2238
66 -[QNSApplication sendEvent:] + 97
67 -[NSApplication run] + 711
68 QEventDispatcherMac::processEvents(QFlags<QEventLoop::ProcessEventsFlag>) + 1522
69 QEventLoop::processEvents(QFlags<QEventLoop::ProcessEventsFlag>) + 77
70 QEventLoop::exec(QFlags<QEventLoop::ProcessEventsFlag>) + 370
71 QMenu::exec(QPoint const&, QAction*) + 103
72 TransferListWidget::displayListMenu(QPoint const&) + 8741
73 TransferListWidget::qt_static_metacall(QObject*, QMetaObject::Call, int, void**) + 622
74 QMetaObject::activate(QObject*, QMetaObject const*, int, void**) + 2196
75 QWidget::event(QEvent*) + 3082
76 QFrame::event(QEvent*) + 45
77 QAbstractScrollArea::viewportEvent(QEvent*) + 108
78 QAbstractItemView::viewportEvent(QEvent*) + 1390
79 QTreeView::viewportEvent(QEvent*) + 218
80 QAbstractScrollAreaFilter::eventFilter(QObject*, QEvent*) + 37
81 QCoreApplicationPrivate::sendThroughObjectEventFilters(QObject*, QEvent*) + 115
82 QApplicationPrivate::notify_helper(QObject*, QEvent*) + 178
83 QApplication::notify(QObject*, QEvent*) + 5742
84 SessionApplication::notify(QObject*, QEvent*) + 21
85 QCoreApplication::notifyInternal(QObject*, QEvent*) + 118
86 qt_sendSpontaneousEvent(QObject*, QEvent*) + 45
87 qt_mac_handleMouseEvent(NSEvent*, QEvent::Type, Qt::MouseButton, QWidget*, bool) + 1378
88 -[NSWindow _reallySendEvent:] + 5682
89 -[NSWindow sendEvent:] + 368
90 -[QCocoaWindow sendEvent:] + 113
91 -[NSApplication sendEvent:] + 2238
92 -[QNSApplication sendEvent:] + 97
93 -[NSApplication run] + 711
94 QEventDispatcherMac::processEvents(QFlags<QEventLoop::ProcessEventsFlag>) + 1522
95 QEventLoop::processEvents(QFlags<QEventLoop::ProcessEventsFlag>) + 77
96 QEventLoop::exec(QFlags<QEventLoop::ProcessEventsFlag>) + 370
97 QCoreApplication::exec() + 199
98 main + 3415
99 start + 52
As we can see the user deleted some torrent (48). QBtSession deleted the torrent
from libtorrent::session (47) and emitted a signal (46), about torrent deletion.
In responce to the signal (43) the TorrentModel notifies (42) its views about a change.
After a long chain of notifications (42-6) the view requested (5) a value of
total_size from TorrentModel. QTorrentHandle is already invalid as the torrent
was removed in (47). So we've got a crash in (3).
The fix is relatively straightforward: do notify TorrentModel about removal not after,
but before torrent is removed from libtorrent::session. This commit does the same
thing to TorrentSpeedMonitor.
This bug reveals a major flaw in a design: currently we have a several components all
subscribed to the torrent removal signal. Signal is delivered to them in arbitrary
order, but they access each other in the handlers of this signal. E.g. TorrentModel
can access TorrentSpeedMonitor. This doesn't lead to a crash because
TorrentSpeedMonitor returns MAX_ETA when ETA is queried for unknown torrent.
10 years ago
|
|
|
|
|
|
|
beginRemoveTorrent(row);
|
|
|
|
delete m_torrents[row];
|
|
|
|
m_torrents.removeAt(row);
|
|
|
|
endRemoveTorrent();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void TorrentModel::stateUpdated(const std::vector<libtorrent::torrent_status> &statuses) {
|
|
|
|
typedef std::vector<libtorrent::torrent_status> statuses_t;
|
|
|
|
|
|
|
|
for (statuses_t::const_iterator i = statuses.begin(), end = statuses.end(); i != end; ++i) {
|
|
|
|
libtorrent::torrent_status const& status = *i;
|
|
|
|
|
|
|
|
const int row = torrentRow(misc::toQString(status.info_hash));
|
|
|
|
if (row >= 0) {
|
|
|
|
m_torrents[row]->refreshStatus(status);
|
|
|
|
notifyTorrentChanged(row);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
emit modelRefreshed();
|
|
|
|
}
|
|
|
|
|
|
|
|
bool TorrentModel::inhibitSystem()
|
|
|
|
{
|
|
|
|
QList<TorrentModelItem*>::const_iterator it = m_torrents.constBegin();
|
|
|
|
QList<TorrentModelItem*>::const_iterator itend = m_torrents.constEnd();
|
|
|
|
for ( ; it != itend; ++it) {
|
|
|
|
switch((*it)->data(TorrentModelItem::TR_STATUS).toInt()) {
|
|
|
|
case TorrentModelItem::STATE_DOWNLOADING:
|
|
|
|
case TorrentModelItem::STATE_DOWNLOADING_META:
|
|
|
|
case TorrentModelItem::STATE_FORCED_DL:
|
|
|
|
case TorrentModelItem::STATE_STALLED_DL:
|
|
|
|
case TorrentModelItem::STATE_SEEDING:
|
|
|
|
case TorrentModelItem::STATE_FORCED_UP:
|
|
|
|
case TorrentModelItem::STATE_STALLED_UP:
|
|
|
|
return true;
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|