mirror of
https://github.com/d47081/qBittorrent.git
synced 2025-02-02 09:55:55 +00:00
Merge pull request #4109 from buinsky/master
WebUI: Implement peers tab
This commit is contained in:
commit
75d4e2a2f2
@ -29,6 +29,7 @@
|
|||||||
#include "base/net/geoipmanager.h"
|
#include "base/net/geoipmanager.h"
|
||||||
#include "base/utils/string.h"
|
#include "base/utils/string.h"
|
||||||
#include "base/unicodestrings.h"
|
#include "base/unicodestrings.h"
|
||||||
|
#include "base/bittorrent/torrenthandle.h"
|
||||||
#include "peerinfo.h"
|
#include "peerinfo.h"
|
||||||
|
|
||||||
namespace libt = libtorrent;
|
namespace libt = libtorrent;
|
||||||
@ -49,9 +50,11 @@ PeerAddress::PeerAddress(QHostAddress ip, ushort port)
|
|||||||
|
|
||||||
// PeerInfo
|
// PeerInfo
|
||||||
|
|
||||||
PeerInfo::PeerInfo(const libt::peer_info &nativeInfo)
|
PeerInfo::PeerInfo(const TorrentHandle *torrent, const libt::peer_info &nativeInfo)
|
||||||
: m_nativeInfo(nativeInfo)
|
: m_nativeInfo(nativeInfo)
|
||||||
{
|
{
|
||||||
|
calcRelevance(torrent);
|
||||||
|
determineFlags();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool PeerInfo::fromDHT() const
|
bool PeerInfo::fromDHT() const
|
||||||
@ -253,3 +256,155 @@ QString PeerInfo::connectionType() const
|
|||||||
|
|
||||||
return connection;
|
return connection;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void PeerInfo::calcRelevance(const TorrentHandle *torrent)
|
||||||
|
{
|
||||||
|
const QBitArray &allPieces = torrent->pieces();
|
||||||
|
const QBitArray &peerPieces = pieces();
|
||||||
|
|
||||||
|
int localMissing = 0;
|
||||||
|
int remoteHaves = 0;
|
||||||
|
|
||||||
|
for (int i = 0; i < allPieces.size(); ++i) {
|
||||||
|
if (!allPieces[i]) {
|
||||||
|
++localMissing;
|
||||||
|
if (peerPieces[i])
|
||||||
|
++remoteHaves;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (localMissing == 0)
|
||||||
|
m_relevance = 0.0;
|
||||||
|
else
|
||||||
|
m_relevance = static_cast<qreal>(remoteHaves) / localMissing;
|
||||||
|
}
|
||||||
|
|
||||||
|
qreal PeerInfo::relevance() const
|
||||||
|
{
|
||||||
|
return m_relevance;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PeerInfo::determineFlags()
|
||||||
|
{
|
||||||
|
if (isInteresting()) {
|
||||||
|
//d = Your client wants to download, but peer doesn't want to send (interested and choked)
|
||||||
|
if (isRemoteChocked()) {
|
||||||
|
m_flags += "d ";
|
||||||
|
m_flagsDescription += tr("interested(local) and choked(peer)");
|
||||||
|
m_flagsDescription += ", ";
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
//D = Currently downloading (interested and not choked)
|
||||||
|
m_flags += "D ";
|
||||||
|
m_flagsDescription += tr("interested(local) and unchoked(peer)");
|
||||||
|
m_flagsDescription += ", ";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isRemoteInterested()) {
|
||||||
|
//u = Peer wants your client to upload, but your client doesn't want to (interested and choked)
|
||||||
|
if (isChocked()) {
|
||||||
|
m_flags += "u ";
|
||||||
|
m_flagsDescription += tr("interested(peer) and choked(local)");
|
||||||
|
m_flagsDescription += ", ";
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
//U = Currently uploading (interested and not choked)
|
||||||
|
m_flags += "U ";
|
||||||
|
m_flagsDescription += tr("interested(peer) and unchoked(local)");
|
||||||
|
m_flagsDescription += ", ";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//O = Optimistic unchoke
|
||||||
|
if (optimisticUnchoke()) {
|
||||||
|
m_flags += "O ";
|
||||||
|
m_flagsDescription += tr("optimistic unchoke");
|
||||||
|
m_flagsDescription += ", ";
|
||||||
|
}
|
||||||
|
|
||||||
|
//S = Peer is snubbed
|
||||||
|
if (isSnubbed()) {
|
||||||
|
m_flags += "S ";
|
||||||
|
m_flagsDescription += tr("peer snubbed");
|
||||||
|
m_flagsDescription += ", ";
|
||||||
|
}
|
||||||
|
|
||||||
|
//I = Peer is an incoming connection
|
||||||
|
if (!isLocalConnection()) {
|
||||||
|
m_flags += "I ";
|
||||||
|
m_flagsDescription += tr("incoming connection");
|
||||||
|
m_flagsDescription += ", ";
|
||||||
|
}
|
||||||
|
|
||||||
|
//K = Peer is unchoking your client, but your client is not interested
|
||||||
|
if (!isRemoteChocked() && !isInteresting()) {
|
||||||
|
m_flags += "K ";
|
||||||
|
m_flagsDescription += tr("not interested(local) and unchoked(peer)");
|
||||||
|
m_flagsDescription += ", ";
|
||||||
|
}
|
||||||
|
|
||||||
|
//? = Your client unchoked the peer but the peer is not interested
|
||||||
|
if (!isChocked() && !isRemoteInterested()) {
|
||||||
|
m_flags += "? ";
|
||||||
|
m_flagsDescription += tr("not interested(peer) and unchoked(local)");
|
||||||
|
m_flagsDescription += ", ";
|
||||||
|
}
|
||||||
|
|
||||||
|
//X = Peer was included in peerlists obtained through Peer Exchange (PEX)
|
||||||
|
if (fromPeX()) {
|
||||||
|
m_flags += "X ";
|
||||||
|
m_flagsDescription += tr("peer from PEX");
|
||||||
|
m_flagsDescription += ", ";
|
||||||
|
}
|
||||||
|
|
||||||
|
//H = Peer was obtained through DHT
|
||||||
|
if (fromDHT()) {
|
||||||
|
m_flags += "H ";
|
||||||
|
m_flagsDescription += tr("peer from DHT");
|
||||||
|
m_flagsDescription += ", ";
|
||||||
|
}
|
||||||
|
|
||||||
|
//E = Peer is using Protocol Encryption (all traffic)
|
||||||
|
if (isRC4Encrypted()) {
|
||||||
|
m_flags += "E ";
|
||||||
|
m_flagsDescription += tr("encrypted traffic");
|
||||||
|
m_flagsDescription += ", ";
|
||||||
|
}
|
||||||
|
|
||||||
|
//e = Peer is using Protocol Encryption (handshake)
|
||||||
|
if (isPlaintextEncrypted()) {
|
||||||
|
m_flags += "e ";
|
||||||
|
m_flagsDescription += tr("encrypted handshake");
|
||||||
|
m_flagsDescription += ", ";
|
||||||
|
}
|
||||||
|
|
||||||
|
//P = Peer is using uTorrent uTP
|
||||||
|
|
||||||
|
if (useUTPSocket()) {
|
||||||
|
m_flags += "P ";
|
||||||
|
m_flagsDescription += QString::fromUtf8(C_UTP);
|
||||||
|
m_flagsDescription += ", ";
|
||||||
|
}
|
||||||
|
|
||||||
|
//L = Peer is local
|
||||||
|
if (fromLSD()) {
|
||||||
|
m_flags += "L";
|
||||||
|
m_flagsDescription += tr("peer from LSD");
|
||||||
|
}
|
||||||
|
|
||||||
|
m_flags = m_flags.trimmed();
|
||||||
|
m_flagsDescription = m_flagsDescription.trimmed();
|
||||||
|
if (m_flagsDescription.endsWith(',', Qt::CaseInsensitive))
|
||||||
|
m_flagsDescription.chop(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
QString PeerInfo::flags() const
|
||||||
|
{
|
||||||
|
return m_flags;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString PeerInfo::flagsDescription() const
|
||||||
|
{
|
||||||
|
return m_flagsDescription;
|
||||||
|
}
|
||||||
|
@ -33,9 +33,12 @@
|
|||||||
|
|
||||||
#include <QHostAddress>
|
#include <QHostAddress>
|
||||||
#include <QBitArray>
|
#include <QBitArray>
|
||||||
|
#include <QCoreApplication>
|
||||||
|
|
||||||
namespace BitTorrent
|
namespace BitTorrent
|
||||||
{
|
{
|
||||||
|
class TorrentHandle;
|
||||||
|
|
||||||
struct PeerAddress
|
struct PeerAddress
|
||||||
{
|
{
|
||||||
QHostAddress ip;
|
QHostAddress ip;
|
||||||
@ -47,8 +50,10 @@ namespace BitTorrent
|
|||||||
|
|
||||||
class PeerInfo
|
class PeerInfo
|
||||||
{
|
{
|
||||||
|
Q_DECLARE_TR_FUNCTIONS(PeerInfo)
|
||||||
|
|
||||||
public:
|
public:
|
||||||
PeerInfo(const libtorrent::peer_info &nativeInfo);
|
PeerInfo(const TorrentHandle *torrent, const libtorrent::peer_info &nativeInfo);
|
||||||
|
|
||||||
bool fromDHT() const;
|
bool fromDHT() const;
|
||||||
bool fromPeX() const;
|
bool fromPeX() const;
|
||||||
@ -89,12 +94,21 @@ namespace BitTorrent
|
|||||||
qlonglong totalDownload() const;
|
qlonglong totalDownload() const;
|
||||||
QBitArray pieces() const;
|
QBitArray pieces() const;
|
||||||
QString connectionType() const;
|
QString connectionType() const;
|
||||||
|
qreal relevance() const;
|
||||||
|
QString flags() const;
|
||||||
|
QString flagsDescription() const;
|
||||||
#ifndef DISABLE_COUNTRIES_RESOLUTION
|
#ifndef DISABLE_COUNTRIES_RESOLUTION
|
||||||
QString country() const;
|
QString country() const;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
void calcRelevance(const TorrentHandle *torrent);
|
||||||
|
void determineFlags();
|
||||||
|
|
||||||
libtorrent::peer_info m_nativeInfo;
|
libtorrent::peer_info m_nativeInfo;
|
||||||
|
qreal m_relevance;
|
||||||
|
QString m_flags;
|
||||||
|
QString m_flagsDescription;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -960,7 +960,7 @@ QList<PeerInfo> TorrentHandle::peers() const
|
|||||||
SAFE_CALL(get_peer_info, nativePeers);
|
SAFE_CALL(get_peer_info, nativePeers);
|
||||||
|
|
||||||
foreach (const libt::peer_info &peer, nativePeers)
|
foreach (const libt::peer_info &peer, nativePeers)
|
||||||
peers << peer;
|
peers << PeerInfo(this, peer);
|
||||||
|
|
||||||
return peers;
|
return peers;
|
||||||
}
|
}
|
||||||
|
@ -51,464 +51,338 @@
|
|||||||
#include "peerlistsortmodel.h"
|
#include "peerlistsortmodel.h"
|
||||||
#include "peerlistwidget.h"
|
#include "peerlistwidget.h"
|
||||||
|
|
||||||
PeerListWidget::PeerListWidget(PropertiesWidget *parent):
|
PeerListWidget::PeerListWidget(PropertiesWidget *parent)
|
||||||
QTreeView(parent), m_properties(parent), m_displayFlags(false)
|
: QTreeView(parent)
|
||||||
|
, m_properties(parent)
|
||||||
|
, m_displayFlags(false)
|
||||||
{
|
{
|
||||||
// Load settings
|
// Load settings
|
||||||
loadSettings();
|
loadSettings();
|
||||||
// Visual settings
|
// Visual settings
|
||||||
setUniformRowHeights(true);
|
setUniformRowHeights(true);
|
||||||
setRootIsDecorated(false);
|
setRootIsDecorated(false);
|
||||||
setItemsExpandable(false);
|
setItemsExpandable(false);
|
||||||
setAllColumnsShowFocus(true);
|
setAllColumnsShowFocus(true);
|
||||||
setSelectionMode(QAbstractItemView::ExtendedSelection);
|
setSelectionMode(QAbstractItemView::ExtendedSelection);
|
||||||
// List Model
|
// List Model
|
||||||
m_listModel = new QStandardItemModel(0, PeerListDelegate::COL_COUNT);
|
m_listModel = new QStandardItemModel(0, PeerListDelegate::COL_COUNT);
|
||||||
m_listModel->setHeaderData(PeerListDelegate::COUNTRY, Qt::Horizontal, QVariant()); // Country flag column
|
m_listModel->setHeaderData(PeerListDelegate::COUNTRY, Qt::Horizontal, QVariant()); // Country flag column
|
||||||
m_listModel->setHeaderData(PeerListDelegate::IP, Qt::Horizontal, tr("IP"));
|
m_listModel->setHeaderData(PeerListDelegate::IP, Qt::Horizontal, tr("IP"));
|
||||||
m_listModel->setHeaderData(PeerListDelegate::PORT, Qt::Horizontal, tr("Port"));
|
m_listModel->setHeaderData(PeerListDelegate::PORT, Qt::Horizontal, tr("Port"));
|
||||||
m_listModel->setHeaderData(PeerListDelegate::FLAGS, Qt::Horizontal, tr("Flags"));
|
m_listModel->setHeaderData(PeerListDelegate::FLAGS, Qt::Horizontal, tr("Flags"));
|
||||||
m_listModel->setHeaderData(PeerListDelegate::CONNECTION, Qt::Horizontal, tr("Connection"));
|
m_listModel->setHeaderData(PeerListDelegate::CONNECTION, Qt::Horizontal, tr("Connection"));
|
||||||
m_listModel->setHeaderData(PeerListDelegate::CLIENT, Qt::Horizontal, tr("Client", "i.e.: Client application"));
|
m_listModel->setHeaderData(PeerListDelegate::CLIENT, Qt::Horizontal, tr("Client", "i.e.: Client application"));
|
||||||
m_listModel->setHeaderData(PeerListDelegate::PROGRESS, Qt::Horizontal, tr("Progress", "i.e: % downloaded"));
|
m_listModel->setHeaderData(PeerListDelegate::PROGRESS, Qt::Horizontal, tr("Progress", "i.e: % downloaded"));
|
||||||
m_listModel->setHeaderData(PeerListDelegate::DOWN_SPEED, Qt::Horizontal, tr("Down Speed", "i.e: Download speed"));
|
m_listModel->setHeaderData(PeerListDelegate::DOWN_SPEED, Qt::Horizontal, tr("Down Speed", "i.e: Download speed"));
|
||||||
m_listModel->setHeaderData(PeerListDelegate::UP_SPEED, Qt::Horizontal, tr("Up Speed", "i.e: Upload speed"));
|
m_listModel->setHeaderData(PeerListDelegate::UP_SPEED, Qt::Horizontal, tr("Up Speed", "i.e: Upload speed"));
|
||||||
m_listModel->setHeaderData(PeerListDelegate::TOT_DOWN, Qt::Horizontal, tr("Downloaded", "i.e: total data downloaded"));
|
m_listModel->setHeaderData(PeerListDelegate::TOT_DOWN, Qt::Horizontal, tr("Downloaded", "i.e: total data downloaded"));
|
||||||
m_listModel->setHeaderData(PeerListDelegate::TOT_UP, Qt::Horizontal, tr("Uploaded", "i.e: total data uploaded"));
|
m_listModel->setHeaderData(PeerListDelegate::TOT_UP, Qt::Horizontal, tr("Uploaded", "i.e: total data uploaded"));
|
||||||
m_listModel->setHeaderData(PeerListDelegate::RELEVANCE, Qt::Horizontal, tr("Relevance", "i.e: How relevant this peer is to us. How many pieces it has that we don't."));
|
m_listModel->setHeaderData(PeerListDelegate::RELEVANCE, Qt::Horizontal, tr("Relevance", "i.e: How relevant this peer is to us. How many pieces it has that we don't."));
|
||||||
// Proxy model to support sorting without actually altering the underlying model
|
// Proxy model to support sorting without actually altering the underlying model
|
||||||
m_proxyModel = new PeerListSortModel();
|
m_proxyModel = new PeerListSortModel();
|
||||||
m_proxyModel->setDynamicSortFilter(true);
|
m_proxyModel->setDynamicSortFilter(true);
|
||||||
m_proxyModel->setSourceModel(m_listModel);
|
m_proxyModel->setSourceModel(m_listModel);
|
||||||
m_proxyModel->setSortCaseSensitivity(Qt::CaseInsensitive);
|
m_proxyModel->setSortCaseSensitivity(Qt::CaseInsensitive);
|
||||||
setModel(m_proxyModel);
|
setModel(m_proxyModel);
|
||||||
//Explicitly set the column visibility. When columns are added/removed
|
//Explicitly set the column visibility. When columns are added/removed
|
||||||
//between versions this prevents some of them being hidden due to
|
//between versions this prevents some of them being hidden due to
|
||||||
//incorrect restoreState() being used.
|
//incorrect restoreState() being used.
|
||||||
for (unsigned int i=0; i<PeerListDelegate::IP_HIDDEN; i++)
|
for (unsigned int i = 0; i < PeerListDelegate::IP_HIDDEN; i++)
|
||||||
showColumn(i);
|
showColumn(i);
|
||||||
hideColumn(PeerListDelegate::IP_HIDDEN);
|
hideColumn(PeerListDelegate::IP_HIDDEN);
|
||||||
hideColumn(PeerListDelegate::COL_COUNT);
|
hideColumn(PeerListDelegate::COL_COUNT);
|
||||||
if (!Preferences::instance()->resolvePeerCountries())
|
if (!Preferences::instance()->resolvePeerCountries())
|
||||||
hideColumn(PeerListDelegate::COUNTRY);
|
hideColumn(PeerListDelegate::COUNTRY);
|
||||||
//To also mitigate the above issue, we have to resize each column when
|
//To also mitigate the above issue, we have to resize each column when
|
||||||
//its size is 0, because explicitly 'showing' the column isn't enough
|
//its size is 0, because explicitly 'showing' the column isn't enough
|
||||||
//in the above scenario.
|
//in the above scenario.
|
||||||
for (unsigned int i=0; i<PeerListDelegate::IP_HIDDEN; i++)
|
for (unsigned int i = 0; i < PeerListDelegate::IP_HIDDEN; i++)
|
||||||
if (!columnWidth(i))
|
if (!columnWidth(i))
|
||||||
resizeColumnToContents(i);
|
resizeColumnToContents(i);
|
||||||
// Context menu
|
// Context menu
|
||||||
setContextMenuPolicy(Qt::CustomContextMenu);
|
setContextMenuPolicy(Qt::CustomContextMenu);
|
||||||
connect(this, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(showPeerListMenu(QPoint)));
|
connect(this, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(showPeerListMenu(QPoint)));
|
||||||
// List delegate
|
// List delegate
|
||||||
m_listDelegate = new PeerListDelegate(this);
|
m_listDelegate = new PeerListDelegate(this);
|
||||||
setItemDelegate(m_listDelegate);
|
setItemDelegate(m_listDelegate);
|
||||||
// Enable sorting
|
// Enable sorting
|
||||||
setSortingEnabled(true);
|
setSortingEnabled(true);
|
||||||
// IP to Hostname resolver
|
// IP to Hostname resolver
|
||||||
updatePeerHostNameResolutionState();
|
updatePeerHostNameResolutionState();
|
||||||
// SIGNAL/SLOT
|
// SIGNAL/SLOT
|
||||||
connect(header(), SIGNAL(sectionClicked(int)), SLOT(handleSortColumnChanged(int)));
|
connect(header(), SIGNAL(sectionClicked(int)), SLOT(handleSortColumnChanged(int)));
|
||||||
handleSortColumnChanged(header()->sortIndicatorSection());
|
handleSortColumnChanged(header()->sortIndicatorSection());
|
||||||
copyHotkey = new QShortcut(QKeySequence(Qt::ControlModifier + Qt::Key_C), this, SLOT(copySelectedPeers()), 0, Qt::WidgetShortcut);
|
m_copyHotkey = new QShortcut(QKeySequence(Qt::ControlModifier + Qt::Key_C), this, SLOT(copySelectedPeers()), 0, Qt::WidgetShortcut);
|
||||||
}
|
}
|
||||||
|
|
||||||
PeerListWidget::~PeerListWidget()
|
PeerListWidget::~PeerListWidget()
|
||||||
{
|
{
|
||||||
saveSettings();
|
saveSettings();
|
||||||
delete m_proxyModel;
|
delete m_proxyModel;
|
||||||
delete m_listModel;
|
delete m_listModel;
|
||||||
delete m_listDelegate;
|
delete m_listDelegate;
|
||||||
if (m_resolver)
|
if (m_resolver)
|
||||||
delete m_resolver;
|
delete m_resolver;
|
||||||
delete copyHotkey;
|
delete m_copyHotkey;
|
||||||
}
|
}
|
||||||
|
|
||||||
void PeerListWidget::updatePeerHostNameResolutionState()
|
void PeerListWidget::updatePeerHostNameResolutionState()
|
||||||
{
|
{
|
||||||
if (Preferences::instance()->resolvePeerHostNames()) {
|
if (Preferences::instance()->resolvePeerHostNames()) {
|
||||||
if (!m_resolver) {
|
if (!m_resolver) {
|
||||||
m_resolver = new Net::ReverseResolution(this);
|
m_resolver = new Net::ReverseResolution(this);
|
||||||
connect(m_resolver, SIGNAL(ipResolved(QString,QString)), SLOT(handleResolved(QString,QString)));
|
connect(m_resolver, SIGNAL(ipResolved(QString, QString)), SLOT(handleResolved(QString, QString)));
|
||||||
loadPeers(m_properties->getCurrentTorrent(), true);
|
loadPeers(m_properties->getCurrentTorrent(), true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (m_resolver) {
|
||||||
|
delete m_resolver;
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
if (m_resolver)
|
|
||||||
delete m_resolver;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void PeerListWidget::updatePeerCountryResolutionState()
|
void PeerListWidget::updatePeerCountryResolutionState()
|
||||||
{
|
{
|
||||||
if (Preferences::instance()->resolvePeerCountries() != m_displayFlags) {
|
if (Preferences::instance()->resolvePeerCountries() != m_displayFlags) {
|
||||||
m_displayFlags = !m_displayFlags;
|
m_displayFlags = !m_displayFlags;
|
||||||
if (m_displayFlags) {
|
if (m_displayFlags) {
|
||||||
loadPeers(m_properties->getCurrentTorrent());
|
loadPeers(m_properties->getCurrentTorrent());
|
||||||
showColumn(PeerListDelegate::COUNTRY);
|
showColumn(PeerListDelegate::COUNTRY);
|
||||||
resizeColumnToContents(PeerListDelegate::COUNTRY);
|
resizeColumnToContents(PeerListDelegate::COUNTRY);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
hideColumn(PeerListDelegate::COUNTRY);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else {
|
|
||||||
hideColumn(PeerListDelegate::COUNTRY);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void PeerListWidget::showPeerListMenu(const QPoint&)
|
void PeerListWidget::showPeerListMenu(const QPoint&)
|
||||||
{
|
{
|
||||||
QMenu menu;
|
QMenu menu;
|
||||||
bool empty_menu = true;
|
bool emptyMenu = true;
|
||||||
BitTorrent::TorrentHandle *const torrent = m_properties->getCurrentTorrent();
|
BitTorrent::TorrentHandle *const torrent = m_properties->getCurrentTorrent();
|
||||||
if (!torrent) return;
|
if (!torrent) return;
|
||||||
|
|
||||||
// Add Peer Action
|
// Add Peer Action
|
||||||
QAction *addPeerAct = 0;
|
QAction *addPeerAct = 0;
|
||||||
if (!torrent->isQueued() && !torrent->isChecking()) {
|
if (!torrent->isQueued() && !torrent->isChecking()) {
|
||||||
addPeerAct = menu.addAction(GuiIconProvider::instance()->getIcon("user-group-new"), tr("Add a new peer..."));
|
addPeerAct = menu.addAction(GuiIconProvider::instance()->getIcon("user-group-new"), tr("Add a new peer..."));
|
||||||
empty_menu = false;
|
emptyMenu = false;
|
||||||
}
|
}
|
||||||
QAction *banAct = 0;
|
QAction *banAct = 0;
|
||||||
QAction *copyPeerAct = 0;
|
QAction *copyPeerAct = 0;
|
||||||
if (!selectionModel()->selectedRows().isEmpty()) {
|
if (!selectionModel()->selectedRows().isEmpty()) {
|
||||||
copyPeerAct = menu.addAction(GuiIconProvider::instance()->getIcon("edit-copy"), tr("Copy selected"));
|
copyPeerAct = menu.addAction(GuiIconProvider::instance()->getIcon("edit-copy"), tr("Copy selected"));
|
||||||
menu.addSeparator();
|
menu.addSeparator();
|
||||||
banAct = menu.addAction(GuiIconProvider::instance()->getIcon("user-group-delete"), tr("Ban peer permanently"));
|
banAct = menu.addAction(GuiIconProvider::instance()->getIcon("user-group-delete"), tr("Ban peer permanently"));
|
||||||
empty_menu = false;
|
emptyMenu = false;
|
||||||
}
|
}
|
||||||
if (empty_menu) return;
|
if (emptyMenu) return;
|
||||||
QAction *act = menu.exec(QCursor::pos());
|
QAction *act = menu.exec(QCursor::pos());
|
||||||
if (act == 0) return;
|
if (act == 0) return;
|
||||||
if (act == addPeerAct) {
|
if (act == addPeerAct) {
|
||||||
QList<BitTorrent::PeerAddress> peersList = PeersAdditionDlg::askForPeers();
|
QList<BitTorrent::PeerAddress> peersList = PeersAdditionDlg::askForPeers();
|
||||||
int peerCount = 0;
|
int peerCount = 0;
|
||||||
foreach (const BitTorrent::PeerAddress &addr, peersList) {
|
foreach (const BitTorrent::PeerAddress &addr, peersList) {
|
||||||
if (torrent->connectPeer(addr)) {
|
if (torrent->connectPeer(addr)) {
|
||||||
qDebug("Adding peer %s...", qPrintable(addr.ip.toString()));
|
qDebug("Adding peer %s...", qPrintable(addr.ip.toString()));
|
||||||
Logger::instance()->addMessage(tr("Manually adding peer '%1'...").arg(addr.ip.toString()));
|
Logger::instance()->addMessage(tr("Manually adding peer '%1'...").arg(addr.ip.toString()));
|
||||||
peerCount++;
|
peerCount++;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
Logger::instance()->addMessage(tr("The peer '%1' could not be added to this torrent.").arg(addr.ip.toString()), Log::WARNING);
|
Logger::instance()->addMessage(tr("The peer '%1' could not be added to this torrent.").arg(addr.ip.toString()), Log::WARNING);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
if (peerCount < peersList.length())
|
||||||
|
QMessageBox::information(0, tr("Peer addition"), tr("Some peers could not be added. Check the Log for details."));
|
||||||
|
else if (peerCount > 0)
|
||||||
|
QMessageBox::information(0, tr("Peer addition"), tr("The peers were added to this torrent."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (act == banAct) {
|
||||||
|
banSelectedPeers();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (act == copyPeerAct) {
|
||||||
|
copySelectedPeers();
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
if (peerCount < peersList.length())
|
|
||||||
QMessageBox::information(0, tr("Peer addition"), tr("Some peers could not be added. Check the Log for details."));
|
|
||||||
else if (peerCount > 0)
|
|
||||||
QMessageBox::information(0, tr("Peer addition"), tr("The peers were added to this torrent."));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (act == banAct) {
|
|
||||||
banSelectedPeers();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (act == copyPeerAct) {
|
|
||||||
copySelectedPeers();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void PeerListWidget::banSelectedPeers()
|
void PeerListWidget::banSelectedPeers()
|
||||||
{
|
{
|
||||||
// Confirm first
|
// Confirm first
|
||||||
int ret = QMessageBox::question(this, tr("Ban peer permanently"), tr("Are you sure you want to ban permanently the selected peers?"),
|
int ret = QMessageBox::question(this, tr("Ban peer permanently"), tr("Are you sure you want to ban permanently the selected peers?"),
|
||||||
tr("&Yes"), tr("&No"),
|
tr("&Yes"), tr("&No"),
|
||||||
QString(), 0, 1);
|
QString(), 0, 1);
|
||||||
if (ret)
|
if (ret)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
QModelIndexList selectedIndexes = selectionModel()->selectedRows();
|
QModelIndexList selectedIndexes = selectionModel()->selectedRows();
|
||||||
foreach (const QModelIndex &index, selectedIndexes) {
|
foreach (const QModelIndex &index, selectedIndexes) {
|
||||||
int row = m_proxyModel->mapToSource(index).row();
|
int row = m_proxyModel->mapToSource(index).row();
|
||||||
QString ip = m_listModel->data(m_listModel->index(row, PeerListDelegate::IP_HIDDEN)).toString();
|
QString ip = m_listModel->data(m_listModel->index(row, PeerListDelegate::IP_HIDDEN)).toString();
|
||||||
qDebug("Banning peer %s...", ip.toLocal8Bit().data());
|
qDebug("Banning peer %s...", ip.toLocal8Bit().data());
|
||||||
Logger::instance()->addMessage(tr("Manually banning peer '%1'...").arg(ip));
|
Logger::instance()->addMessage(tr("Manually banning peer '%1'...").arg(ip));
|
||||||
BitTorrent::Session::instance()->banIP(ip);
|
BitTorrent::Session::instance()->banIP(ip);
|
||||||
}
|
}
|
||||||
// Refresh list
|
// Refresh list
|
||||||
loadPeers(m_properties->getCurrentTorrent());
|
loadPeers(m_properties->getCurrentTorrent());
|
||||||
}
|
}
|
||||||
|
|
||||||
void PeerListWidget::copySelectedPeers()
|
void PeerListWidget::copySelectedPeers()
|
||||||
{
|
{
|
||||||
QModelIndexList selectedIndexes = selectionModel()->selectedRows();
|
QModelIndexList selectedIndexes = selectionModel()->selectedRows();
|
||||||
QStringList selectedPeers;
|
QStringList selectedPeers;
|
||||||
foreach (const QModelIndex &index, selectedIndexes) {
|
foreach (const QModelIndex &index, selectedIndexes) {
|
||||||
int row = m_proxyModel->mapToSource(index).row();
|
int row = m_proxyModel->mapToSource(index).row();
|
||||||
QString ip = m_listModel->data(m_listModel->index(row, PeerListDelegate::IP_HIDDEN)).toString();
|
QString ip = m_listModel->data(m_listModel->index(row, PeerListDelegate::IP_HIDDEN)).toString();
|
||||||
QString myport = m_listModel->data(m_listModel->index(row, PeerListDelegate::PORT)).toString();
|
QString myport = m_listModel->data(m_listModel->index(row, PeerListDelegate::PORT)).toString();
|
||||||
if (ip.indexOf(".") == -1) // IPv6
|
if (ip.indexOf(".") == -1) // IPv6
|
||||||
selectedPeers << "[" + ip + "]:" + myport;
|
selectedPeers << "[" + ip + "]:" + myport;
|
||||||
else // IPv4
|
else // IPv4
|
||||||
selectedPeers << ip + ":" + myport;
|
selectedPeers << ip + ":" + myport;
|
||||||
}
|
|
||||||
QApplication::clipboard()->setText(selectedPeers.join("\n"));
|
|
||||||
}
|
|
||||||
|
|
||||||
void PeerListWidget::clear() {
|
|
||||||
qDebug("clearing peer list");
|
|
||||||
m_peerItems.clear();
|
|
||||||
m_peerAddresses.clear();
|
|
||||||
m_missingFlags.clear();
|
|
||||||
int nbrows = m_listModel->rowCount();
|
|
||||||
if (nbrows > 0) {
|
|
||||||
qDebug("Cleared %d peers", nbrows);
|
|
||||||
m_listModel->removeRows(0, nbrows);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void PeerListWidget::loadSettings() {
|
|
||||||
header()->restoreState(Preferences::instance()->getPeerListState());
|
|
||||||
}
|
|
||||||
|
|
||||||
void PeerListWidget::saveSettings() const {
|
|
||||||
Preferences::instance()->setPeerListState(header()->saveState());
|
|
||||||
}
|
|
||||||
|
|
||||||
void PeerListWidget::loadPeers(BitTorrent::TorrentHandle *const torrent, bool force_hostname_resolution) {
|
|
||||||
if (!torrent) return;
|
|
||||||
|
|
||||||
QList<BitTorrent::PeerInfo> peers = torrent->peers();
|
|
||||||
QSet<QString> old_peers_set = m_peerItems.keys().toSet();
|
|
||||||
|
|
||||||
foreach (const BitTorrent::PeerInfo &peer, peers) {
|
|
||||||
BitTorrent::PeerAddress addr = peer.address();
|
|
||||||
if (addr.ip.isNull()) continue;
|
|
||||||
|
|
||||||
QString peer_ip = addr.ip.toString();
|
|
||||||
if (m_peerItems.contains(peer_ip)) {
|
|
||||||
// Update existing peer
|
|
||||||
updatePeer(peer_ip, torrent, peer);
|
|
||||||
old_peers_set.remove(peer_ip);
|
|
||||||
if (force_hostname_resolution && m_resolver) {
|
|
||||||
m_resolver->resolve(peer_ip);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Add new peer
|
|
||||||
m_peerItems[peer_ip] = addPeer(peer_ip, torrent, peer);
|
|
||||||
m_peerAddresses[peer_ip] = addr;
|
|
||||||
// Resolve peer host name is asked
|
|
||||||
if (m_resolver)
|
|
||||||
m_resolver->resolve(peer_ip);
|
|
||||||
}
|
}
|
||||||
}
|
QApplication::clipboard()->setText(selectedPeers.join("\n"));
|
||||||
// Delete peers that are gone
|
|
||||||
QSetIterator<QString> it(old_peers_set);
|
|
||||||
while(it.hasNext()) {
|
|
||||||
const QString& ip = it.next();
|
|
||||||
m_missingFlags.remove(ip);
|
|
||||||
m_peerAddresses.remove(ip);
|
|
||||||
QStandardItem *item = m_peerItems.take(ip);
|
|
||||||
m_listModel->removeRow(item->row());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QStandardItem* PeerListWidget::addPeer(const QString& ip, BitTorrent::TorrentHandle *const torrent, const BitTorrent::PeerInfo &peer) {
|
void PeerListWidget::clear()
|
||||||
int row = m_listModel->rowCount();
|
{
|
||||||
// Adding Peer to peer list
|
qDebug("clearing peer list");
|
||||||
m_listModel->insertRow(row);
|
m_peerItems.clear();
|
||||||
m_listModel->setData(m_listModel->index(row, PeerListDelegate::IP), ip);
|
m_peerAddresses.clear();
|
||||||
m_listModel->setData(m_listModel->index(row, PeerListDelegate::IP), ip, Qt::ToolTipRole);
|
m_missingFlags.clear();
|
||||||
m_listModel->setData(m_listModel->index(row, PeerListDelegate::PORT), peer.address().port);
|
int nbrows = m_listModel->rowCount();
|
||||||
m_listModel->setData(m_listModel->index(row, PeerListDelegate::IP_HIDDEN), ip);
|
if (nbrows > 0) {
|
||||||
if (m_displayFlags) {
|
qDebug("Cleared %d peers", nbrows);
|
||||||
const QIcon ico = GuiIconProvider::instance()->getFlagIcon(peer.country());
|
m_listModel->removeRows(0, nbrows);
|
||||||
if (!ico.isNull()) {
|
|
||||||
m_listModel->setData(m_listModel->index(row, PeerListDelegate::COUNTRY), ico, Qt::DecorationRole);
|
|
||||||
const QString country_name = Net::GeoIPManager::CountryName(peer.country());
|
|
||||||
m_listModel->setData(m_listModel->index(row, PeerListDelegate::COUNTRY), country_name, Qt::ToolTipRole);
|
|
||||||
} else {
|
|
||||||
m_missingFlags.insert(ip);
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
m_listModel->setData(m_listModel->index(row, PeerListDelegate::CONNECTION), peer.connectionType());
|
|
||||||
QString flags, tooltip;
|
|
||||||
getFlags(peer, flags, tooltip);
|
|
||||||
m_listModel->setData(m_listModel->index(row, PeerListDelegate::FLAGS), flags);
|
|
||||||
m_listModel->setData(m_listModel->index(row, PeerListDelegate::FLAGS), tooltip, Qt::ToolTipRole);
|
|
||||||
m_listModel->setData(m_listModel->index(row, PeerListDelegate::CLIENT), peer.client());
|
|
||||||
m_listModel->setData(m_listModel->index(row, PeerListDelegate::PROGRESS), peer.progress());
|
|
||||||
m_listModel->setData(m_listModel->index(row, PeerListDelegate::DOWN_SPEED), peer.payloadDownSpeed());
|
|
||||||
m_listModel->setData(m_listModel->index(row, PeerListDelegate::UP_SPEED), peer.payloadUpSpeed());
|
|
||||||
m_listModel->setData(m_listModel->index(row, PeerListDelegate::TOT_DOWN), peer.totalDownload());
|
|
||||||
m_listModel->setData(m_listModel->index(row, PeerListDelegate::TOT_UP), peer.totalUpload());
|
|
||||||
m_listModel->setData(m_listModel->index(row, PeerListDelegate::RELEVANCE), getPeerRelevance(torrent->pieces(), peer.pieces()));
|
|
||||||
return m_listModel->item(row, PeerListDelegate::IP);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void PeerListWidget::updatePeer(const QString &ip, BitTorrent::TorrentHandle *const torrent, const BitTorrent::PeerInfo &peer) {
|
void PeerListWidget::loadSettings()
|
||||||
QStandardItem *item = m_peerItems.value(ip);
|
{
|
||||||
int row = item->row();
|
header()->restoreState(Preferences::instance()->getPeerListState());
|
||||||
if (m_displayFlags) {
|
}
|
||||||
const QIcon ico = GuiIconProvider::instance()->getFlagIcon(peer.country());
|
|
||||||
if (!ico.isNull()) {
|
void PeerListWidget::saveSettings() const
|
||||||
m_listModel->setData(m_listModel->index(row, PeerListDelegate::COUNTRY), ico, Qt::DecorationRole);
|
{
|
||||||
const QString country_name = Net::GeoIPManager::CountryName(peer.country());
|
Preferences::instance()->setPeerListState(header()->saveState());
|
||||||
m_listModel->setData(m_listModel->index(row, PeerListDelegate::COUNTRY), country_name, Qt::ToolTipRole);
|
}
|
||||||
m_missingFlags.remove(ip);
|
|
||||||
|
void PeerListWidget::loadPeers(BitTorrent::TorrentHandle *const torrent, bool forceHostnameResolution)
|
||||||
|
{
|
||||||
|
if (!torrent) return;
|
||||||
|
|
||||||
|
QList<BitTorrent::PeerInfo> peers = torrent->peers();
|
||||||
|
QSet<QString> oldeersSet = m_peerItems.keys().toSet();
|
||||||
|
|
||||||
|
foreach (const BitTorrent::PeerInfo &peer, peers) {
|
||||||
|
BitTorrent::PeerAddress addr = peer.address();
|
||||||
|
if (addr.ip.isNull()) continue;
|
||||||
|
|
||||||
|
QString peerIp = addr.ip.toString();
|
||||||
|
if (m_peerItems.contains(peerIp)) {
|
||||||
|
// Update existing peer
|
||||||
|
updatePeer(peerIp, torrent, peer);
|
||||||
|
oldeersSet.remove(peerIp);
|
||||||
|
if (forceHostnameResolution && m_resolver)
|
||||||
|
m_resolver->resolve(peerIp);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Add new peer
|
||||||
|
m_peerItems[peerIp] = addPeer(peerIp, torrent, peer);
|
||||||
|
m_peerAddresses[peerIp] = addr;
|
||||||
|
// Resolve peer host name is asked
|
||||||
|
if (m_resolver)
|
||||||
|
m_resolver->resolve(peerIp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Delete peers that are gone
|
||||||
|
QSetIterator<QString> it(oldeersSet);
|
||||||
|
while (it.hasNext()) {
|
||||||
|
const QString& ip = it.next();
|
||||||
|
m_missingFlags.remove(ip);
|
||||||
|
m_peerAddresses.remove(ip);
|
||||||
|
QStandardItem *item = m_peerItems.take(ip);
|
||||||
|
m_listModel->removeRow(item->row());
|
||||||
}
|
}
|
||||||
}
|
|
||||||
m_listModel->setData(m_listModel->index(row, PeerListDelegate::CONNECTION), peer.connectionType());
|
|
||||||
QString flags, tooltip;
|
|
||||||
getFlags(peer, flags, tooltip);
|
|
||||||
m_listModel->setData(m_listModel->index(row, PeerListDelegate::PORT), peer.address().port);
|
|
||||||
m_listModel->setData(m_listModel->index(row, PeerListDelegate::FLAGS), flags);
|
|
||||||
m_listModel->setData(m_listModel->index(row, PeerListDelegate::FLAGS), tooltip, Qt::ToolTipRole);
|
|
||||||
m_listModel->setData(m_listModel->index(row, PeerListDelegate::CLIENT), peer.client());
|
|
||||||
m_listModel->setData(m_listModel->index(row, PeerListDelegate::PROGRESS), peer.progress());
|
|
||||||
m_listModel->setData(m_listModel->index(row, PeerListDelegate::DOWN_SPEED), peer.payloadDownSpeed());
|
|
||||||
m_listModel->setData(m_listModel->index(row, PeerListDelegate::UP_SPEED), peer.payloadUpSpeed());
|
|
||||||
m_listModel->setData(m_listModel->index(row, PeerListDelegate::TOT_DOWN), peer.totalDownload());
|
|
||||||
m_listModel->setData(m_listModel->index(row, PeerListDelegate::TOT_UP), peer.totalUpload());
|
|
||||||
m_listModel->setData(m_listModel->index(row, PeerListDelegate::RELEVANCE), getPeerRelevance(torrent->pieces(), peer.pieces()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void PeerListWidget::handleResolved(const QString &ip, const QString &hostname) {
|
QStandardItem* PeerListWidget::addPeer(const QString& ip, BitTorrent::TorrentHandle *const torrent, const BitTorrent::PeerInfo &peer)
|
||||||
QStandardItem *item = m_peerItems.value(ip, 0);
|
{
|
||||||
if (item) {
|
int row = m_listModel->rowCount();
|
||||||
qDebug("Resolved %s -> %s", qPrintable(ip), qPrintable(hostname));
|
// Adding Peer to peer list
|
||||||
item->setData(hostname, Qt::DisplayRole);
|
m_listModel->insertRow(row);
|
||||||
}
|
m_listModel->setData(m_listModel->index(row, PeerListDelegate::IP), ip);
|
||||||
|
m_listModel->setData(m_listModel->index(row, PeerListDelegate::IP), ip, Qt::ToolTipRole);
|
||||||
|
m_listModel->setData(m_listModel->index(row, PeerListDelegate::PORT), peer.address().port);
|
||||||
|
m_listModel->setData(m_listModel->index(row, PeerListDelegate::IP_HIDDEN), ip);
|
||||||
|
if (m_displayFlags) {
|
||||||
|
const QIcon ico = GuiIconProvider::instance()->getFlagIcon(peer.country());
|
||||||
|
if (!ico.isNull()) {
|
||||||
|
m_listModel->setData(m_listModel->index(row, PeerListDelegate::COUNTRY), ico, Qt::DecorationRole);
|
||||||
|
const QString countryName = Net::GeoIPManager::CountryName(peer.country());
|
||||||
|
m_listModel->setData(m_listModel->index(row, PeerListDelegate::COUNTRY), countryName, Qt::ToolTipRole);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
m_missingFlags.insert(ip);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
m_listModel->setData(m_listModel->index(row, PeerListDelegate::CONNECTION), peer.connectionType());
|
||||||
|
m_listModel->setData(m_listModel->index(row, PeerListDelegate::FLAGS), peer.flags());
|
||||||
|
m_listModel->setData(m_listModel->index(row, PeerListDelegate::FLAGS), peer.flagsDescription(), Qt::ToolTipRole);
|
||||||
|
m_listModel->setData(m_listModel->index(row, PeerListDelegate::CLIENT), peer.client());
|
||||||
|
m_listModel->setData(m_listModel->index(row, PeerListDelegate::PROGRESS), peer.progress());
|
||||||
|
m_listModel->setData(m_listModel->index(row, PeerListDelegate::DOWN_SPEED), peer.payloadDownSpeed());
|
||||||
|
m_listModel->setData(m_listModel->index(row, PeerListDelegate::UP_SPEED), peer.payloadUpSpeed());
|
||||||
|
m_listModel->setData(m_listModel->index(row, PeerListDelegate::TOT_DOWN), peer.totalDownload());
|
||||||
|
m_listModel->setData(m_listModel->index(row, PeerListDelegate::TOT_UP), peer.totalUpload());
|
||||||
|
m_listModel->setData(m_listModel->index(row, PeerListDelegate::RELEVANCE), peer.relevance());
|
||||||
|
return m_listModel->item(row, PeerListDelegate::IP);
|
||||||
|
}
|
||||||
|
|
||||||
|
void PeerListWidget::updatePeer(const QString &ip, BitTorrent::TorrentHandle *const torrent, const BitTorrent::PeerInfo &peer)
|
||||||
|
{
|
||||||
|
QStandardItem *item = m_peerItems.value(ip);
|
||||||
|
int row = item->row();
|
||||||
|
if (m_displayFlags) {
|
||||||
|
const QIcon ico = GuiIconProvider::instance()->getFlagIcon(peer.country());
|
||||||
|
if (!ico.isNull()) {
|
||||||
|
m_listModel->setData(m_listModel->index(row, PeerListDelegate::COUNTRY), ico, Qt::DecorationRole);
|
||||||
|
const QString countryName = Net::GeoIPManager::CountryName(peer.country());
|
||||||
|
m_listModel->setData(m_listModel->index(row, PeerListDelegate::COUNTRY), countryName, Qt::ToolTipRole);
|
||||||
|
m_missingFlags.remove(ip);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
m_listModel->setData(m_listModel->index(row, PeerListDelegate::CONNECTION), peer.connectionType());
|
||||||
|
m_listModel->setData(m_listModel->index(row, PeerListDelegate::PORT), peer.address().port);
|
||||||
|
m_listModel->setData(m_listModel->index(row, PeerListDelegate::FLAGS), peer.flags());
|
||||||
|
m_listModel->setData(m_listModel->index(row, PeerListDelegate::FLAGS), peer.flagsDescription(), Qt::ToolTipRole);
|
||||||
|
m_listModel->setData(m_listModel->index(row, PeerListDelegate::CLIENT), peer.client());
|
||||||
|
m_listModel->setData(m_listModel->index(row, PeerListDelegate::PROGRESS), peer.progress());
|
||||||
|
m_listModel->setData(m_listModel->index(row, PeerListDelegate::DOWN_SPEED), peer.payloadDownSpeed());
|
||||||
|
m_listModel->setData(m_listModel->index(row, PeerListDelegate::UP_SPEED), peer.payloadUpSpeed());
|
||||||
|
m_listModel->setData(m_listModel->index(row, PeerListDelegate::TOT_DOWN), peer.totalDownload());
|
||||||
|
m_listModel->setData(m_listModel->index(row, PeerListDelegate::TOT_UP), peer.totalUpload());
|
||||||
|
m_listModel->setData(m_listModel->index(row, PeerListDelegate::RELEVANCE), peer.relevance());
|
||||||
|
}
|
||||||
|
|
||||||
|
void PeerListWidget::handleResolved(const QString &ip, const QString &hostname)
|
||||||
|
{
|
||||||
|
QStandardItem *item = m_peerItems.value(ip, 0);
|
||||||
|
if (item) {
|
||||||
|
qDebug("Resolved %s -> %s", qPrintable(ip), qPrintable(hostname));
|
||||||
|
item->setData(hostname, Qt::DisplayRole);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void PeerListWidget::handleSortColumnChanged(int col)
|
void PeerListWidget::handleSortColumnChanged(int col)
|
||||||
{
|
{
|
||||||
if (col == PeerListDelegate::COUNTRY) {
|
if (col == PeerListDelegate::COUNTRY) {
|
||||||
qDebug("Sorting by decoration");
|
qDebug("Sorting by decoration");
|
||||||
m_proxyModel->setSortRole(Qt::ToolTipRole);
|
m_proxyModel->setSortRole(Qt::ToolTipRole);
|
||||||
} else {
|
|
||||||
m_proxyModel->setSortRole(Qt::DisplayRole);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void PeerListWidget::getFlags(const BitTorrent::PeerInfo &peer, QString& flags, QString& tooltip)
|
|
||||||
{
|
|
||||||
if (peer.isInteresting()) {
|
|
||||||
//d = Your client wants to download, but peer doesn't want to send (interested and choked)
|
|
||||||
if (peer.isRemoteChocked()) {
|
|
||||||
flags += "d ";
|
|
||||||
tooltip += tr("interested(local) and choked(peer)");
|
|
||||||
tooltip += ", ";
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
//D = Currently downloading (interested and not choked)
|
m_proxyModel->setSortRole(Qt::DisplayRole);
|
||||||
flags += "D ";
|
|
||||||
tooltip += tr("interested(local) and unchoked(peer)");
|
|
||||||
tooltip += ", ";
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (peer.isRemoteInterested()) {
|
|
||||||
//u = Peer wants your client to upload, but your client doesn't want to (interested and choked)
|
|
||||||
if (peer.isChocked()) {
|
|
||||||
flags += "u ";
|
|
||||||
tooltip += tr("interested(peer) and choked(local)");
|
|
||||||
tooltip += ", ";
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
//U = Currently uploading (interested and not choked)
|
|
||||||
flags += "U ";
|
|
||||||
tooltip += tr("interested(peer) and unchoked(local)");
|
|
||||||
tooltip += ", ";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//O = Optimistic unchoke
|
|
||||||
if (peer.optimisticUnchoke()) {
|
|
||||||
flags += "O ";
|
|
||||||
tooltip += tr("optimistic unchoke");
|
|
||||||
tooltip += ", ";
|
|
||||||
}
|
|
||||||
|
|
||||||
//S = Peer is snubbed
|
|
||||||
if (peer.isSnubbed()) {
|
|
||||||
flags += "S ";
|
|
||||||
tooltip += tr("peer snubbed");
|
|
||||||
tooltip += ", ";
|
|
||||||
}
|
|
||||||
|
|
||||||
//I = Peer is an incoming connection
|
|
||||||
if (!peer.isLocalConnection()) {
|
|
||||||
flags += "I ";
|
|
||||||
tooltip += tr("incoming connection");
|
|
||||||
tooltip += ", ";
|
|
||||||
}
|
|
||||||
|
|
||||||
//K = Peer is unchoking your client, but your client is not interested
|
|
||||||
if (!peer.isRemoteChocked() && !peer.isInteresting()) {
|
|
||||||
flags += "K ";
|
|
||||||
tooltip += tr("not interested(local) and unchoked(peer)");
|
|
||||||
tooltip += ", ";
|
|
||||||
}
|
|
||||||
|
|
||||||
//? = Your client unchoked the peer but the peer is not interested
|
|
||||||
if (!peer.isChocked() && !peer.isRemoteInterested()) {
|
|
||||||
flags += "? ";
|
|
||||||
tooltip += tr("not interested(peer) and unchoked(local)");
|
|
||||||
tooltip += ", ";
|
|
||||||
}
|
|
||||||
|
|
||||||
//X = Peer was included in peerlists obtained through Peer Exchange (PEX)
|
|
||||||
if (peer.fromPeX()) {
|
|
||||||
flags += "X ";
|
|
||||||
tooltip += tr("peer from PEX");
|
|
||||||
tooltip += ", ";
|
|
||||||
}
|
|
||||||
|
|
||||||
//H = Peer was obtained through DHT
|
|
||||||
if (peer.fromDHT()) {
|
|
||||||
flags += "H ";
|
|
||||||
tooltip += tr("peer from DHT");
|
|
||||||
tooltip += ", ";
|
|
||||||
}
|
|
||||||
|
|
||||||
//E = Peer is using Protocol Encryption (all traffic)
|
|
||||||
if (peer.isRC4Encrypted()) {
|
|
||||||
flags += "E ";
|
|
||||||
tooltip += tr("encrypted traffic");
|
|
||||||
tooltip += ", ";
|
|
||||||
}
|
|
||||||
|
|
||||||
//e = Peer is using Protocol Encryption (handshake)
|
|
||||||
if (peer.isPlaintextEncrypted()) {
|
|
||||||
flags += "e ";
|
|
||||||
tooltip += tr("encrypted handshake");
|
|
||||||
tooltip += ", ";
|
|
||||||
}
|
|
||||||
|
|
||||||
//P = Peer is using uTorrent uTP
|
|
||||||
|
|
||||||
if (peer.useUTPSocket()) {
|
|
||||||
flags += "P ";
|
|
||||||
tooltip += QString::fromUtf8(C_UTP);
|
|
||||||
tooltip += ", ";
|
|
||||||
}
|
|
||||||
|
|
||||||
//L = Peer is local
|
|
||||||
if (peer.fromLSD()) {
|
|
||||||
flags += "L";
|
|
||||||
tooltip += tr("peer from LSD");
|
|
||||||
}
|
|
||||||
|
|
||||||
flags = flags.trimmed();
|
|
||||||
tooltip = tooltip.trimmed();
|
|
||||||
if (tooltip.endsWith(',', Qt::CaseInsensitive))
|
|
||||||
tooltip.chop(1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
qreal PeerListWidget::getPeerRelevance(const QBitArray &allPieces, const QBitArray &peerPieces)
|
|
||||||
{
|
|
||||||
int localMissing = 0;
|
|
||||||
int remoteHaves = 0;
|
|
||||||
|
|
||||||
for (int i = 0; i < allPieces.size(); ++i) {
|
|
||||||
if (!allPieces[i]) {
|
|
||||||
++localMissing;
|
|
||||||
if (peerPieces[i])
|
|
||||||
++remoteHaves;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (localMissing == 0)
|
|
||||||
return 0.0;
|
|
||||||
|
|
||||||
return static_cast<qreal>(remoteHaves) / localMissing;
|
|
||||||
}
|
|
||||||
|
@ -54,52 +54,46 @@ QT_END_NAMESPACE
|
|||||||
|
|
||||||
namespace BitTorrent
|
namespace BitTorrent
|
||||||
{
|
{
|
||||||
|
class TorrentHandle;
|
||||||
class TorrentHandle;
|
class PeerInfo;
|
||||||
class PeerInfo;
|
struct PeerAddress;
|
||||||
struct PeerAddress;
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class PeerListWidget : public QTreeView {
|
class PeerListWidget: public QTreeView
|
||||||
Q_OBJECT
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
PeerListWidget(PropertiesWidget *parent);
|
explicit PeerListWidget(PropertiesWidget *parent);
|
||||||
~PeerListWidget();
|
~PeerListWidget();
|
||||||
|
|
||||||
public slots:
|
void loadPeers(BitTorrent::TorrentHandle *const torrent, bool forceHostnameResolution = false);
|
||||||
void loadPeers(BitTorrent::TorrentHandle *const torrent, bool force_hostname_resolution = false);
|
QStandardItem *addPeer(const QString &ip, BitTorrent::TorrentHandle *const torrent, const BitTorrent::PeerInfo &peer);
|
||||||
QStandardItem *addPeer(const QString &ip, BitTorrent::TorrentHandle *const torrent, const BitTorrent::PeerInfo &peer);
|
void updatePeer(const QString &ip, BitTorrent::TorrentHandle *const torrent, const BitTorrent::PeerInfo &peer);
|
||||||
void updatePeer(const QString &ip, BitTorrent::TorrentHandle *const torrent, const BitTorrent::PeerInfo &peer);
|
void handleResolved(const QString &ip, const QString &hostname);
|
||||||
void handleResolved(const QString &ip, const QString &hostname);
|
void updatePeerHostNameResolutionState();
|
||||||
void updatePeerHostNameResolutionState();
|
void updatePeerCountryResolutionState();
|
||||||
void updatePeerCountryResolutionState();
|
void clear();
|
||||||
void clear();
|
|
||||||
|
|
||||||
protected slots:
|
private slots:
|
||||||
void loadSettings();
|
void loadSettings();
|
||||||
void saveSettings() const;
|
void saveSettings() const;
|
||||||
void showPeerListMenu(const QPoint&);
|
void showPeerListMenu(const QPoint&);
|
||||||
void banSelectedPeers();
|
void banSelectedPeers();
|
||||||
void copySelectedPeers();
|
void copySelectedPeers();
|
||||||
void handleSortColumnChanged(int col);
|
void handleSortColumnChanged(int col);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static void getFlags(const BitTorrent::PeerInfo &peer, QString &flags, QString &tooltip);
|
QStandardItemModel *m_listModel;
|
||||||
qreal getPeerRelevance(const QBitArray &allPieces, const QBitArray &peerPieces);
|
PeerListDelegate *m_listDelegate;
|
||||||
|
PeerListSortModel *m_proxyModel;
|
||||||
private:
|
QHash<QString, QStandardItem*> m_peerItems;
|
||||||
QStandardItemModel *m_listModel;
|
QHash<QString, BitTorrent::PeerAddress> m_peerAddresses;
|
||||||
PeerListDelegate *m_listDelegate;
|
QSet<QString> m_missingFlags;
|
||||||
PeerListSortModel *m_proxyModel;
|
QPointer<Net::ReverseResolution> m_resolver;
|
||||||
QHash<QString, QStandardItem*> m_peerItems;
|
PropertiesWidget *m_properties;
|
||||||
QHash<QString, BitTorrent::PeerAddress> m_peerAddresses;
|
bool m_displayFlags;
|
||||||
QSet<QString> m_missingFlags;
|
QShortcut *m_copyHotkey;
|
||||||
QPointer<Net::ReverseResolution> m_resolver;
|
|
||||||
PropertiesWidget *m_properties;
|
|
||||||
bool m_displayFlags;
|
|
||||||
QShortcut *copyHotkey;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // PEERLISTWIDGET_H
|
#endif // PEERLISTWIDGET_H
|
||||||
|
@ -36,7 +36,9 @@
|
|||||||
#include "base/bittorrent/sessionstatus.h"
|
#include "base/bittorrent/sessionstatus.h"
|
||||||
#include "base/bittorrent/torrenthandle.h"
|
#include "base/bittorrent/torrenthandle.h"
|
||||||
#include "base/bittorrent/trackerentry.h"
|
#include "base/bittorrent/trackerentry.h"
|
||||||
|
#include "base/bittorrent/peerinfo.h"
|
||||||
#include "base/torrentfilter.h"
|
#include "base/torrentfilter.h"
|
||||||
|
#include "base/net/geoipmanager.h"
|
||||||
#include "jsonutils.h"
|
#include "jsonutils.h"
|
||||||
|
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
@ -105,6 +107,22 @@ static const char KEY_TORRENT_SUPER_SEEDING[] = "super_seeding";
|
|||||||
static const char KEY_TORRENT_FORCE_START[] = "force_start";
|
static const char KEY_TORRENT_FORCE_START[] = "force_start";
|
||||||
static const char KEY_TORRENT_SAVE_PATH[] = "save_path";
|
static const char KEY_TORRENT_SAVE_PATH[] = "save_path";
|
||||||
|
|
||||||
|
// Peer keys
|
||||||
|
static const char KEY_PEER_IP[] = "ip";
|
||||||
|
static const char KEY_PEER_PORT[] = "port";
|
||||||
|
static const char KEY_PEER_COUNTRY_CODE[] = "country_code";
|
||||||
|
static const char KEY_PEER_COUNTRY[] = "country";
|
||||||
|
static const char KEY_PEER_CLIENT[] = "client";
|
||||||
|
static const char KEY_PEER_PROGRESS[] = "progress";
|
||||||
|
static const char KEY_PEER_DOWN_SPEED[] = "dl_speed";
|
||||||
|
static const char KEY_PEER_UP_SPEED[] = "up_speed";
|
||||||
|
static const char KEY_PEER_TOT_DOWN[] = "downloaded";
|
||||||
|
static const char KEY_PEER_TOT_UP[] = "uploaded";
|
||||||
|
static const char KEY_PEER_CONNECTION_TYPE[] = "connection";
|
||||||
|
static const char KEY_PEER_FLAGS[] = "flags";
|
||||||
|
static const char KEY_PEER_FLAGS_DESCRIPTION[] = "flags_desc";
|
||||||
|
static const char KEY_PEER_RELEVANCE[] = "relevance";
|
||||||
|
|
||||||
// Tracker keys
|
// Tracker keys
|
||||||
static const char KEY_TRACKER_URL[] = "url";
|
static const char KEY_TRACKER_URL[] = "url";
|
||||||
static const char KEY_TRACKER_STATUS[] = "status";
|
static const char KEY_TRACKER_STATUS[] = "status";
|
||||||
@ -171,6 +189,9 @@ static const char KEY_SYNC_MAINDATA_QUEUEING[] = "queueing";
|
|||||||
static const char KEY_SYNC_MAINDATA_USE_ALT_SPEED_LIMITS[] = "use_alt_speed_limits";
|
static const char KEY_SYNC_MAINDATA_USE_ALT_SPEED_LIMITS[] = "use_alt_speed_limits";
|
||||||
static const char KEY_SYNC_MAINDATA_REFRESH_INTERVAL[] = "refresh_interval";
|
static const char KEY_SYNC_MAINDATA_REFRESH_INTERVAL[] = "refresh_interval";
|
||||||
|
|
||||||
|
// Sync torrent peers keys
|
||||||
|
static const char KEY_SYNC_TORRENT_PEERS_SHOW_FLAGS[] = "show_flags";
|
||||||
|
|
||||||
static const char KEY_FULL_UPDATE[] = "full_update";
|
static const char KEY_FULL_UPDATE[] = "full_update";
|
||||||
static const char KEY_RESPONSE_ID[] = "rid";
|
static const char KEY_RESPONSE_ID[] = "rid";
|
||||||
static const char KEY_SUFFIX_REMOVED[] = "_removed";
|
static const char KEY_SUFFIX_REMOVED[] = "_removed";
|
||||||
@ -354,6 +375,54 @@ QByteArray btjson::getSyncMainData(int acceptedResponseId, QVariantMap &lastData
|
|||||||
return json::toJson(generateSyncData(acceptedResponseId, data, lastAcceptedData, lastData));
|
return json::toJson(generateSyncData(acceptedResponseId, data, lastAcceptedData, lastData));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QByteArray btjson::getSyncTorrentPeersData(int acceptedResponseId, QString hash, QVariantMap &lastData, QVariantMap &lastAcceptedData)
|
||||||
|
{
|
||||||
|
BitTorrent::TorrentHandle *const torrent = BitTorrent::Session::instance()->findTorrent(hash);
|
||||||
|
if (!torrent) {
|
||||||
|
qWarning() << Q_FUNC_INFO << "Invalid torrent " << qPrintable(hash);
|
||||||
|
return QByteArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
QVariantMap data;
|
||||||
|
QVariantHash peers;
|
||||||
|
QList<BitTorrent::PeerInfo> peersList = torrent->peers();
|
||||||
|
#ifndef DISABLE_COUNTRIES_RESOLUTION
|
||||||
|
bool resolvePeerCountries = Preferences::instance()->resolvePeerCountries();
|
||||||
|
#else
|
||||||
|
bool resolvePeerCountries = false;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
data[KEY_SYNC_TORRENT_PEERS_SHOW_FLAGS] = resolvePeerCountries;
|
||||||
|
|
||||||
|
foreach (const BitTorrent::PeerInfo &pi, peersList) {
|
||||||
|
if (pi.address().ip.isNull()) continue;
|
||||||
|
QVariantMap peer;
|
||||||
|
#ifndef DISABLE_COUNTRIES_RESOLUTION
|
||||||
|
if (resolvePeerCountries) {
|
||||||
|
peer[KEY_PEER_COUNTRY_CODE] = pi.country().toLower();
|
||||||
|
peer[KEY_PEER_COUNTRY] = Net::GeoIPManager::CountryName(pi.country());
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
peer[KEY_PEER_IP] = pi.address().ip.toString();
|
||||||
|
peer[KEY_PEER_PORT] = pi.address().port;
|
||||||
|
peer[KEY_PEER_CLIENT] = pi.client();
|
||||||
|
peer[KEY_PEER_PROGRESS] = pi.progress();
|
||||||
|
peer[KEY_PEER_DOWN_SPEED] = pi.payloadDownSpeed();
|
||||||
|
peer[KEY_PEER_UP_SPEED] = pi.payloadUpSpeed();
|
||||||
|
peer[KEY_PEER_TOT_DOWN] = pi.totalDownload();
|
||||||
|
peer[KEY_PEER_TOT_UP] = pi.totalUpload();
|
||||||
|
peer[KEY_PEER_CONNECTION_TYPE] = pi.connectionType();
|
||||||
|
peer[KEY_PEER_FLAGS] = pi.flags();
|
||||||
|
peer[KEY_PEER_FLAGS_DESCRIPTION] = pi.flagsDescription();
|
||||||
|
peer[KEY_PEER_RELEVANCE] = pi.relevance();
|
||||||
|
peers[pi.address().ip.toString() + ":" + QString::number(pi.address().port)] = peer;
|
||||||
|
}
|
||||||
|
|
||||||
|
data["peers"] = peers;
|
||||||
|
|
||||||
|
return json::toJson(generateSyncData(acceptedResponseId, data, lastAcceptedData, lastData));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the trackers for a torrent in JSON format.
|
* Returns the trackers for a torrent in JSON format.
|
||||||
*
|
*
|
||||||
@ -610,7 +679,7 @@ QByteArray btjson::getTorrentsRatesLimits(QStringList &hashes, bool downloadLimi
|
|||||||
map[hash] = limit;
|
map[hash] = limit;
|
||||||
}
|
}
|
||||||
|
|
||||||
return json::toJson(map);
|
return json::toJson(map);
|
||||||
}
|
}
|
||||||
|
|
||||||
QVariantMap toMap(BitTorrent::TorrentHandle *const torrent)
|
QVariantMap toMap(BitTorrent::TorrentHandle *const torrent)
|
||||||
|
@ -46,6 +46,7 @@ public:
|
|||||||
static QByteArray getTorrents(QString filter = "all", QString label = QString(),
|
static QByteArray getTorrents(QString filter = "all", QString label = QString(),
|
||||||
QString sortedColumn = "name", bool reverse = false, int limit = 0, int offset = 0);
|
QString sortedColumn = "name", bool reverse = false, int limit = 0, int offset = 0);
|
||||||
static QByteArray getSyncMainData(int acceptedResponseId, QVariantMap &lastData, QVariantMap &lastAcceptedData);
|
static QByteArray getSyncMainData(int acceptedResponseId, QVariantMap &lastData, QVariantMap &lastAcceptedData);
|
||||||
|
static QByteArray getSyncTorrentPeersData(int acceptedResponseId, QString hash, QVariantMap &lastData, QVariantMap &lastAcceptedData);
|
||||||
static QByteArray getTrackersForTorrent(const QString& hash);
|
static QByteArray getTrackersForTorrent(const QString& hash);
|
||||||
static QByteArray getWebSeedsForTorrent(const QString& hash);
|
static QByteArray getWebSeedsForTorrent(const QString& hash);
|
||||||
static QByteArray getPropertiesForTorrent(const QString& hash);
|
static QByteArray getPropertiesForTorrent(const QString& hash);
|
||||||
|
@ -82,6 +82,7 @@ QMap<QString, QMap<QString, WebApplication::Action> > WebApplication::initialize
|
|||||||
ADD_ACTION(query, propertiesWebSeeds);
|
ADD_ACTION(query, propertiesWebSeeds);
|
||||||
ADD_ACTION(query, propertiesFiles);
|
ADD_ACTION(query, propertiesFiles);
|
||||||
ADD_ACTION(sync, maindata);
|
ADD_ACTION(sync, maindata);
|
||||||
|
ADD_ACTION(sync, torrent_peers);
|
||||||
ADD_ACTION(command, shutdown);
|
ADD_ACTION(command, shutdown);
|
||||||
ADD_ACTION(command, download);
|
ADD_ACTION(command, download);
|
||||||
ADD_ACTION(command, upload);
|
ADD_ACTION(command, upload);
|
||||||
@ -277,6 +278,19 @@ void WebApplication::action_sync_maindata()
|
|||||||
session()->syncMainDataLastAcceptedResponse), Http::CONTENT_TYPE_JSON);
|
session()->syncMainDataLastAcceptedResponse), Http::CONTENT_TYPE_JSON);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GET param:
|
||||||
|
// - hash (string): torrent hash
|
||||||
|
// - rid (int): last response id
|
||||||
|
void WebApplication::action_sync_torrent_peers()
|
||||||
|
{
|
||||||
|
CHECK_URI(0);
|
||||||
|
print(btjson::getSyncTorrentPeersData(request().gets["rid"].toInt(),
|
||||||
|
request().gets["hash"],
|
||||||
|
session()->syncTorrentPeersLastResponse,
|
||||||
|
session()->syncTorrentPeersLastAcceptedResponse), Http::CONTENT_TYPE_JSON);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
void WebApplication::action_version_api()
|
void WebApplication::action_version_api()
|
||||||
{
|
{
|
||||||
CHECK_URI(0);
|
CHECK_URI(0);
|
||||||
|
@ -55,6 +55,7 @@ private:
|
|||||||
void action_query_propertiesWebSeeds();
|
void action_query_propertiesWebSeeds();
|
||||||
void action_query_propertiesFiles();
|
void action_query_propertiesFiles();
|
||||||
void action_sync_maindata();
|
void action_sync_maindata();
|
||||||
|
void action_sync_torrent_peers();
|
||||||
void action_command_shutdown();
|
void action_command_shutdown();
|
||||||
void action_command_download();
|
void action_command_download();
|
||||||
void action_command_upload();
|
void action_command_upload();
|
||||||
|
@ -35,6 +35,8 @@ struct WebSessionData
|
|||||||
{
|
{
|
||||||
QVariantMap syncMainDataLastResponse;
|
QVariantMap syncMainDataLastResponse;
|
||||||
QVariantMap syncMainDataLastAcceptedResponse;
|
QVariantMap syncMainDataLastAcceptedResponse;
|
||||||
|
QVariantMap syncTorrentPeersLastResponse;
|
||||||
|
QVariantMap syncTorrentPeersLastAcceptedResponse;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // WEBSESSIONDATA
|
#endif // WEBSESSIONDATA
|
||||||
|
@ -29,7 +29,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#myTable tr:nth-child(even),
|
#torrentsTable tr:nth-child(even),
|
||||||
|
#torrentPeersTable tr:nth-child(even),
|
||||||
#filesTable tr:nth-child(even),
|
#filesTable tr:nth-child(even),
|
||||||
#properties #torrentFiles tr.alt,
|
#properties #torrentFiles tr.alt,
|
||||||
#properties #trackers tr.alt,
|
#properties #trackers tr.alt,
|
||||||
@ -50,8 +51,13 @@
|
|||||||
background-color: #415A8D;
|
background-color: #415A8D;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
}
|
}
|
||||||
|
#torrentPeersTable tr.selected {
|
||||||
|
background-color: #354158;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
#myTable tr:hover,
|
#torrentsTable tr:hover,
|
||||||
|
#torrentPeersTable tr:hover,
|
||||||
#filesTable tr:hover,
|
#filesTable tr:hover,
|
||||||
#properties #torrentFiles tr.over,
|
#properties #torrentFiles tr.over,
|
||||||
#properties #trackers tr.over,
|
#properties #trackers tr.over,
|
||||||
@ -60,7 +66,8 @@
|
|||||||
color: #fff;
|
color: #fff;
|
||||||
}
|
}
|
||||||
|
|
||||||
#myTable tr:hover,
|
#torrentsTable tr:hover,
|
||||||
|
#torrentsTable tr:hover,
|
||||||
#properties #torrentFiles tr.over,
|
#properties #torrentFiles tr.over,
|
||||||
#properties #trackers tr.over,
|
#properties #trackers tr.over,
|
||||||
#transferList tr.over {
|
#transferList tr.over {
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
<ul id="propertiesTabs" class="tab-menu">
|
<ul id="propertiesTabs" class="tab-menu">
|
||||||
<li id="PropGeneralLink" class="selected"><a>QBT_TR(General)QBT_TR</a></li>
|
<li id="PropGeneralLink" class="selected"><a>QBT_TR(General)QBT_TR</a></li>
|
||||||
<li id="PropTrackersLink"><a>QBT_TR(Trackers)QBT_TR</a></li>
|
<li id="PropTrackersLink"><a>QBT_TR(Trackers)QBT_TR</a></li>
|
||||||
|
<li id="PropPeersLink"><a>QBT_TR(Peers)QBT_TR</a></li>
|
||||||
<li id="PropWebSeedsLink"><a>QBT_TR(HTTP Sources)QBT_TR</a></li>
|
<li id="PropWebSeedsLink"><a>QBT_TR(HTTP Sources)QBT_TR</a></li>
|
||||||
<li id="PropFilesLink"><a>QBT_TR(Content)QBT_TR</a></li>
|
<li id="PropFilesLink"><a>QBT_TR(Content)QBT_TR</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
|
@ -63,6 +63,18 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div id="prop_peers" class="invisible">
|
||||||
|
<div id="peers">
|
||||||
|
<table class="torrentTable" cellpadding="0" cellspacing="0" style="width: 100%">
|
||||||
|
<thead>
|
||||||
|
<tr id="torrentPeersTableHeader">
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody id="torrentPeersTable"></tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div id="prop_webseeds" class="invisible">
|
<div id="prop_webseeds" class="invisible">
|
||||||
<div id="webseeds">
|
<div id="webseeds">
|
||||||
<table class="torrentTable" cellpadding="0" cellspacing="0" style="width: 100%">
|
<table class="torrentTable" cellpadding="0" cellspacing="0" style="width: 100%">
|
||||||
@ -92,3 +104,7 @@
|
|||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<script type="text/javascript">
|
||||||
|
torrentPeersTable.setup('torrentPeersTable', 'torrentPeersTableHeader', null);
|
||||||
|
</script>
|
@ -22,9 +22,11 @@
|
|||||||
* THE SOFTWARE.
|
* THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
myTable = new dynamicTable();
|
torrentsTable = new TorrentsTable();
|
||||||
|
torrentPeersTable = new TorrentPeersTable();
|
||||||
|
|
||||||
var updatePropertiesPanel = function(){};
|
var updatePropertiesPanel = function(){};
|
||||||
|
var updateTorrentPeersData = function(){};
|
||||||
var updateMainData = function(){};
|
var updateMainData = function(){};
|
||||||
var alternativeSpeedLimits = false;
|
var alternativeSpeedLimits = false;
|
||||||
var queueing_enabled = true;
|
var queueing_enabled = true;
|
||||||
@ -99,7 +101,7 @@ window.addEvent('load', function () {
|
|||||||
selected_label = hash;
|
selected_label = hash;
|
||||||
localStorage.setItem('selected_label', selected_label);
|
localStorage.setItem('selected_label', selected_label);
|
||||||
highlightSelectedLabel();
|
highlightSelectedLabel();
|
||||||
if (typeof myTable.table != 'undefined')
|
if (typeof torrentsTable.table != 'undefined')
|
||||||
updateMainData();
|
updateMainData();
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -117,7 +119,7 @@ window.addEvent('load', function () {
|
|||||||
selected_filter = f;
|
selected_filter = f;
|
||||||
localStorage.setItem('selected_filter', f);
|
localStorage.setItem('selected_filter', f);
|
||||||
// Reload torrents
|
// Reload torrents
|
||||||
if (typeof myTable.table != 'undefined')
|
if (typeof torrentsTable.table != 'undefined')
|
||||||
updateMainData();
|
updateMainData();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -230,9 +232,9 @@ window.addEvent('load', function () {
|
|||||||
return new Element('li', {id: hash, html: html});
|
return new Element('li', {id: hash, html: html});
|
||||||
};
|
};
|
||||||
|
|
||||||
var all = myTable.getRowIds().length;
|
var all = torrentsTable.getRowIds().length;
|
||||||
var unlabelled = 0;
|
var unlabelled = 0;
|
||||||
Object.each(myTable.rows, function(row) {
|
Object.each(torrentsTable.rows, function(row) {
|
||||||
if (row['full_data'].label.length === 0)
|
if (row['full_data'].label.length === 0)
|
||||||
unlabelled += 1;
|
unlabelled += 1;
|
||||||
});
|
});
|
||||||
@ -286,7 +288,7 @@ window.addEvent('load', function () {
|
|||||||
var update_labels = false;
|
var update_labels = false;
|
||||||
var full_update = (response['full_update'] == true);
|
var full_update = (response['full_update'] == true);
|
||||||
if (full_update) {
|
if (full_update) {
|
||||||
myTable.rows.erase();
|
torrentsTable.clear();
|
||||||
label_list = {};
|
label_list = {};
|
||||||
}
|
}
|
||||||
if (response['rid']) {
|
if (response['rid']) {
|
||||||
@ -309,19 +311,20 @@ window.addEvent('load', function () {
|
|||||||
if (response['torrents']) {
|
if (response['torrents']) {
|
||||||
for (var key in response['torrents']) {
|
for (var key in response['torrents']) {
|
||||||
response['torrents'][key]['hash'] = key;
|
response['torrents'][key]['hash'] = key;
|
||||||
myTable.updateRowData(response['torrents'][key]);
|
response['torrents'][key]['rowId'] = key;
|
||||||
|
torrentsTable.updateRowData(response['torrents'][key]);
|
||||||
if (addTorrentToLabelList(response['torrents'][key]))
|
if (addTorrentToLabelList(response['torrents'][key]))
|
||||||
update_labels = true;
|
update_labels = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (response['torrents_removed'])
|
if (response['torrents_removed'])
|
||||||
response['torrents_removed'].each(function (hash) {
|
response['torrents_removed'].each(function (hash) {
|
||||||
myTable.removeRow(hash);
|
torrentsTable.removeRow(hash);
|
||||||
removeTorrentFromLabelList(hash);
|
removeTorrentFromLabelList(hash);
|
||||||
update_labels = true; // Allways to update All label
|
update_labels = true; // Allways to update All label
|
||||||
});
|
});
|
||||||
myTable.updateTable(full_update);
|
torrentsTable.updateTable(full_update);
|
||||||
myTable.altRow();
|
torrentsTable.altRow();
|
||||||
if (response['server_state']) {
|
if (response['server_state']) {
|
||||||
var tmp = response['server_state'];
|
var tmp = response['server_state'];
|
||||||
for(var key in tmp)
|
for(var key in tmp)
|
||||||
@ -340,7 +343,7 @@ window.addEvent('load', function () {
|
|||||||
};
|
};
|
||||||
|
|
||||||
updateMainData = function() {
|
updateMainData = function() {
|
||||||
myTable.updateTable();
|
torrentsTable.updateTable();
|
||||||
clearTimeout(syncMainDataTimer);
|
clearTimeout(syncMainDataTimer);
|
||||||
syncMainDataTimer = syncMainData.delay(100);
|
syncMainDataTimer = syncMainData.delay(100);
|
||||||
}
|
}
|
||||||
@ -373,8 +376,8 @@ window.addEvent('load', function () {
|
|||||||
|
|
||||||
if (queueing_enabled != serverState.queueing) {
|
if (queueing_enabled != serverState.queueing) {
|
||||||
queueing_enabled = serverState.queueing;
|
queueing_enabled = serverState.queueing;
|
||||||
myTable.columns['priority'].force_hide = !queueing_enabled;
|
torrentsTable.columns['priority'].force_hide = !queueing_enabled;
|
||||||
myTable.updateColumn('priority');
|
torrentsTable.updateColumn('priority');
|
||||||
if (queueing_enabled) {
|
if (queueing_enabled) {
|
||||||
$('queueingLinks').removeClass('invisible');
|
$('queueingLinks').removeClass('invisible');
|
||||||
$('queueingButtons').removeClass('invisible');
|
$('queueingButtons').removeClass('invisible');
|
||||||
@ -424,11 +427,6 @@ window.addEvent('load', function () {
|
|||||||
$('DlInfos').addEvent('click', globalDownloadLimitFN);
|
$('DlInfos').addEvent('click', globalDownloadLimitFN);
|
||||||
$('UpInfos').addEvent('click', globalUploadLimitFN);
|
$('UpInfos').addEvent('click', globalUploadLimitFN);
|
||||||
|
|
||||||
setSortedColumn = function (column) {
|
|
||||||
myTable.setSortedColumn(column);
|
|
||||||
updateMainData();
|
|
||||||
};
|
|
||||||
|
|
||||||
$('showTopToolbarLink').addEvent('click', function(e) {
|
$('showTopToolbarLink').addEvent('click', function(e) {
|
||||||
showTopToolbar = !showTopToolbar;
|
showTopToolbar = !showTopToolbar;
|
||||||
localStorage.setItem('show_top_toolbar', showTopToolbar.toString());
|
localStorage.setItem('show_top_toolbar', showTopToolbar.toString());
|
||||||
@ -489,7 +487,7 @@ window.addEvent('load', function () {
|
|||||||
},
|
},
|
||||||
contentURL : 'properties_content.html',
|
contentURL : 'properties_content.html',
|
||||||
require : {
|
require : {
|
||||||
css : ['css/Tabs.css'],
|
css : ['css/Tabs.css', 'css/dynamicTable.css'],
|
||||||
js : ['scripts/prop-general.js', 'scripts/prop-trackers.js', 'scripts/prop-webseeds.js', 'scripts/prop-files.js'],
|
js : ['scripts/prop-general.js', 'scripts/prop-trackers.js', 'scripts/prop-webseeds.js', 'scripts/prop-files.js'],
|
||||||
},
|
},
|
||||||
tabsURL : 'properties.html',
|
tabsURL : 'properties.html',
|
||||||
@ -501,6 +499,8 @@ window.addEvent('load', function () {
|
|||||||
updateTorrentData();
|
updateTorrentData();
|
||||||
else if (!$('prop_trackers').hasClass('invisible'))
|
else if (!$('prop_trackers').hasClass('invisible'))
|
||||||
updateTrackersData();
|
updateTrackersData();
|
||||||
|
else if (!$('prop_peers').hasClass('invisible'))
|
||||||
|
updateTorrentPeersData();
|
||||||
else if (!$('prop_webseeds').hasClass('invisible'))
|
else if (!$('prop_webseeds').hasClass('invisible'))
|
||||||
updateWebSeedsData();
|
updateWebSeedsData();
|
||||||
else if (!$('prop_files').hasClass('invisible'))
|
else if (!$('prop_files').hasClass('invisible'))
|
||||||
@ -512,6 +512,7 @@ window.addEvent('load', function () {
|
|||||||
$('prop_trackers').addClass("invisible");
|
$('prop_trackers').addClass("invisible");
|
||||||
$('prop_webseeds').addClass("invisible");
|
$('prop_webseeds').addClass("invisible");
|
||||||
$('prop_files').addClass("invisible");
|
$('prop_files').addClass("invisible");
|
||||||
|
$('prop_peers').addClass("invisible");
|
||||||
updatePropertiesPanel();
|
updatePropertiesPanel();
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -520,6 +521,16 @@ window.addEvent('load', function () {
|
|||||||
$('prop_general').addClass("invisible");
|
$('prop_general').addClass("invisible");
|
||||||
$('prop_webseeds').addClass("invisible");
|
$('prop_webseeds').addClass("invisible");
|
||||||
$('prop_files').addClass("invisible");
|
$('prop_files').addClass("invisible");
|
||||||
|
$('prop_peers').addClass("invisible");
|
||||||
|
updatePropertiesPanel();
|
||||||
|
});
|
||||||
|
|
||||||
|
$('PropPeersLink').addEvent('click', function(e){
|
||||||
|
$('prop_peers').removeClass("invisible");
|
||||||
|
$('prop_trackers').addClass("invisible");
|
||||||
|
$('prop_general').addClass("invisible");
|
||||||
|
$('prop_webseeds').addClass("invisible");
|
||||||
|
$('prop_files').addClass("invisible");
|
||||||
updatePropertiesPanel();
|
updatePropertiesPanel();
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -528,6 +539,7 @@ window.addEvent('load', function () {
|
|||||||
$('prop_general').addClass("invisible");
|
$('prop_general').addClass("invisible");
|
||||||
$('prop_trackers').addClass("invisible");
|
$('prop_trackers').addClass("invisible");
|
||||||
$('prop_files').addClass("invisible");
|
$('prop_files').addClass("invisible");
|
||||||
|
$('prop_peers').addClass("invisible");
|
||||||
updatePropertiesPanel();
|
updatePropertiesPanel();
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -536,6 +548,7 @@ window.addEvent('load', function () {
|
|||||||
$('prop_general').addClass("invisible");
|
$('prop_general').addClass("invisible");
|
||||||
$('prop_trackers').addClass("invisible");
|
$('prop_trackers').addClass("invisible");
|
||||||
$('prop_webseeds').addClass("invisible");
|
$('prop_webseeds').addClass("invisible");
|
||||||
|
$('prop_peers').addClass("invisible");
|
||||||
updatePropertiesPanel();
|
updatePropertiesPanel();
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -556,7 +569,7 @@ var keyboardEvents = new Keyboard({
|
|||||||
defaultEventType: 'keydown',
|
defaultEventType: 'keydown',
|
||||||
events: {
|
events: {
|
||||||
'ctrl+a': function(event) {
|
'ctrl+a': function(event) {
|
||||||
myTable.selectAll();
|
torrentsTable.selectAll();
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
},
|
},
|
||||||
'delete': function(event) {
|
'delete': function(event) {
|
||||||
@ -567,3 +580,78 @@ var keyboardEvents = new Keyboard({
|
|||||||
});
|
});
|
||||||
|
|
||||||
keyboardEvents.activate();
|
keyboardEvents.activate();
|
||||||
|
|
||||||
|
var loadTorrentPeersTimer;
|
||||||
|
var syncTorrentPeersLastResponseId = 0;
|
||||||
|
var show_flags = true;
|
||||||
|
var loadTorrentPeersData = function(){
|
||||||
|
if ($('prop_peers').hasClass('invisible') ||
|
||||||
|
$('propertiesPanel_collapseToggle').hasClass('panel-expand')) {
|
||||||
|
syncTorrentPeersLastResponseId = 0;
|
||||||
|
torrentPeersTable.clear();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var current_hash = torrentsTable.getCurrentTorrentHash();
|
||||||
|
if (current_hash == "") {
|
||||||
|
syncTorrentPeersLastResponseId = 0;
|
||||||
|
torrentPeersTable.clear();
|
||||||
|
clearTimeout(loadTorrentPeersTimer);
|
||||||
|
loadTorrentPeersTimer = loadTorrentPeersData.delay(syncMainDataTimerPeriod);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var url = new URI('sync/torrent_peers');
|
||||||
|
url.setData('rid', syncTorrentPeersLastResponseId);
|
||||||
|
url.setData('hash', current_hash);
|
||||||
|
var request = new Request.JSON({
|
||||||
|
url: url,
|
||||||
|
noCache: true,
|
||||||
|
method: 'get',
|
||||||
|
onFailure: function() {
|
||||||
|
$('error_div').set('html', 'QBT_TR(qBittorrent client is not reachable)QBT_TR');
|
||||||
|
clearTimeout(loadTorrentPeersTimer);
|
||||||
|
loadTorrentPeersTimer = loadTorrentPeersData.delay(5000);
|
||||||
|
},
|
||||||
|
onSuccess: function(response) {
|
||||||
|
$('error_div').set('html', '');
|
||||||
|
if (response) {
|
||||||
|
var full_update = (response['full_update'] == true);
|
||||||
|
if (full_update) {
|
||||||
|
torrentPeersTable.clear();
|
||||||
|
}
|
||||||
|
if (response['rid']) {
|
||||||
|
syncTorrentPeersLastResponseId = response['rid'];
|
||||||
|
}
|
||||||
|
if (response['peers']) {
|
||||||
|
for (var key in response['peers']) {
|
||||||
|
response['peers'][key]['rowId'] = key;
|
||||||
|
torrentPeersTable.updateRowData(response['peers'][key]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (response['peers_removed'])
|
||||||
|
response['peers_removed'].each(function (hash) {
|
||||||
|
torrentPeersTable.removeRow(hash);
|
||||||
|
});
|
||||||
|
torrentPeersTable.updateTable(full_update);
|
||||||
|
torrentPeersTable.altRow();
|
||||||
|
|
||||||
|
if (response['show_flags']) {
|
||||||
|
if (show_flags != response['show_flags']) {
|
||||||
|
show_flags = response['show_flags'];
|
||||||
|
torrentPeersTable.columns['country'].force_hide = !show_flags;
|
||||||
|
torrentPeersTable.updateColumn('country');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
torrentPeersTable.clear();
|
||||||
|
}
|
||||||
|
clearTimeout(loadTorrentPeersTimer);
|
||||||
|
loadTorrentPeersTimer = loadTorrentPeersData.delay(syncMainDataTimerPeriod);
|
||||||
|
}
|
||||||
|
}).send();
|
||||||
|
};
|
||||||
|
|
||||||
|
updateTorrentPeersData = function(){
|
||||||
|
clearTimeout(loadTorrentPeersTimer);
|
||||||
|
loadTorrentPeersData();
|
||||||
|
};
|
@ -140,9 +140,9 @@ var ContextMenu = new Class({
|
|||||||
there_are_force_start = false;
|
there_are_force_start = false;
|
||||||
all_are_super_seeding = true;
|
all_are_super_seeding = true;
|
||||||
|
|
||||||
var h = myTable.selectedIds();
|
var h = torrentsTable.selectedRowsIds();
|
||||||
h.each(function(item, index){
|
h.each(function(item, index){
|
||||||
var data = myTable.rows.get(item).full_data;
|
var data = torrentsTable.rows.get(item).full_data;
|
||||||
|
|
||||||
if (data['seq_dl'] != true)
|
if (data['seq_dl'] != true)
|
||||||
all_are_seq_dl = false;
|
all_are_seq_dl = false;
|
||||||
|
@ -31,55 +31,35 @@
|
|||||||
|
|
||||||
**************************************************************/
|
**************************************************************/
|
||||||
|
|
||||||
var dynamicTable = new Class({
|
var DynamicTable = new Class({
|
||||||
|
|
||||||
initialize : function () {},
|
initialize : function () {},
|
||||||
|
|
||||||
setup : function (table, context_menu) {
|
setup : function (tableId, tableHeaderId, context_menu) {
|
||||||
this.table = $(table);
|
this.tableId = tableId;
|
||||||
|
this.tableHeaderId = tableHeaderId;
|
||||||
|
this.table = $(tableId);
|
||||||
this.rows = new Hash();
|
this.rows = new Hash();
|
||||||
this.cur = new Array();
|
this.cur = new Array();
|
||||||
this.columns = new Array();
|
this.columns = new Array();
|
||||||
this.context_menu = context_menu;
|
this.context_menu = context_menu;
|
||||||
this.sortedColumn = getLocalStorageItem('sorted_column', 'name');
|
this.sortedColumn = getLocalStorageItem('sorted_column_' + this.tableId, 0);
|
||||||
this.reverseSort = getLocalStorageItem('reverse_sort', '0');
|
this.reverseSort = getLocalStorageItem('reverse_sort_' + this.tableId, '0');
|
||||||
this.initColumns();
|
this.initColumns();
|
||||||
this.loadColumnsOrder();
|
this.loadColumnsOrder();
|
||||||
this.updateHeader();
|
this.updateHeader();
|
||||||
},
|
},
|
||||||
|
|
||||||
initColumns : function () {
|
initColumns : function () {},
|
||||||
this.newColumn('priority', 'width: 30px; cursor: pointer', '#');
|
|
||||||
this.newColumn('state_icon', 'width: 16px', '');
|
|
||||||
this.newColumn('name', 'min-width: 200px; cursor: pointer', 'QBT_TR(Name)QBT_TR');
|
|
||||||
this.newColumn('size', 'width: 100px; cursor: pointer', 'QBT_TR(Size)QBT_TR');
|
|
||||||
this.newColumn('progress', 'width: 80px; cursor: pointer', 'QBT_TR(Done)QBT_TR');
|
|
||||||
this.newColumn('num_seeds', 'width: 100px; cursor: pointer', 'QBT_TR(Seeds)QBT_TR');
|
|
||||||
this.newColumn('num_leechs', 'width: 100px; cursor: pointer', 'QBT_TR(Peers)QBT_TR');
|
|
||||||
this.newColumn('dlspeed', 'width: 100px; cursor: pointer', 'QBT_TR(Down Speed)QBT_TR');
|
|
||||||
this.newColumn('upspeed', 'width: 100px; cursor: pointer', 'QBT_TR(Up Speed)QBT_TR');
|
|
||||||
this.newColumn('eta', 'width: 100px; cursor: pointer', 'QBT_TR(ETA)QBT_TR');
|
|
||||||
this.newColumn('ratio', 'width: 100px; cursor: pointer', 'QBT_TR(Ratio)QBT_TR');
|
|
||||||
this.newColumn('label', 'width: 100px; cursor: pointer', 'QBT_TR(Label)QBT_TR');
|
|
||||||
|
|
||||||
this.columns['state_icon'].onclick = '';
|
|
||||||
this.columns['state_icon'].dataProperties[0] = 'state';
|
|
||||||
|
|
||||||
this.columns['num_seeds'].dataProperties.push('num_complete');
|
|
||||||
|
|
||||||
this.columns['num_leechs'].dataProperties.push('num_incomplete');
|
|
||||||
|
|
||||||
this.initColumnsFunctions();
|
|
||||||
},
|
|
||||||
|
|
||||||
newColumn : function (name, style, caption) {
|
newColumn : function (name, style, caption) {
|
||||||
var column = {};
|
var column = {};
|
||||||
column['name'] = name;
|
column['name'] = name;
|
||||||
column['visible'] = getLocalStorageItem('column_' + name + '_visible', '1');
|
column['visible'] = getLocalStorageItem('column_' + name + '_visible_' + this.tableId, '1');
|
||||||
column['force_hide'] = false;
|
column['force_hide'] = false;
|
||||||
column['caption'] = caption;
|
column['caption'] = caption;
|
||||||
column['style'] = style;
|
column['style'] = style;
|
||||||
column['onclick'] = 'setSortedColumn(\'' + name + '\');';
|
column['onclick'] = 'this._this.setSortedColumn(\'' + name + '\');';
|
||||||
column['dataProperties'] = [name];
|
column['dataProperties'] = [name];
|
||||||
column['getRowValue'] = function (row, pos) {
|
column['getRowValue'] = function (row, pos) {
|
||||||
if (pos == undefined)
|
if (pos == undefined)
|
||||||
@ -99,12 +79,12 @@ var dynamicTable = new Class({
|
|||||||
this.columns.push(column);
|
this.columns.push(column);
|
||||||
this.columns[name] = column;
|
this.columns[name] = column;
|
||||||
|
|
||||||
$('torrentTableHeader').appendChild(new Element('th'));
|
$(this.tableHeaderId).appendChild(new Element('th'));
|
||||||
},
|
},
|
||||||
|
|
||||||
loadColumnsOrder : function () {
|
loadColumnsOrder : function () {
|
||||||
columnsOrder = ['state_icon']; // status icon column is always the first
|
columnsOrder = ['state_icon']; // status icon column is always the first
|
||||||
val = localStorage.getItem('columns_order');
|
val = localStorage.getItem('columns_order_' + this.tableId);
|
||||||
if (val === null || val === undefined) return;
|
if (val === null || val === undefined) return;
|
||||||
val.split(',').forEach(function(v) {
|
val.split(',').forEach(function(v) {
|
||||||
if ((v in this.columns) && (!columnsOrder.contains(v)))
|
if ((v in this.columns) && (!columnsOrder.contains(v)))
|
||||||
@ -126,14 +106,15 @@ var dynamicTable = new Class({
|
|||||||
val += ',';
|
val += ',';
|
||||||
val += this.columns[i].name;
|
val += this.columns[i].name;
|
||||||
}
|
}
|
||||||
localStorage.setItem('columns_order', val);
|
localStorage.setItem('columns_order_' + this.tableId, val);
|
||||||
},
|
},
|
||||||
|
|
||||||
updateHeader : function () {
|
updateHeader : function () {
|
||||||
ths = $('torrentTableHeader').getElements('th');
|
var ths = $(this.tableHeaderId).getElements('th');
|
||||||
|
|
||||||
for (var i = 0; i < ths.length; i++) {
|
for (var i = 0; i < ths.length; i++) {
|
||||||
th = ths[i];
|
th = ths[i];
|
||||||
|
th._this = this;
|
||||||
th.setAttribute('onclick', this.columns[i].onclick);
|
th.setAttribute('onclick', this.columns[i].onclick);
|
||||||
th.innerHTML = this.columns[i].caption;
|
th.innerHTML = this.columns[i].caption;
|
||||||
th.setAttribute('style', this.columns[i].style);
|
th.setAttribute('style', this.columns[i].style);
|
||||||
@ -154,7 +135,7 @@ var dynamicTable = new Class({
|
|||||||
updateColumn : function (columnName) {
|
updateColumn : function (columnName) {
|
||||||
var pos = this.getColumnPos(columnName);
|
var pos = this.getColumnPos(columnName);
|
||||||
var visible = ((this.columns[pos].visible != '0') && !this.columns[pos].force_hide);
|
var visible = ((this.columns[pos].visible != '0') && !this.columns[pos].force_hide);
|
||||||
var ths = $('torrentTableHeader').getElements('th');
|
var ths = $(this.tableHeaderId).getElements('th');
|
||||||
if (visible)
|
if (visible)
|
||||||
ths[pos].removeClass('invisible');
|
ths[pos].removeClass('invisible');
|
||||||
else
|
else
|
||||||
@ -176,11 +157,12 @@ var dynamicTable = new Class({
|
|||||||
// Toggle sort order
|
// Toggle sort order
|
||||||
this.reverseSort = this.reverseSort == '0' ? '1' : '0';
|
this.reverseSort = this.reverseSort == '0' ? '1' : '0';
|
||||||
}
|
}
|
||||||
localStorage.setItem('sorted_column', column);
|
localStorage.setItem('sorted_column_' + this.tableId, column);
|
||||||
localStorage.setItem('reverse_sort', this.reverseSort);
|
localStorage.setItem('reverse_sort_' + this.tableId, this.reverseSort);
|
||||||
|
this.updateTable(false);
|
||||||
},
|
},
|
||||||
|
|
||||||
getCurrentTorrentHash : function () {
|
getSelectedRowId : function () {
|
||||||
if (this.cur.length > 0)
|
if (this.cur.length > 0)
|
||||||
return this.cur[0];
|
return this.cur[0];
|
||||||
return '';
|
return '';
|
||||||
@ -206,19 +188,19 @@ var dynamicTable = new Class({
|
|||||||
var trs = this.table.getElements('tr');
|
var trs = this.table.getElements('tr');
|
||||||
for (var i = 0; i < trs.length; i++) {
|
for (var i = 0; i < trs.length; i++) {
|
||||||
var tr = trs[i];
|
var tr = trs[i];
|
||||||
this.cur.push(tr.hash);
|
this.cur.push(tr.rowId);
|
||||||
if (!tr.hasClass('selected'))
|
if (!tr.hasClass('selected'))
|
||||||
tr.addClass('selected');
|
tr.addClass('selected');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
selectRow : function (hash) {
|
selectRow : function (rowId) {
|
||||||
this.cur.empty();
|
this.cur.empty();
|
||||||
this.cur.push(hash);
|
this.cur.push(rowId);
|
||||||
var trs = this.table.getElements('tr');
|
var trs = this.table.getElements('tr');
|
||||||
for (var i = 0; i < trs.length; i++) {
|
for (var i = 0; i < trs.length; i++) {
|
||||||
var tr = trs[i];
|
var tr = trs[i];
|
||||||
if (tr.hash == hash) {
|
if (tr.rowId == rowId) {
|
||||||
if (!tr.hasClass('selected'))
|
if (!tr.hasClass('selected'))
|
||||||
tr.addClass('selected');
|
tr.addClass('selected');
|
||||||
}
|
}
|
||||||
@ -229,17 +211,17 @@ var dynamicTable = new Class({
|
|||||||
},
|
},
|
||||||
|
|
||||||
updateRowData : function (data) {
|
updateRowData : function (data) {
|
||||||
var hash = data['hash'];
|
var rowId = data['rowId'];
|
||||||
var row;
|
var row;
|
||||||
|
|
||||||
if (!this.rows.has(hash)) {
|
if (!this.rows.has(rowId)) {
|
||||||
row = {};
|
row = {};
|
||||||
this.rows.set(hash, row);
|
this.rows.set(rowId, row);
|
||||||
row['full_data'] = {};
|
row['full_data'] = {};
|
||||||
row['hash'] = hash;
|
row['rowId'] = rowId;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
row = this.rows.get(hash);
|
row = this.rows.get(rowId);
|
||||||
|
|
||||||
row['data'] = data;
|
row['data'] = data;
|
||||||
|
|
||||||
@ -247,64 +229,19 @@ var dynamicTable = new Class({
|
|||||||
row['full_data'][x] = data[x];
|
row['full_data'][x] = data[x];
|
||||||
},
|
},
|
||||||
|
|
||||||
applyFilter : function (row, filterName, labelName) {
|
|
||||||
var state = row['full_data'].state;
|
|
||||||
switch(filterName) {
|
|
||||||
case 'downloading':
|
|
||||||
if (state != 'downloading' && !~state.indexOf('DL'))
|
|
||||||
return false;
|
|
||||||
break;
|
|
||||||
case 'seeding':
|
|
||||||
if (state != 'uploading' && state != 'forcedUP' && state != 'stalledUP' && state != 'queuedUP' && state != 'checkingUP')
|
|
||||||
return false;
|
|
||||||
break;
|
|
||||||
case 'completed':
|
|
||||||
if (state != 'uploading' && !~state.indexOf('UP'))
|
|
||||||
return false;
|
|
||||||
break;
|
|
||||||
case 'paused':
|
|
||||||
if (state != 'pausedDL')
|
|
||||||
return false;
|
|
||||||
break;
|
|
||||||
case 'resumed':
|
|
||||||
if (~state.indexOf('paused'))
|
|
||||||
return false;
|
|
||||||
break;
|
|
||||||
case 'active':
|
|
||||||
if (state != 'downloading' && state != 'forcedDL' && state != 'uploading' && state != 'forcedUP')
|
|
||||||
return false;
|
|
||||||
break;
|
|
||||||
case 'inactive':
|
|
||||||
if (state == 'downloading' || state == 'forcedDL' || state == 'uploading' || state == 'forcedUP')
|
|
||||||
return false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (labelName == LABELS_ALL)
|
|
||||||
return true;
|
|
||||||
|
|
||||||
if (labelName == LABELS_UNLABELLED && row['full_data'].label.length === 0)
|
|
||||||
return true;
|
|
||||||
|
|
||||||
if (labelName != genHash( row['full_data'].label) )
|
|
||||||
return false;
|
|
||||||
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
|
|
||||||
getFilteredAndSortedRows : function () {
|
getFilteredAndSortedRows : function () {
|
||||||
var filteredRows = new Array();
|
var filteredRows = new Array();
|
||||||
|
|
||||||
var rows = this.rows.getValues();
|
var rows = this.rows.getValues();
|
||||||
|
|
||||||
for (i = 0; i < rows.length; i++)
|
for (i = 0; i < rows.length; i++)
|
||||||
if (this.applyFilter(rows[i], selected_filter, selected_label)) {
|
{
|
||||||
filteredRows.push(rows[i]);
|
filteredRows.push(rows[i]);
|
||||||
filteredRows[rows[i].hash] = rows[i];
|
filteredRows[rows[i].rowId] = rows[i];
|
||||||
}
|
}
|
||||||
|
|
||||||
filteredRows.sort(function (row1, row2) {
|
filteredRows.sort(function (row1, row2) {
|
||||||
column = this.columns[this.sortedColumn];
|
var column = this.columns[this.sortedColumn];
|
||||||
res = column.compareRows(row1, row2);
|
res = column.compareRows(row1, row2);
|
||||||
if (this.reverseSort == '0')
|
if (this.reverseSort == '0')
|
||||||
return res;
|
return res;
|
||||||
@ -314,10 +251,10 @@ var dynamicTable = new Class({
|
|||||||
return filteredRows;
|
return filteredRows;
|
||||||
},
|
},
|
||||||
|
|
||||||
getTrByHash : function (hash) {
|
getTrByRowId : function (rowId) {
|
||||||
trs = this.table.getElements('tr');
|
trs = this.table.getElements('tr');
|
||||||
for (var i = 0; i < trs.length; i++)
|
for (var i = 0; i < trs.length; i++)
|
||||||
if (trs[i].hash == hash)
|
if (trs[i].rowId == rowId)
|
||||||
return trs[i];
|
return trs[i];
|
||||||
return null;
|
return null;
|
||||||
},
|
},
|
||||||
@ -337,10 +274,10 @@ var dynamicTable = new Class({
|
|||||||
var trs = this.table.getElements('tr');
|
var trs = this.table.getElements('tr');
|
||||||
|
|
||||||
for (var rowPos = 0; rowPos < rows.length; rowPos++) {
|
for (var rowPos = 0; rowPos < rows.length; rowPos++) {
|
||||||
var hash = rows[rowPos]['hash'];
|
var rowId = rows[rowPos]['rowId'];
|
||||||
tr_found = false;
|
tr_found = false;
|
||||||
for (j = rowPos; j < trs.length; j++)
|
for (j = rowPos; j < trs.length; j++)
|
||||||
if (trs[j]['hash'] == hash) {
|
if (trs[j]['rowId'] == rowId) {
|
||||||
trs[rowPos].removeClass('over');
|
trs[rowPos].removeClass('over');
|
||||||
tr_found = true;
|
tr_found = true;
|
||||||
if (rowPos == j)
|
if (rowPos == j)
|
||||||
@ -357,59 +294,49 @@ var dynamicTable = new Class({
|
|||||||
var tr = new Element('tr');
|
var tr = new Element('tr');
|
||||||
|
|
||||||
tr.addClass("menu-target");
|
tr.addClass("menu-target");
|
||||||
tr['hash'] = rows[rowPos]['hash'];
|
tr['rowId'] = rows[rowPos]['rowId'];
|
||||||
|
|
||||||
|
tr._this = this;
|
||||||
tr.addEvent('contextmenu', function (e) {
|
tr.addEvent('contextmenu', function (e) {
|
||||||
if (!myTable.cur.contains(this.hash))
|
if (!this._this.cur.contains(this.rowId))
|
||||||
myTable.selectRow(this.hash);
|
this._this.selectRow(this.rowId);
|
||||||
return true;
|
|
||||||
});
|
|
||||||
tr.addEvent('dblclick', function (e) {
|
|
||||||
e.stop();
|
|
||||||
myTable.selectRow(this.hash);
|
|
||||||
var row = myTable.rows.get(this.hash);
|
|
||||||
var state = row['full_data'].state;
|
|
||||||
if (~state.indexOf('paused'))
|
|
||||||
startFN();
|
|
||||||
else
|
|
||||||
pauseFN();
|
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
tr.addEvent('click', function (e) {
|
tr.addEvent('click', function (e) {
|
||||||
e.stop();
|
e.stop();
|
||||||
if (e.control) {
|
if (e.control) {
|
||||||
// CTRL key was pressed
|
// CTRL key was pressed
|
||||||
if (myTable.cur.contains(this.hash)) {
|
if (this._this.cur.contains(this.rowId)) {
|
||||||
// remove it
|
// remove it
|
||||||
myTable.cur.erase(this.hash);
|
this._this.cur.erase(this.rowId);
|
||||||
// Remove selected style
|
// Remove selected style
|
||||||
this.removeClass('selected');
|
this.removeClass('selected');
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
myTable.cur.push(this.hash);
|
this._this.cur.push(this.rowId);
|
||||||
// Add selected style
|
// Add selected style
|
||||||
this.addClass('selected');
|
this.addClass('selected');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
if (e.shift && myTable.cur.length == 1) {
|
if (e.shift && this._this.cur.length == 1) {
|
||||||
// Shift key was pressed
|
// Shift key was pressed
|
||||||
var first_row_hash = myTable.cur[0];
|
var first_row_id = this._this.cur[0];
|
||||||
var last_row_hash = this.hash;
|
var last_row_id = this.rowId;
|
||||||
myTable.cur.empty();
|
this._this.cur.empty();
|
||||||
var trs = myTable.table.getElements('tr');
|
var trs = this._this.table.getElements('tr');
|
||||||
var select = false;
|
var select = false;
|
||||||
for (var i = 0; i < trs.length; i++) {
|
for (var i = 0; i < trs.length; i++) {
|
||||||
var tr = trs[i];
|
var tr = trs[i];
|
||||||
|
|
||||||
if ((tr.hash == first_row_hash) || (tr.hash == last_row_hash)) {
|
if ((tr.rowId == first_row_id) || (tr.rowId == last_row_id)) {
|
||||||
myTable.cur.push(tr.hash);
|
this._this.cur.push(tr.rowId);
|
||||||
tr.addClass('selected');
|
tr.addClass('selected');
|
||||||
select = !select;
|
select = !select;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
if (select) {
|
if (select) {
|
||||||
myTable.cur.push(tr.hash);
|
this._this.cur.push(tr.rowId);
|
||||||
tr.addClass('selected');
|
tr.addClass('selected');
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@ -418,13 +345,15 @@ var dynamicTable = new Class({
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Simple selection
|
// Simple selection
|
||||||
myTable.selectRow(this.hash);
|
this._this.selectRow(this.rowId);
|
||||||
updatePropertiesPanel();
|
updatePropertiesPanel();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.setupTrEvents(tr);
|
||||||
|
|
||||||
for (var j = 0 ; j < this.columns.length; j++) {
|
for (var j = 0 ; j < this.columns.length; j++) {
|
||||||
var td = new Element('td');
|
var td = new Element('td');
|
||||||
if ((this.columns[j].visible == '0') || this.columns[j].force_hide)
|
if ((this.columns[j].visible == '0') || this.columns[j].force_hide)
|
||||||
@ -443,7 +372,8 @@ var dynamicTable = new Class({
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Update context menu
|
// Update context menu
|
||||||
this.context_menu.addTarget(tr);
|
if (this.context_menu)
|
||||||
|
this.context_menu.addTarget(tr);
|
||||||
|
|
||||||
this.updateRow(tr, true);
|
this.updateRow(tr, true);
|
||||||
}
|
}
|
||||||
@ -457,8 +387,10 @@ var dynamicTable = new Class({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
setupTrEvents : function (tr) {},
|
||||||
|
|
||||||
updateRow : function (tr, fullUpdate) {
|
updateRow : function (tr, fullUpdate) {
|
||||||
var row = this.rows.get(tr.hash);
|
var row = this.rows.get(tr.rowId);
|
||||||
data = row[fullUpdate ? 'full_data' : 'data'];
|
data = row[fullUpdate ? 'full_data' : 'data'];
|
||||||
|
|
||||||
tds = tr.getElements('td');
|
tds = tr.getElements('td');
|
||||||
@ -467,43 +399,64 @@ var dynamicTable = new Class({
|
|||||||
this.columns[i].updateTd(tds[i], row);
|
this.columns[i].updateTd(tds[i], row);
|
||||||
}
|
}
|
||||||
row['data'] = {};
|
row['data'] = {};
|
||||||
|
|
||||||
/*
|
|
||||||
for(var prop in data)
|
|
||||||
for (var i = 0; i < this.columns.length; i++)
|
|
||||||
for (var j = 0; j < this.columns[i].dataProperties.length; j++)
|
|
||||||
if (this.columns[i].dataProperties[j] == prop)
|
|
||||||
this.columns[i].updateTd(tds[i], row);
|
|
||||||
|
|
||||||
if (this.cur.contains(tr.hash)) {
|
|
||||||
if (!tr.hasClass('selected'))
|
|
||||||
tr.addClass('selected');
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
if (tr.hasClass('selected'))
|
|
||||||
tr.removeClass('selected');
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
},
|
},
|
||||||
|
|
||||||
removeRow : function (hash) {
|
removeRow : function (rowId) {
|
||||||
this.cur.erase(hash);
|
this.cur.erase(rowId);
|
||||||
var tr = this.getTrByHash(hash);
|
var tr = this.getTrByRowId(rowId);
|
||||||
if (tr != null) {
|
if (tr != null) {
|
||||||
tr.dispose();
|
tr.dispose();
|
||||||
this.rows.erase(hash);
|
this.rows.erase(rowId);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
},
|
},
|
||||||
|
|
||||||
selectedIds : function () {
|
clear : function () {
|
||||||
|
this.cur.empty();
|
||||||
|
this.rows.empty();
|
||||||
|
var trs = this.table.getElements('tr');
|
||||||
|
while (trs.length > 0) {
|
||||||
|
trs[trs.length - 1].dispose();
|
||||||
|
trs.pop();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
selectedRowsIds : function () {
|
||||||
return this.cur;
|
return this.cur;
|
||||||
},
|
},
|
||||||
|
|
||||||
getRowIds : function () {
|
getRowIds : function () {
|
||||||
return this.rows.getKeys();
|
return this.rows.getKeys();
|
||||||
},
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
var TorrentsTable = new Class({
|
||||||
|
Extends: DynamicTable,
|
||||||
|
|
||||||
|
initColumns : function () {
|
||||||
|
this.newColumn('priority', 'width: 30px; cursor: pointer', '#');
|
||||||
|
this.newColumn('state_icon', 'width: 16px', '');
|
||||||
|
this.newColumn('name', 'min-width: 200px; cursor: pointer', 'QBT_TR(Name)QBT_TR');
|
||||||
|
this.newColumn('size', 'width: 100px; cursor: pointer', 'QBT_TR(Size)QBT_TR');
|
||||||
|
this.newColumn('progress', 'width: 80px; cursor: pointer', 'QBT_TR(Done)QBT_TR');
|
||||||
|
this.newColumn('num_seeds', 'width: 100px; cursor: pointer', 'QBT_TR(Seeds)QBT_TR');
|
||||||
|
this.newColumn('num_leechs', 'width: 100px; cursor: pointer', 'QBT_TR(Peers)QBT_TR');
|
||||||
|
this.newColumn('dlspeed', 'width: 100px; cursor: pointer', 'QBT_TR(Down Speed)QBT_TR');
|
||||||
|
this.newColumn('upspeed', 'width: 100px; cursor: pointer', 'QBT_TR(Up Speed)QBT_TR');
|
||||||
|
this.newColumn('eta', 'width: 100px; cursor: pointer', 'QBT_TR(ETA)QBT_TR');
|
||||||
|
this.newColumn('ratio', 'width: 100px; cursor: pointer', 'QBT_TR(Ratio)QBT_TR');
|
||||||
|
this.newColumn('label', 'width: 100px; cursor: pointer', 'QBT_TR(Label)QBT_TR');
|
||||||
|
|
||||||
|
this.columns['state_icon'].onclick = '';
|
||||||
|
this.columns['state_icon'].dataProperties[0] = 'state';
|
||||||
|
|
||||||
|
this.columns['num_seeds'].dataProperties.push('num_complete');
|
||||||
|
|
||||||
|
this.columns['num_leechs'].dataProperties.push('num_incomplete');
|
||||||
|
|
||||||
|
this.initColumnsFunctions();
|
||||||
|
},
|
||||||
|
|
||||||
initColumnsFunctions : function () {
|
initColumnsFunctions : function () {
|
||||||
|
|
||||||
@ -659,10 +612,205 @@ var dynamicTable = new Class({
|
|||||||
html = (Math.floor(100 * ratio) / 100).toFixed(2); //Don't round up
|
html = (Math.floor(100 * ratio) / 100).toFixed(2); //Don't round up
|
||||||
td.set('html', html);
|
td.set('html', html);
|
||||||
};
|
};
|
||||||
}
|
},
|
||||||
|
|
||||||
|
applyFilter : function (row, filterName, labelName) {
|
||||||
|
var state = row['full_data'].state;
|
||||||
|
switch(filterName) {
|
||||||
|
case 'downloading':
|
||||||
|
if (state != 'downloading' && !~state.indexOf('DL'))
|
||||||
|
return false;
|
||||||
|
break;
|
||||||
|
case 'seeding':
|
||||||
|
if (state != 'uploading' && state != 'forcedUP' && state != 'stalledUP' && state != 'queuedUP' && state != 'checkingUP')
|
||||||
|
return false;
|
||||||
|
break;
|
||||||
|
case 'completed':
|
||||||
|
if (state != 'uploading' && !~state.indexOf('UP'))
|
||||||
|
return false;
|
||||||
|
break;
|
||||||
|
case 'paused':
|
||||||
|
if (state != 'pausedDL')
|
||||||
|
return false;
|
||||||
|
break;
|
||||||
|
case 'resumed':
|
||||||
|
if (~state.indexOf('paused'))
|
||||||
|
return false;
|
||||||
|
break;
|
||||||
|
case 'active':
|
||||||
|
if (state != 'downloading' && state != 'forcedDL' && state != 'uploading' && state != 'forcedUP')
|
||||||
|
return false;
|
||||||
|
break;
|
||||||
|
case 'inactive':
|
||||||
|
if (state == 'downloading' || state == 'forcedDL' || state == 'uploading' || state == 'forcedUP')
|
||||||
|
return false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (labelName == LABELS_ALL)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
if (labelName == LABELS_UNLABELLED && row['full_data'].label.length === 0)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
if (labelName != genHash( row['full_data'].label) )
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
|
||||||
|
getFilteredAndSortedRows : function () {
|
||||||
|
var filteredRows = new Array();
|
||||||
|
|
||||||
|
var rows = this.rows.getValues();
|
||||||
|
|
||||||
|
for (i = 0; i < rows.length; i++)
|
||||||
|
if (this.applyFilter(rows[i], selected_filter, selected_label)) {
|
||||||
|
filteredRows.push(rows[i]);
|
||||||
|
filteredRows[rows[i].rowId] = rows[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
filteredRows.sort(function (row1, row2) {
|
||||||
|
var column = this.columns[this.sortedColumn];
|
||||||
|
res = column.compareRows(row1, row2);
|
||||||
|
if (this.reverseSort == '0')
|
||||||
|
return res;
|
||||||
|
else
|
||||||
|
return -res;
|
||||||
|
}.bind(this));
|
||||||
|
return filteredRows;
|
||||||
|
},
|
||||||
|
|
||||||
|
setupTrEvents : function (tr) {
|
||||||
|
tr.addEvent('dblclick', function (e) {
|
||||||
|
e.stop();
|
||||||
|
this._this.selectRow(this.rowId);
|
||||||
|
var row = this._this.rows.get(this.rowId);
|
||||||
|
var state = row['full_data'].state;
|
||||||
|
if (~state.indexOf('paused'))
|
||||||
|
startFN();
|
||||||
|
else
|
||||||
|
pauseFN();
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
getCurrentTorrentHash : function () {
|
||||||
|
return this.getSelectedRowId();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
var TorrentPeersTable = new Class({
|
||||||
|
Extends: DynamicTable,
|
||||||
|
|
||||||
|
initColumns : function () {
|
||||||
|
this.newColumn('country', 'width: 4px', '');
|
||||||
|
this.newColumn('ip', 'width: 80px', 'QBT_TR(IP)QBT_TR');
|
||||||
|
this.newColumn('port', 'width: 35px', 'QBT_TR(Port)QBT_TR');
|
||||||
|
this.newColumn('client', 'width: 110px', 'QBT_TR(Client)QBT_TR');
|
||||||
|
this.newColumn('progress', 'width: 30px', 'QBT_TR(Progress)QBT_TR');
|
||||||
|
this.newColumn('dl_speed', 'width: 30px', 'QBT_TR(Down Speed)QBT_TR');
|
||||||
|
this.newColumn('up_speed', 'width: 30px', 'QBT_TR(Up Speed)QBT_TR');
|
||||||
|
this.newColumn('downloaded', 'width: 30px', 'QBT_TR(Downloaded)QBT_TR');
|
||||||
|
this.newColumn('uploaded', 'width: 30px', 'QBT_TR(Uploaded)QBT_TR');
|
||||||
|
this.newColumn('connection', 'width: 30px', 'QBT_TR(Connection)QBT_TR');
|
||||||
|
this.newColumn('flags', 'width: 30px', 'QBT_TR(Flags)QBT_TR');
|
||||||
|
this.newColumn('relevance', 'min-width: 30px', 'QBT_TR(Relevance)QBT_TR');
|
||||||
|
|
||||||
|
this.columns['country'].dataProperties.push('country_code');
|
||||||
|
this.columns['flags'].dataProperties.push('flags_desc');
|
||||||
|
this.initColumnsFunctions();
|
||||||
|
},
|
||||||
|
|
||||||
|
initColumnsFunctions : function () {
|
||||||
|
|
||||||
|
// country
|
||||||
|
|
||||||
|
this.columns['country'].updateTd = function (td, row) {
|
||||||
|
var country = this.getRowValue(row, 0);
|
||||||
|
var country_code = this.getRowValue(row, 1);
|
||||||
|
|
||||||
|
if (!country_code) {
|
||||||
|
if (td.getChildren('img').length)
|
||||||
|
td.getChildren('img')[0].dispose();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var img_path = 'images/flags/' + country_code + '.png';
|
||||||
|
|
||||||
|
if (td.getChildren('img').length) {
|
||||||
|
var img = td.getChildren('img')[0];
|
||||||
|
img.set('src', img_path);
|
||||||
|
img.set('alt', country);
|
||||||
|
img.set('title', country);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
td.adopt(new Element('img', {
|
||||||
|
'src' : img_path,
|
||||||
|
'alt' : country,
|
||||||
|
'title' : country
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
// ip
|
||||||
|
|
||||||
|
this.columns['ip'].compareRows = function (row1, row2) {
|
||||||
|
var ip1 = this.getRowValue(row1);
|
||||||
|
var ip2 = this.getRowValue(row2);
|
||||||
|
|
||||||
|
var a = ip1.split(".");
|
||||||
|
var b = ip2.split(".");
|
||||||
|
|
||||||
|
for (var i = 0; i < 4; i++){
|
||||||
|
if (a[i] != b[i])
|
||||||
|
return a[i] - b[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
// progress, relevance
|
||||||
|
|
||||||
|
this.columns['progress'].updateTd = function (td, row) {
|
||||||
|
var progress = this.getRowValue(row);
|
||||||
|
var progressFormated = (progress * 100).round(1);
|
||||||
|
if (progressFormated == 100.0 && progress != 1.0)
|
||||||
|
progressFormated = 99.9;
|
||||||
|
progressFormated += "%";
|
||||||
|
td.set('html', progressFormated);
|
||||||
|
};
|
||||||
|
|
||||||
|
this.columns['relevance'].updateTd = this.columns['progress'].updateTd;
|
||||||
|
|
||||||
|
// dl_speed, up_speed
|
||||||
|
|
||||||
|
this.columns['dl_speed'].updateTd = function (td, row) {
|
||||||
|
var speed = this.getRowValue(row);
|
||||||
|
if (speed == 0)
|
||||||
|
td.set('html', '');
|
||||||
|
else
|
||||||
|
td.set('html', friendlyUnit(speed, true));
|
||||||
|
};
|
||||||
|
|
||||||
|
this.columns['up_speed'].updateTd = this.columns['dl_speed'].updateTd;
|
||||||
|
|
||||||
|
// downloaded, uploaded
|
||||||
|
|
||||||
|
this.columns['downloaded'].updateTd = function (td, row) {
|
||||||
|
var downloaded = this.getRowValue(row);
|
||||||
|
td.set('html', friendlyUnit(downloaded, false));
|
||||||
|
};
|
||||||
|
|
||||||
|
this.columns['uploaded'].updateTd = this.columns['downloaded'].updateTd;
|
||||||
|
|
||||||
|
// flags
|
||||||
|
|
||||||
|
this.columns['flags'].updateTd = function (td, row) {
|
||||||
|
td.innerHTML = this.getRowValue(row, 0);
|
||||||
|
td.title = this.getRowValue(row, 1);
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
});
|
});
|
||||||
//dynamicTable.implement(new Options);
|
|
||||||
//dynamicTable.implement(new Events);
|
|
||||||
|
|
||||||
/*************************************************************/
|
/*************************************************************/
|
||||||
|
@ -119,7 +119,7 @@ initializeWindows = function() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
uploadLimitFN = function() {
|
uploadLimitFN = function() {
|
||||||
var h = myTable.selectedIds();
|
var h = torrentsTable.selectedRowsIds();
|
||||||
if (h.length) {
|
if (h.length) {
|
||||||
var hash = h[0];
|
var hash = h[0];
|
||||||
new MochaUI.Window({
|
new MochaUI.Window({
|
||||||
@ -139,7 +139,7 @@ initializeWindows = function() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
toggleSequentialDownloadFN = function() {
|
toggleSequentialDownloadFN = function() {
|
||||||
var h = myTable.selectedIds();
|
var h = torrentsTable.selectedRowsIds();
|
||||||
if (h.length) {
|
if (h.length) {
|
||||||
new Request({
|
new Request({
|
||||||
url: 'command/toggleSequentialDownload',
|
url: 'command/toggleSequentialDownload',
|
||||||
@ -153,7 +153,7 @@ initializeWindows = function() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
toggleFirstLastPiecePrioFN = function() {
|
toggleFirstLastPiecePrioFN = function() {
|
||||||
var h = myTable.selectedIds();
|
var h = torrentsTable.selectedRowsIds();
|
||||||
if (h.length) {
|
if (h.length) {
|
||||||
new Request({
|
new Request({
|
||||||
url: 'command/toggleFirstLastPiecePrio',
|
url: 'command/toggleFirstLastPiecePrio',
|
||||||
@ -167,7 +167,7 @@ initializeWindows = function() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
setSuperSeedingFN = function(val) {
|
setSuperSeedingFN = function(val) {
|
||||||
var h = myTable.selectedIds();
|
var h = torrentsTable.selectedRowsIds();
|
||||||
if (h.length) {
|
if (h.length) {
|
||||||
new Request({
|
new Request({
|
||||||
url: 'command/setSuperSeeding',
|
url: 'command/setSuperSeeding',
|
||||||
@ -182,7 +182,7 @@ initializeWindows = function() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
setForceStartFN = function() {
|
setForceStartFN = function() {
|
||||||
var h = myTable.selectedIds();
|
var h = torrentsTable.selectedRowsIds();
|
||||||
if (h.length) {
|
if (h.length) {
|
||||||
new Request({
|
new Request({
|
||||||
url: 'command/setForceStart',
|
url: 'command/setForceStart',
|
||||||
@ -213,7 +213,7 @@ initializeWindows = function() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
downloadLimitFN = function() {
|
downloadLimitFN = function() {
|
||||||
var h = myTable.selectedIds();
|
var h = torrentsTable.selectedRowsIds();
|
||||||
if (h.length) {
|
if (h.length) {
|
||||||
var hash = h[0];
|
var hash = h[0];
|
||||||
new MochaUI.Window({
|
new MochaUI.Window({
|
||||||
@ -233,7 +233,7 @@ initializeWindows = function() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
deleteFN = function() {
|
deleteFN = function() {
|
||||||
var h = myTable.selectedIds();
|
var h = torrentsTable.selectedRowsIds();
|
||||||
if (h.length) {
|
if (h.length) {
|
||||||
new MochaUI.Window({
|
new MochaUI.Window({
|
||||||
id: 'confirmDeletionPage',
|
id: 'confirmDeletionPage',
|
||||||
@ -257,7 +257,7 @@ initializeWindows = function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
pauseFN = function() {
|
pauseFN = function() {
|
||||||
var h = myTable.selectedIds();
|
var h = torrentsTable.selectedRowsIds();
|
||||||
if (h.length) {
|
if (h.length) {
|
||||||
h.each(function(hash, index) {
|
h.each(function(hash, index) {
|
||||||
new Request({
|
new Request({
|
||||||
@ -273,7 +273,7 @@ initializeWindows = function() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
startFN = function() {
|
startFN = function() {
|
||||||
var h = myTable.selectedIds();
|
var h = torrentsTable.selectedRowsIds();
|
||||||
if (h.length) {
|
if (h.length) {
|
||||||
h.each(function(hash, index) {
|
h.each(function(hash, index) {
|
||||||
new Request({
|
new Request({
|
||||||
@ -289,7 +289,7 @@ initializeWindows = function() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
recheckFN = function() {
|
recheckFN = function() {
|
||||||
var h = myTable.selectedIds();
|
var h = torrentsTable.selectedRowsIds();
|
||||||
if (h.length) {
|
if (h.length) {
|
||||||
h.each(function(hash, index) {
|
h.each(function(hash, index) {
|
||||||
new Request({
|
new Request({
|
||||||
@ -305,7 +305,7 @@ initializeWindows = function() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
newLabelFN = function () {
|
newLabelFN = function () {
|
||||||
var h = myTable.selectedIds();
|
var h = torrentsTable.selectedRowsIds();
|
||||||
if (h.length) {
|
if (h.length) {
|
||||||
new MochaUI.Window({
|
new MochaUI.Window({
|
||||||
id: 'newLabelPage',
|
id: 'newLabelPage',
|
||||||
@ -327,7 +327,7 @@ initializeWindows = function() {
|
|||||||
var labelName = '';
|
var labelName = '';
|
||||||
if (labelHash != 0)
|
if (labelHash != 0)
|
||||||
var labelName = label_list[labelHash].name;
|
var labelName = label_list[labelHash].name;
|
||||||
var h = myTable.selectedIds();
|
var h = torrentsTable.selectedRowsIds();
|
||||||
if (h.length) {
|
if (h.length) {
|
||||||
new Request({
|
new Request({
|
||||||
url: 'command/setLabel',
|
url: 'command/setLabel',
|
||||||
@ -353,7 +353,7 @@ initializeWindows = function() {
|
|||||||
['pause', 'resume', 'recheck'].each(function(item) {
|
['pause', 'resume', 'recheck'].each(function(item) {
|
||||||
addClickEvent(item, function(e) {
|
addClickEvent(item, function(e) {
|
||||||
new Event(e).stop();
|
new Event(e).stop();
|
||||||
var h = myTable.selectedIds();
|
var h = torrentsTable.selectedRowsIds();
|
||||||
if (h.length) {
|
if (h.length) {
|
||||||
h.each(function(hash, index) {
|
h.each(function(hash, index) {
|
||||||
new Request({
|
new Request({
|
||||||
@ -377,7 +377,7 @@ initializeWindows = function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
setPriorityFN = function(cmd) {
|
setPriorityFN = function(cmd) {
|
||||||
var h = myTable.selectedIds();
|
var h = torrentsTable.selectedRowsIds();
|
||||||
if (h.length) {
|
if (h.length) {
|
||||||
new Request({
|
new Request({
|
||||||
url: 'command/' + cmd,
|
url: 'command/' + cmd,
|
||||||
|
@ -278,7 +278,7 @@ var loadTorrentFilesData = function() {
|
|||||||
// Tab changed, don't do anything
|
// Tab changed, don't do anything
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var new_hash = myTable.getCurrentTorrentHash();
|
var new_hash = torrentsTable.getCurrentTorrentHash();
|
||||||
if (new_hash == "") {
|
if (new_hash == "") {
|
||||||
fTable.removeAllRows();
|
fTable.removeAllRows();
|
||||||
clearTimeout(loadTorrentFilesDataTimer);
|
clearTimeout(loadTorrentFilesDataTimer);
|
||||||
|
@ -32,7 +32,7 @@ var loadTorrentData = function() {
|
|||||||
// Tab changed, don't do anything
|
// Tab changed, don't do anything
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var current_hash = myTable.getCurrentTorrentHash();
|
var current_hash = torrentsTable.getCurrentTorrentHash();
|
||||||
if (current_hash == "") {
|
if (current_hash == "") {
|
||||||
clearData();
|
clearData();
|
||||||
clearTimeout(loadTorrentDataTimer);
|
clearTimeout(loadTorrentDataTimer);
|
||||||
|
@ -59,7 +59,7 @@ var loadTrackersData = function() {
|
|||||||
// Tab changed, don't do anything
|
// Tab changed, don't do anything
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var new_hash = myTable.getCurrentTorrentHash();
|
var new_hash = torrentsTable.getCurrentTorrentHash();
|
||||||
if (new_hash == "") {
|
if (new_hash == "") {
|
||||||
tTable.removeAllRows();
|
tTable.removeAllRows();
|
||||||
clearTimeout(loadTrackersDataTimer);
|
clearTimeout(loadTrackersDataTimer);
|
||||||
|
@ -59,7 +59,7 @@ var loadWebSeedsData = function() {
|
|||||||
// Tab changed, don't do anything
|
// Tab changed, don't do anything
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var new_hash = myTable.getCurrentTorrentHash();
|
var new_hash = torrentsTable.getCurrentTorrentHash();
|
||||||
if (new_hash == "") {
|
if (new_hash == "") {
|
||||||
wsTable.removeAllRows();
|
wsTable.removeAllRows();
|
||||||
clearTimeout(loadWebSeedsDataTimer);
|
clearTimeout(loadWebSeedsDataTimer);
|
||||||
|
@ -1,15 +1,15 @@
|
|||||||
<table class="torrentTable" cellpadding="0" cellspacing="0" style="-webkit-touch-callout: none; -webkit-user-select: none; -khtml-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none;">
|
<table class="torrentTable" cellpadding="0" cellspacing="0" style="-webkit-touch-callout: none; -webkit-user-select: none; -khtml-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none;">
|
||||||
<thead>
|
<thead>
|
||||||
<tr id="torrentTableHeader">
|
<tr id="torrentsTableHeader">
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody id="myTable"></tbody>
|
<tbody id="torrentsTable"></tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
|
|
||||||
//create a context menu
|
//create a context menu
|
||||||
var context_menu = new ContextMenu({
|
var torrents_table_context_menu = new ContextMenu({
|
||||||
targets : '.menu-target',
|
targets : '.menu-target',
|
||||||
menu : 'contextmenu',
|
menu : 'contextmenu',
|
||||||
actions : {
|
actions : {
|
||||||
@ -62,5 +62,5 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
myTable.setup('myTable', context_menu);
|
torrentsTable.setup('torrentsTable', 'torrentsTableHeader', torrents_table_context_menu);
|
||||||
</script>
|
</script>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user