qBittorrent/src/qtlibtorrent/torrentmodel.cpp

452 lines
15 KiB
C++
Raw Normal View History

2010-11-14 22:32:29 +03:00
/*
* 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 "torrentmodel.h"
#include "torrentpersistentdata.h"
#include "qbtsession.h"
using namespace libtorrent;
TorrentModelItem::TorrentModelItem(const QTorrentHandle &h)
{
m_torrent = h;
2010-11-14 21:50:32 +03:00
m_hash = h.hash();
m_name = TorrentPersistentData::getName(h.hash());
if(m_name.isEmpty()) m_name = h.name();
m_addedTime = TorrentPersistentData::getAddedDate(h.hash());
m_seedTime = TorrentPersistentData::getSeedDate(h.hash());
m_label = TorrentPersistentData::getLabel(h.hash());
}
TorrentModelItem::State TorrentModelItem::state() const
{
try {
// Pause or Queued
if(m_torrent.is_paused()) {
m_icon = QIcon(":/Icons/skin/paused.png");
m_fgColor = QColor("red");
return m_torrent.is_seed() ? STATE_PAUSED_UP : STATE_PAUSED_DL;
}
if(m_torrent.is_queued()) {
m_icon = QIcon(":/Icons/skin/queued.png");
m_fgColor = QColor("grey");
return m_torrent.is_seed() ? STATE_QUEUED_UP : STATE_QUEUED_DL;
}
// Other states
switch(m_torrent.state()) {
case torrent_status::allocating:
case torrent_status::downloading_metadata:
case torrent_status::downloading: {
if(m_torrent.download_payload_rate() > 0) {
m_icon = QIcon(":/Icons/skin/downloading.png");
m_fgColor = QColor("green");
return STATE_DOWNLOADING;
} else {
m_icon = QIcon(":/Icons/skin/stalledDL.png");
m_fgColor = QColor("grey");
return STATE_STALLED_DL;
}
}
case torrent_status::finished:
case torrent_status::seeding:
if(m_torrent.upload_payload_rate() > 0) {
m_icon = QIcon(":/Icons/skin/uploading.png");
m_fgColor = QColor("orange");
return STATE_SEEDING;
} else {
m_icon = QIcon(":/Icons/skin/stalledUP.png");
m_fgColor = QColor("grey");
return STATE_STALLED_UP;
}
case torrent_status::queued_for_checking:
case torrent_status::checking_resume_data:
case torrent_status::checking_files:
m_icon = QIcon(":/Icons/skin/checking.png");
m_fgColor = QColor("grey");
return m_torrent.is_seed() ? STATE_CHECKING_UP : STATE_CHECKING_DL;
default:
m_icon = QIcon(":/Icons/skin/error.png");
m_fgColor = QColor("red");
return STATE_INVALID;
}
} catch(invalid_handle&) {
m_icon = QIcon(":/Icons/skin/error.png");
m_fgColor = QColor("red");
return STATE_INVALID;
}
}
bool TorrentModelItem::setData(int column, const QVariant &value, int role)
{
qDebug() << Q_FUNC_INFO << column << value;
if(role != Qt::DisplayRole) return false;
// Label and Name columns can be edited
switch(column) {
case TR_NAME:
m_name = value.toString();
TorrentPersistentData::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::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 m_icon;
}
if(role == Qt::ForegroundRole) {
return m_fgColor;
}
2010-11-14 22:07:24 +03:00
if(role != Qt::DisplayRole && role != Qt::UserRole) return QVariant();
switch(column) {
case TR_NAME:
return m_name;
case TR_PRIORITY:
return m_torrent.queue_position();
case TR_SIZE:
return m_torrent.has_metadata() ? static_cast<qlonglong>(m_torrent.actual_size()) : -1;
case TR_PROGRESS:
return m_torrent.progress();
case TR_STATUS:
return state();
case TR_SEEDS: {
2010-11-14 22:07:24 +03:00
return (role == Qt::DisplayRole) ? m_torrent.num_seeds() : m_torrent.num_complete();
}
case TR_PEERS: {
2010-11-14 22:07:24 +03:00
return (role == Qt::DisplayRole) ? (m_torrent.num_peers()-m_torrent.num_seeds()) : m_torrent.num_incomplete();
}
case TR_DLSPEED:
return m_torrent.download_payload_rate();
case TR_UPSPEED:
return m_torrent.upload_payload_rate();
case TR_ETA: {
// XXX: Is this correct?
if(m_torrent.is_seed() || m_torrent.is_paused() || m_torrent.is_queued()) return MAX_ETA;
return QBtSession::instance()->getETA(m_torrent.hash());
}
case TR_RATIO:
return QBtSession::instance()->getRealRatio(m_torrent.hash());
case TR_LABEL:
return m_label;
case TR_ADD_DATE:
return m_addedTime;
case TR_SEED_DATE:
return m_seedTime;
case TR_TRACKER:
return m_torrent.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_torrent.total_wanted_done());
case TR_AMOUNT_LEFT:
return static_cast<qlonglong>(m_torrent.total_wanted() - m_torrent.total_wanted_done());
default:
return QVariant();
}
}
// 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;
for(it = torrents.begin(); it != torrents.end(); it++) {
addTorrent(QTorrentHandle(*it));
}
// 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(deletedTorrent(QString)), SLOT(removeTorrent(QString)));
2010-11-24 23:31:14 +03:00
connect(QBtSession::instance(), SIGNAL(finishedTorrent(QTorrentHandle)), SLOT(handleTorrentUpdate(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)));
}
TorrentModel::~TorrentModel() {
qDeleteAll(m_torrents);
m_torrents.clear();
}
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("Amount downloaded", "Amount of data downloaded (e.g. in MB)");
case TorrentModelItem::TR_AMOUNT_LEFT: return tr("Amount left", "Amount of data left to download (e.g. in MB)");
default:
return QVariant();
}
}
if(role == Qt::TextAlignmentRole) {
switch(section) {
case TorrentModelItem::TR_PRIORITY:
case TorrentModelItem::TR_SIZE:
case TorrentModelItem::TR_SEEDS:
case TorrentModelItem::TR_PEERS:
case TorrentModelItem::TR_DLSPEED:
case TorrentModelItem::TR_UPSPEED:
case TorrentModelItem::TR_RATIO:
case TorrentModelItem::TR_DLLIMIT:
case TorrentModelItem::TR_UPLIMIT:
case TorrentModelItem::TR_AMOUNT_DOWNLOADED:
case TorrentModelItem::TR_AMOUNT_LEFT:
return Qt::AlignRight;
case TorrentModelItem::TR_PROGRESS:
return Qt::AlignHCenter;
default:
return Qt::AlignLeft;
}
}
}
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
{
QList<TorrentModelItem*>::const_iterator it;
2010-11-14 21:50:32 +03:00
int row = 0;
for(it = m_torrents.constBegin(); it != m_torrents.constEnd(); 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::removeTorrent(const QString &hash)
{
const int row = torrentRow(hash);
qDebug() << Q_FUNC_INFO << hash << row;
2010-11-14 22:11:52 +03:00
if(row >= 0) {
beginRemoveTorrent(row);
m_torrents.removeAt(row);
endRemoveTorrent();
}
}
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();
}
2010-11-24 23:31:14 +03:00
void TorrentModel::handleTorrentUpdate(const QTorrentHandle &h)
{
const int row = torrentRow(h.hash());
if(row >= 0) {
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()
{
emit dataChanged(index(0, 0), index(rowCount()-1, columnCount()-1));
}
TorrentStatusReport TorrentModel::getTorrentStatusReport() const
{
TorrentStatusReport report;
QList<TorrentModelItem*>::const_iterator it;
for(it = m_torrents.constBegin(); it != m_torrents.constEnd(); it++) {
switch((*it)->data(TorrentModelItem::TR_STATUS).toInt()) {
case TorrentModelItem::STATE_DOWNLOADING:
++report.nb_active;
++report.nb_downloading;
break;
case TorrentModelItem::STATE_PAUSED_DL:
++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:
++report.nb_active;
++report.nb_seeding;
break;
case TorrentModelItem::STATE_PAUSED_UP:
++report.nb_paused;
case TorrentModelItem::STATE_STALLED_UP:
case TorrentModelItem::STATE_CHECKING_UP:
case TorrentModelItem::STATE_QUEUED_UP: {
++report.nb_seeding;
++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());
if(row >= 0) {
emit torrentAboutToBeRemoved(m_torrents.at(row));
}
}