|
|
|
/*
|
|
|
|
* 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 <QIcon>
|
|
|
|
|
|
|
|
#include "core/bittorrent/session.h"
|
|
|
|
#include "core/torrentfilter.h"
|
|
|
|
#include "core/utils/fs.h"
|
|
|
|
#include "torrentmodel.h"
|
|
|
|
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
TorrentModelItem::TorrentModelItem(BitTorrent::TorrentHandle *torrent)
|
|
|
|
: m_torrent(torrent)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
BitTorrent::TorrentState TorrentModelItem::state() const
|
|
|
|
{
|
|
|
|
return m_torrent->state();
|
|
|
|
}
|
|
|
|
|
|
|
|
QIcon TorrentModelItem::getIconByState(BitTorrent::TorrentState state)
|
|
|
|
{
|
|
|
|
switch (state) {
|
|
|
|
case BitTorrent::TorrentState::Downloading:
|
|
|
|
case BitTorrent::TorrentState::ForcedDownloading:
|
|
|
|
case BitTorrent::TorrentState::DownloadingMetadata:
|
|
|
|
return get_downloading_icon();
|
|
|
|
case BitTorrent::TorrentState::Allocating:
|
|
|
|
case BitTorrent::TorrentState::StalledDownloading:
|
|
|
|
return get_stalled_downloading_icon();
|
|
|
|
case BitTorrent::TorrentState::StalledUploading:
|
|
|
|
return get_stalled_uploading_icon();
|
|
|
|
case BitTorrent::TorrentState::Uploading:
|
|
|
|
case BitTorrent::TorrentState::ForcedUploading:
|
|
|
|
return get_uploading_icon();
|
|
|
|
case BitTorrent::TorrentState::PausedDownloading:
|
|
|
|
return get_paused_icon();
|
|
|
|
case BitTorrent::TorrentState::PausedUploading:
|
|
|
|
return get_completed_icon();
|
|
|
|
case BitTorrent::TorrentState::QueuedDownloading:
|
|
|
|
case BitTorrent::TorrentState::QueuedUploading:
|
|
|
|
return get_queued_icon();
|
|
|
|
case BitTorrent::TorrentState::CheckingDownloading:
|
|
|
|
case BitTorrent::TorrentState::CheckingUploading:
|
|
|
|
return get_checking_icon();
|
|
|
|
case BitTorrent::TorrentState::Unknown:
|
|
|
|
case BitTorrent::TorrentState::Error:
|
|
|
|
return get_error_icon();
|
|
|
|
default:
|
|
|
|
Q_ASSERT(false);
|
|
|
|
return get_error_icon();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
QColor TorrentModelItem::getColorByState(BitTorrent::TorrentState state)
|
|
|
|
{
|
|
|
|
bool dark = isDarkTheme();
|
|
|
|
|
|
|
|
switch (state) {
|
|
|
|
case BitTorrent::TorrentState::Downloading:
|
|
|
|
case BitTorrent::TorrentState::ForcedDownloading:
|
|
|
|
case BitTorrent::TorrentState::DownloadingMetadata:
|
|
|
|
return QColor(34, 139, 34); // Forest Green
|
|
|
|
case BitTorrent::TorrentState::Allocating:
|
|
|
|
case BitTorrent::TorrentState::StalledDownloading:
|
|
|
|
case BitTorrent::TorrentState::StalledUploading:
|
|
|
|
if (!dark)
|
|
|
|
return QColor(0, 0, 0); // Black
|
|
|
|
else
|
|
|
|
return QColor(255, 255, 255); // White
|
|
|
|
case BitTorrent::TorrentState::Uploading:
|
|
|
|
case BitTorrent::TorrentState::ForcedUploading:
|
|
|
|
if (!dark)
|
|
|
|
return QColor(65, 105, 225); // Royal Blue
|
|
|
|
else
|
|
|
|
return QColor(100, 149, 237); // Cornflower Blue
|
|
|
|
case BitTorrent::TorrentState::PausedDownloading:
|
|
|
|
return QColor(250, 128, 114); // Salmon
|
|
|
|
case BitTorrent::TorrentState::PausedUploading:
|
|
|
|
if (!dark)
|
|
|
|
return QColor(0, 0, 139); // Dark Blue
|
|
|
|
else
|
|
|
|
return QColor(65, 105, 225); // Royal Blue
|
|
|
|
case BitTorrent::TorrentState::Error:
|
|
|
|
return QColor(255, 0, 0); // red
|
|
|
|
case BitTorrent::TorrentState::QueuedDownloading:
|
|
|
|
case BitTorrent::TorrentState::QueuedUploading:
|
|
|
|
case BitTorrent::TorrentState::CheckingDownloading:
|
|
|
|
case BitTorrent::TorrentState::CheckingUploading:
|
|
|
|
return QColor(0, 128, 128); // Teal
|
|
|
|
case BitTorrent::TorrentState::Unknown:
|
|
|
|
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_torrent->setName(value.toString());
|
|
|
|
return true;
|
|
|
|
case TR_LABEL: {
|
|
|
|
QString new_label = value.toString();
|
|
|
|
if (m_torrent->label() != new_label) {
|
|
|
|
QString old_label = m_torrent->label();
|
|
|
|
m_torrent->setLabel(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_torrent->name();
|
|
|
|
case TR_PRIORITY:
|
|
|
|
return m_torrent->queuePosition();
|
|
|
|
case TR_SIZE:
|
|
|
|
return m_torrent->hasMetadata() ? m_torrent->wantedSize() : -1;
|
|
|
|
case TR_PROGRESS:
|
|
|
|
return m_torrent->progress();
|
|
|
|
case TR_STATUS:
|
|
|
|
return static_cast<int>(m_torrent->state());
|
|
|
|
case TR_SEEDS:
|
|
|
|
return (role == Qt::DisplayRole) ? m_torrent->seedsCount() : m_torrent->completeCount();
|
|
|
|
case TR_PEERS:
|
|
|
|
return (role == Qt::DisplayRole) ? (m_torrent->peersCount() - m_torrent->seedsCount()) : m_torrent->incompleteCount();
|
|
|
|
case TR_DLSPEED:
|
|
|
|
return m_torrent->downloadPayloadRate();
|
|
|
|
case TR_UPSPEED:
|
|
|
|
return m_torrent->uploadPayloadRate();
|
|
|
|
case TR_ETA:
|
|
|
|
return m_torrent->eta();
|
|
|
|
case TR_RATIO:
|
|
|
|
return m_torrent->realRatio();
|
|
|
|
case TR_LABEL:
|
|
|
|
return m_torrent->label();
|
|
|
|
case TR_ADD_DATE:
|
|
|
|
return m_torrent->addedTime();
|
|
|
|
case TR_SEED_DATE:
|
|
|
|
return m_torrent->completedTime();
|
|
|
|
case TR_TRACKER:
|
|
|
|
return m_torrent->currentTracker();
|
|
|
|
case TR_DLLIMIT:
|
|
|
|
return m_torrent->downloadLimit();
|
|
|
|
case TR_UPLIMIT:
|
|
|
|
return m_torrent->uploadLimit();
|
|
|
|
case TR_AMOUNT_DOWNLOADED:
|
|
|
|
return m_torrent->totalDownload();
|
|
|
|
case TR_AMOUNT_UPLOADED:
|
|
|
|
return m_torrent->totalUpload();
|
|
|
|
case TR_AMOUNT_DOWNLOADED_SESSION:
|
|
|
|
return m_torrent->totalPayloadDownload();
|
|
|
|
case TR_AMOUNT_UPLOADED_SESSION:
|
|
|
|
return m_torrent->totalPayloadUpload();
|
|
|
|
case TR_AMOUNT_LEFT:
|
|
|
|
return m_torrent->incompletedSize();
|
|
|
|
case TR_TIME_ELAPSED:
|
|
|
|
return (role == Qt::DisplayRole) ? m_torrent->activeTime() : m_torrent->seedingTime();
|
|
|
|
case TR_SAVE_PATH:
|
|
|
|
return m_torrent->savePathParsed();
|
|
|
|
case TR_COMPLETED:
|
|
|
|
return m_torrent->completedSize();
|
|
|
|
case TR_RATIO_LIMIT:
|
|
|
|
return m_torrent->maxRatio();
|
|
|
|
case TR_SEEN_COMPLETE_DATE:
|
|
|
|
return m_torrent->lastSeenComplete();
|
|
|
|
case TR_LAST_ACTIVITY:
|
|
|
|
if (m_torrent->isPaused() || m_torrent->isChecking())
|
|
|
|
return -1;
|
|
|
|
if (m_torrent->timeSinceUpload() < m_torrent->timeSinceDownload())
|
|
|
|
return m_torrent->timeSinceUpload();
|
|
|
|
else
|
|
|
|
return m_torrent->timeSinceDownload();
|
|
|
|
case TR_TOTAL_SIZE:
|
|
|
|
return m_torrent->hasMetadata() ? m_torrent->totalSize() : -1;
|
|
|
|
default:
|
|
|
|
return QVariant();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
BitTorrent::TorrentHandle *TorrentModelItem::torrentHandle() const
|
|
|
|
{
|
|
|
|
return m_torrent;
|
|
|
|
}
|
|
|
|
|
|
|
|
// TORRENT MODEL
|
|
|
|
|
|
|
|
TorrentModel::TorrentModel(QObject *parent)
|
|
|
|
: QAbstractListModel(parent)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
void TorrentModel::populate()
|
|
|
|
{
|
|
|
|
// Load the torrents
|
|
|
|
foreach (BitTorrent::TorrentHandle *const torrent, BitTorrent::Session::instance()->torrents())
|
|
|
|
addTorrent(torrent);
|
|
|
|
|
|
|
|
// Listen for torrent changes
|
|
|
|
connect(BitTorrent::Session::instance(), SIGNAL(torrentAdded(BitTorrent::TorrentHandle *const)), SLOT(addTorrent(BitTorrent::TorrentHandle *const)));
|
|
|
|
connect(BitTorrent::Session::instance(), SIGNAL(torrentAboutToBeRemoved(BitTorrent::TorrentHandle *const)), SLOT(handleTorrentAboutToBeRemoved(BitTorrent::TorrentHandle *const)));
|
|
|
|
connect(BitTorrent::Session::instance(), SIGNAL(torrentStatusUpdated(BitTorrent::TorrentHandle *const)), this, SLOT(handleTorrentStatusUpdated(BitTorrent::TorrentHandle *const)));
|
|
|
|
|
|
|
|
connect(BitTorrent::Session::instance(), SIGNAL(torrentFinished(BitTorrent::TorrentHandle *const)), SLOT(handleTorrentStatusUpdated(BitTorrent::TorrentHandle *const)));
|
|
|
|
connect(BitTorrent::Session::instance(), SIGNAL(torrentMetadataLoaded(BitTorrent::TorrentHandle *const)), SLOT(handleTorrentStatusUpdated(BitTorrent::TorrentHandle *const)));
|
|
|
|
connect(BitTorrent::Session::instance(), SIGNAL(torrentResumed(BitTorrent::TorrentHandle *const)), SLOT(handleTorrentStatusUpdated(BitTorrent::TorrentHandle *const)));
|
|
|
|
connect(BitTorrent::Session::instance(), SIGNAL(torrentPaused(BitTorrent::TorrentHandle *const)), SLOT(handleTorrentStatusUpdated(BitTorrent::TorrentHandle *const)));
|
|
|
|
connect(BitTorrent::Session::instance(), SIGNAL(torrentFinishedChecking(BitTorrent::TorrentHandle *const)), SLOT(handleTorrentStatusUpdated(BitTorrent::TorrentHandle *const)));
|
|
|
|
}
|
|
|
|
|
|
|
|
TorrentModel::~TorrentModel() {
|
|
|
|
qDebug() << Q_FUNC_INFO << "ENTER";
|
|
|
|
qDeleteAll(m_items);
|
|
|
|
m_items.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_PRIORITY: return "#";
|
|
|
|
case TorrentModelItem::TR_NAME: return tr("Name", "i.e: torrent name");
|
|
|
|
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();
|
|
|
|
|
|
|
|
if (index.row() >= 0 && index.row() < rowCount() && index.column() >= 0 && index.column() < columnCount())
|
|
|
|
return m_items[index.row()]->data(index.column(), role);
|
|
|
|
|
|
|
|
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");
|
|
|
|
if ((index.row() >= 0) && (index.row() < rowCount()) && (index.column() >= 0) && (index.column() < columnCount())) {
|
|
|
|
bool change = m_items[index.row()]->setData(index.column(), value, role);
|
|
|
|
if (change)
|
|
|
|
notifyTorrentChanged(index.row());
|
|
|
|
return change;
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
int TorrentModel::torrentRow(const BitTorrent::InfoHash &hash) const
|
|
|
|
{
|
|
|
|
int row = 0;
|
|
|
|
|
|
|
|
foreach (TorrentModelItem *const item, m_items) {
|
|
|
|
if (item->hash() == hash) return row;
|
|
|
|
++row;
|
|
|
|
}
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
void TorrentModel::addTorrent(BitTorrent::TorrentHandle *const torrent)
|
|
|
|
{
|
|
|
|
if (torrentRow(torrent->hash()) < 0) {
|
|
|
|
beginInsertTorrent(m_items.size());
|
|
|
|
TorrentModelItem *item = new TorrentModelItem(torrent);
|
|
|
|
connect(item, SIGNAL(labelChanged(QString, QString)), SLOT(handleTorrentLabelChange(QString, QString)));
|
|
|
|
m_items << 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::notifyTorrentChanged(int row)
|
|
|
|
{
|
|
|
|
emit dataChanged(index(row, 0), index(row, columnCount() - 1));
|
|
|
|
}
|
|
|
|
|
|
|
|
Qt::ItemFlags TorrentModel::flags(const QModelIndex &index) const
|
|
|
|
{
|
|
|
|
if (!index.isValid())
|
|
|
|
return 0;
|
|
|
|
// Explicitly 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_items.at(row)->hash();
|
|
|
|
return QString();
|
|
|
|
}
|
|
|
|
|
|
|
|
BitTorrent::TorrentHandle *TorrentModel::torrentHandle(const QModelIndex &index) const
|
|
|
|
{
|
|
|
|
if (index.isValid() && (index.row() >= 0) && (index.row() < rowCount()))
|
|
|
|
return m_items[index.row()]->torrentHandle();
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
void TorrentModel::handleTorrentAboutToBeRemoved(BitTorrent::TorrentHandle *const torrent)
|
|
|
|
{
|
|
|
|
const int row = torrentRow(torrent->hash());
|
|
|
|
qDebug() << Q_FUNC_INFO << row;
|
|
|
|
if (row >= 0) {
|
|
|
|
emit torrentAboutToBeRemoved(m_items.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_items.takeAt(row);
|
|
|
|
endRemoveTorrent();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void TorrentModel::handleTorrentStatusUpdated(BitTorrent::TorrentHandle *const torrent)
|
|
|
|
{
|
|
|
|
const int row = torrentRow(torrent->hash());
|
|
|
|
if (row >= 0)
|
|
|
|
notifyTorrentChanged(row);
|
|
|
|
}
|