- Totally rewritten Web UI list refresh system (fixed memory leak)

This commit is contained in:
Christophe Dumez 2008-09-28 11:30:24 +00:00
parent a65cd5c39c
commit 0879f2c0ca
8 changed files with 115 additions and 419 deletions

View file

@ -6,6 +6,7 @@
- FEATURE: Can have different proxies for Bittorrent and search engine - FEATURE: Can have different proxies for Bittorrent and search engine
- FEATURE: Allow multiple item selection in Web UI transfer list - FEATURE: Allow multiple item selection in Web UI transfer list
- FEATURE: Moved uploads to a separate list in Web UI - FEATURE: Moved uploads to a separate list in Web UI
- BUGFIX: Totally rewritten Web UI list refresh system (fixed memory leak)
- BUGFIX: Disable ETA calculation when ETA column is hidden - BUGFIX: Disable ETA calculation when ETA column is hidden
- BUGFIX: Removed "disconnected" connection state, detection was far from perfect - BUGFIX: Removed "disconnected" connection state, detection was far from perfect
- COSMETIC: Transfer speed, ratio, connection status and DHT nodes are displayed in status bar - COSMETIC: Transfer speed, ratio, connection status and DHT nodes are displayed in status bar

View file

@ -721,7 +721,7 @@ void bittorrent::setUnfinishedTorrent(QString hash) {
updateDownloadQueue(); updateDownloadQueue();
} }
} }
emit torrentSwitchedtoUnfinished(hash); //emit torrentSwitchedtoUnfinished(hash);
} }
// Add the given hash to the list of finished torrents // Add the given hash to the list of finished torrents
@ -759,7 +759,7 @@ void bittorrent::setFinishedTorrent(QString hash) {
} }
// Save fast resume data // Save fast resume data
saveFastResumeAndRatioData(hash); saveFastResumeAndRatioData(hash);
emit torrentSwitchedtoFinished(hash); //emit torrentSwitchedtoFinished(hash);
} }
// Pause a running torrent // Pause a running torrent

View file

@ -224,8 +224,8 @@ class bittorrent : public QObject {
void updateUnfinishedTorrentNumber(); void updateUnfinishedTorrentNumber();
void forceUnfinishedListUpdate(); void forceUnfinishedListUpdate();
void forceFinishedListUpdate(); void forceFinishedListUpdate();
void torrentSwitchedtoFinished(QString hash); /*void torrentSwitchedtoFinished(QString hash);
void torrentSwitchedtoUnfinished(QString hash); void torrentSwitchedtoUnfinished(QString hash);*/
}; };
#endif #endif

View file

@ -27,49 +27,20 @@
EventManager::EventManager(QObject *parent, bittorrent *BTSession) EventManager::EventManager(QObject *parent, bittorrent *BTSession)
: QObject(parent), BTSession(BTSession) : QObject(parent), BTSession(BTSession)
{ {
revision = 0;
} }
void EventManager::update(QVariantMap event) QVariant EventManager::getEventList() const {
{
++revision;
events << QPair<ulong, QVariantMap>(revision, event);
emit updated();
//qDebug("Added the following event");
//qDebug() << event;
/* QLinkedList<QPair<ulong, QVariantMap> >::iterator i;
for (i = events.begin(); i != events.end(); i++)
qDebug() << *i;*/
}
QVariant EventManager::querySince(ulong r) const
{
QVariantList list; QVariantList list;
QLinkedListIterator<QPair<ulong, QVariantMap> > i(events); foreach(QVariantMap event, event_list.values()) {
i.toBack(); list << QVariant(event);
while (i.hasPrevious())
{
QPair<ulong, QVariantMap> pair = i.previous();
if (pair.first <= r)
break;
list.prepend(QVariant(pair.second));
} }
QVariantMap map; return QVariant(list);
map["events"] = QVariant(list);
map["revision"] = QVariant((qulonglong) revision);
return QVariant(map);
}
bool EventManager::isUpdated(ulong r) const
{
return (r < revision);
} }
void EventManager::addedTorrent(QTorrentHandle& h) void EventManager::addedTorrent(QTorrentHandle& h)
{ {
QVariantMap event; QVariantMap event;
QString hash = h.hash(); QString hash = h.hash();
event["type"] = QVariant("add");
event["hash"] = QVariant(hash); event["hash"] = QVariant(hash);
event["name"] = QVariant(h.name()); event["name"] = QVariant(h.name());
event["seed"] = QVariant(h.is_seed()); event["seed"] = QVariant(h.is_seed());
@ -113,219 +84,61 @@ void EventManager::addedTorrent(QTorrentHandle& h)
event["state"] = QVariant(); event["state"] = QVariant();
} }
} }
update(event); event_list[hash] = event;
}
void EventManager::torrentSwitchedtoUnfinished(QString hash) {
QVariantMap event;
QTorrentHandle h = BTSession->getTorrentHandle(hash);
event["type"] = QVariant("unfinish");
event["hash"] = QVariant(h.hash());
event["name"] = QVariant(h.name());
if(h.is_paused()) {
if(BTSession->isQueueingEnabled() && (BTSession->isDownloadQueued(hash) || BTSession->isUploadQueued(hash)))
event["state"] = QVariant("queued");
else
event["state"] = QVariant("paused");
} else {
switch(h.state())
{
case torrent_status::finished:
case torrent_status::seeding:
event["state"] = QVariant("seeding");
break;
case torrent_status::checking_files:
case torrent_status::queued_for_checking:
event["state"] = QVariant("checking");
break;
case torrent_status::connecting_to_tracker:
if(h.download_payload_rate() > 0)
event["state"] = QVariant("downloading");
else
event["state"] = QVariant("connecting");
break;
case torrent_status::downloading:
case torrent_status::downloading_metadata:
if(h.download_payload_rate() > 0)
event["state"] = QVariant("downloading");
else
event["state"] = QVariant("stalled");
break;
default:
qDebug("No status, should not happen!!! status is %d", h.state());
event["state"] = QVariant();
}
}
event["size"] = QVariant((qlonglong)h.actual_size());
event["progress"] = QVariant(h.progress());
event["dlspeed"] = QVariant(h.download_payload_rate());
event["upspeed"] = QVariant(h.upload_payload_rate());
update(event);
}
void EventManager::torrentSwitchedtoFinished(QString hash) {
QVariantMap event;
QTorrentHandle h = BTSession->getTorrentHandle(hash);
event["type"] = QVariant("finish");
event["hash"] = QVariant(h.hash());
event["name"] = QVariant(h.name());
if(h.is_paused()) {
if(BTSession->isQueueingEnabled() && (BTSession->isDownloadQueued(hash) || BTSession->isUploadQueued(hash)))
event["state"] = QVariant("queued");
else
event["state"] = QVariant("paused");
} else {
switch(h.state())
{
case torrent_status::finished:
case torrent_status::seeding:
event["state"] = QVariant("seeding");
break;
case torrent_status::checking_files:
case torrent_status::queued_for_checking:
event["state"] = QVariant("checking");
break;
case torrent_status::connecting_to_tracker:
if(h.download_payload_rate() > 0)
event["state"] = QVariant("downloading");
else
event["state"] = QVariant("connecting");
break;
case torrent_status::downloading:
case torrent_status::downloading_metadata:
if(h.download_payload_rate() > 0)
event["state"] = QVariant("downloading");
else
event["state"] = QVariant("stalled");
break;
default:
qDebug("No status, should not happen!!! status is %d", h.state());
event["state"] = QVariant();
}
}
event["size"] = QVariant((qlonglong)h.actual_size());
event["upspeed"] = QVariant(h.upload_payload_rate());
update(event);
} }
void EventManager::deletedTorrent(QString hash) void EventManager::deletedTorrent(QString hash)
{ {
QVariantMap event; event_list.remove(hash);
QTorrentHandle h = BTSession->getTorrentHandle(hash);
event["type"] = QVariant("delete");
event["hash"] = QVariant(hash);
event["seed"] = QVariant(h.is_seed());
QLinkedList<QPair<ulong, QVariantMap> >::iterator i = events.end();
bool loop = true;
while (loop && i != events.begin()) {
--i;
QVariantMap oldevent = i->second;
if(oldevent["hash"] == QVariant(hash))
{
if(oldevent["type"] == QVariant("add"))
loop = false;
i = events.erase(i);
}
}
update(event);
} }
void EventManager::modifiedTorrent(QTorrentHandle h) void EventManager::modifiedTorrent(QTorrentHandle h)
{ {
QString hash = h.hash(); QString hash = h.hash();
QVariantMap event; QVariantMap event;
QVariant v;
if(h.is_paused()) { if(h.is_paused()) {
if(BTSession->isQueueingEnabled() && (BTSession->isDownloadQueued(hash) || BTSession->isUploadQueued(hash))) if(BTSession->isQueueingEnabled() && (BTSession->isDownloadQueued(hash) || BTSession->isUploadQueued(hash)))
v = QVariant("queued"); event["state"] = QVariant("queued");
else else
v = QVariant("paused"); event["state"] = QVariant("paused");
} else { } else {
switch(h.state()) switch(h.state())
{ {
case torrent_status::finished: case torrent_status::finished:
case torrent_status::seeding: case torrent_status::seeding:
v = QVariant("seeding"); event["state"] = QVariant("seeding");
break; break;
case torrent_status::checking_files: case torrent_status::checking_files:
case torrent_status::queued_for_checking: case torrent_status::queued_for_checking:
v = QVariant("checking"); event["state"] = QVariant("checking");
break; break;
case torrent_status::connecting_to_tracker: case torrent_status::connecting_to_tracker:
if(h.download_payload_rate() > 0) if(h.download_payload_rate() > 0)
v = QVariant("downloading"); event["state"] = QVariant("downloading");
else else
v = QVariant("connecting"); event["state"] = QVariant("connecting");
break; break;
case torrent_status::downloading: case torrent_status::downloading:
case torrent_status::downloading_metadata: case torrent_status::downloading_metadata:
if(h.download_payload_rate() > 0) if(h.download_payload_rate() > 0)
v = QVariant("downloading"); event["state"] = QVariant("downloading");
else else
v = QVariant("stalled"); event["state"] = QVariant("stalled");
break; break;
default: default:
qDebug("No status, should not happen!!! status is %d", h.state()); qDebug("No status, should not happen!!! status is %d", h.state());
v = QVariant(); event["state"] = QVariant();
} }
} }
if(modify(hash, "state", v)) event["name"] = QVariant(h.name());
event["state"] = v; event["size"] = QVariant((qlonglong)h.actual_size());
v = QVariant(h.name());
if(modify(hash, "name", v))
event["name"] = v;
v = QVariant((qlonglong)h.actual_size());
if(modify(hash, "size", v))
event["size"] = v;
if(!h.is_seed()) { if(!h.is_seed()) {
v = QVariant(h.progress()); event["progress"] = QVariant(h.progress());
if(modify(hash, "progress", v)) event["dlspeed"] = QVariant(h.download_payload_rate());
event["progress"] = v;
v = QVariant(h.download_payload_rate());
if(modify(hash, "dlspeed", v))
event["dlspeed"] = v;
}
v = QVariant(h.upload_payload_rate());
if(modify(hash, "upspeed", v))
event["upspeed"] = v;
v = QVariant(h.is_seed());
event["seed"] = v;
if(event.size() > 0)
{
event["type"] = QVariant("modify");
event["hash"] = QVariant(hash);
update(event);
} }
} event["upspeed"] = QVariant(h.upload_payload_rate());
event["seed"] = QVariant(h.is_seed());
bool EventManager::modify(QString hash, QString key, QVariant value) event["hash"] = QVariant(hash);
{ event_list[hash] = event;
QLinkedList<QPair<ulong, QVariantMap> >::iterator i = events.end();
while (i != events.begin()) {
--i;
QVariantMap event = i->second;
if(event["hash"] == QVariant(hash))
{
if(event["type"] == QVariant("add"))
return true;
if(event.contains(key))
{
if(event[key] == value)
return false;
else
{
if(event.size() <= 3)
i = events.erase(i);
else
i->second.remove(key);
return true;
}
}
}
}
return true;
} }

View file

@ -23,8 +23,7 @@
#define EVENTMANAGER_H #define EVENTMANAGER_H
#include "qtorrenthandle.h" #include "qtorrenthandle.h"
#include <QLinkedList> #include <QHash>
#include <QPair>
#include <QVariant> #include <QVariant>
struct bittorrent; struct bittorrent;
@ -33,9 +32,7 @@ class EventManager : public QObject
{ {
Q_OBJECT Q_OBJECT
private: private:
ulong revision; QHash<QString, QVariantMap> event_list;
QLinkedList<QPair <ulong, QVariantMap> > events;
bool modify(QString hash, QString key, QVariant value);
bittorrent* BTSession; bittorrent* BTSession;
protected: protected:
@ -43,18 +40,12 @@ class EventManager : public QObject
public: public:
EventManager(QObject *parent, bittorrent* BTSession); EventManager(QObject *parent, bittorrent* BTSession);
QVariant querySince(ulong r) const; QVariant getEventList() const;
bool isUpdated(ulong r) const;
signals:
void updated();
public slots: public slots:
void addedTorrent(QTorrentHandle& h); void addedTorrent(QTorrentHandle& h);
void deletedTorrent(QString hash); void deletedTorrent(QString hash);
void modifiedTorrent(QTorrentHandle h); void modifiedTorrent(QTorrentHandle h);
void torrentSwitchedtoUnfinished(QString hash);
void torrentSwitchedtoFinished(QString hash);
}; };
#endif #endif

View file

@ -115,12 +115,7 @@ void HttpConnection::respond()
{ {
if (list[1] == "events") if (list[1] == "events")
{ {
EventManager* manager = parent->eventManager(); respondJson();
uint r = parser.get("r").toUInt();
if(manager->isUpdated(r))
respondJson();
else
connect(manager, SIGNAL(updated()), this, SLOT(respondJson()));
return; return;
} }
} }
@ -166,9 +161,7 @@ void HttpConnection::respondNotFound()
void HttpConnection::respondJson() void HttpConnection::respondJson()
{ {
EventManager* manager = parent->eventManager(); EventManager* manager = parent->eventManager();
QString temp = parser.get("r"); QVariant data = manager->getEventList();
uint r = parser.get("r").toUInt();
QVariant data = manager->querySince(r);
QString string = toJson(data); QString string = toJson(data);
generator.setStatusLine(200, "OK"); generator.setStatusLine(200, "OK");
generator.setContentTypeByExt("js"); generator.setContentTypeByExt("js");

View file

@ -37,16 +37,14 @@ HttpServer::HttpServer(bittorrent *BTSession, int msec, QObject* parent) : QTcpS
QTorrentHandle h = BTSession->getTorrentHandle(hash); QTorrentHandle h = BTSession->getTorrentHandle(hash);
if(h.is_valid()) manager->addedTorrent(h); if(h.is_valid()) manager->addedTorrent(h);
} }
list = BTSession->getFinishedTorrents(); list = BTSession->getFinishedTorrents();
foreach(QString hash, list) { foreach(QString hash, list) {
QTorrentHandle h = BTSession->getTorrentHandle(hash); QTorrentHandle h = BTSession->getTorrentHandle(hash);
if(h.is_valid()) manager->addedTorrent(h); if(h.is_valid()) manager->addedTorrent(h);
} }
//connect BTSession to manager //connect BTSession to manager
connect(BTSession, SIGNAL(addedTorrent(QTorrentHandle&)), manager, SLOT(addedTorrent(QTorrentHandle&))); connect(BTSession, SIGNAL(addedTorrent(QTorrentHandle&)), manager, SLOT(addedTorrent(QTorrentHandle&)));
connect(BTSession, SIGNAL(deletedTorrent(QString)), manager, SLOT(deletedTorrent(QString))); connect(BTSession, SIGNAL(deletedTorrent(QString)), manager, SLOT(deletedTorrent(QString)));
connect(BTSession, SIGNAL(torrentSwitchedtoUnfinished(QString)), manager, SLOT(torrentSwitchedtoUnfinished(QString)));
connect(BTSession, SIGNAL(torrentSwitchedtoFinished(QString)), manager, SLOT(torrentSwitchedtoFinished(QString)));
//set timer //set timer
timer = new QTimer(this); timer = new QTimer(this);
connect(timer, SIGNAL(timeout()), this, SLOT(onTimer())); connect(timer, SIGNAL(timeout()), this, SLOT(onTimer()));

View file

@ -32,6 +32,26 @@ window.addEvent('domready', function(){
myTableUP = new dynamicTable('myTableUP', {overCls: 'over', selectCls: 'selected', altCls: 'alt', type: 'UP'}); myTableUP = new dynamicTable('myTableUP', {overCls: 'over', selectCls: 'selected', altCls: 'alt', type: 'UP'});
var r=0; var r=0;
var waiting=false; var waiting=false;
var stateToImg = function(state){
switch (state)
{
case 'paused':
return '<img src="images/skin/paused.png"/>';
case 'seeding':
return '<img src="images/skin/seeding.png"/>';
case 'checking':
return '<img src="images/time.png"/>';
case 'downloading':
return '<img src="images/skin/downloading.png"/>';
case 'connecting':
return '<img src="images/skin/connecting.png"/>';
case 'stalled':
return '<img src="images/skin/stalled.png"/>';
case 'queued':
return '<img src="images/skin/queued.png"/>';
}
return '';
};
var round1 = function(val){return Math.round(val*10)/10}; var round1 = function(val){return Math.round(val*10)/10};
var fspeed = function(val){return round1(val/1024) + ' KiB/s';}; var fspeed = function(val){return round1(val/1024) + ' KiB/s';};
var fsize = function(val){ var fsize = function(val){
@ -45,197 +65,77 @@ window.addEvent('domready', function(){
return round1(val) + ' TiB'; return round1(val) + ' TiB';
}; };
var ajaxfn = function(){ var ajaxfn = function(){
var url = 'json/events?r='+r; var url = 'json/events';
if (!waiting){ if (!waiting){
waiting=true; waiting=true;
var request = new Request.JSON({ var request = new Request.JSON({
url: url, url: url,
method: 'get', method: 'get',
onComplete: function(jsonObj) { onComplete: function(events) {
if(jsonObj){ if(events){
r=jsonObj.revision; // Add new torrents or update them
var events=jsonObj.events; unfinished_hashes = myTable.getRowIds();
events.each(function(event){ finished_hashes = myTableUP.getRowIds();
switch(event.type){ events_hashes = new Array();
case 'add': events.each(function(event){
var row = new Array(); events_hashes[events_hashes.length] = event.hash;
if(event.seed) if(event.seed) {
row.length = 4;
else
row.length = 6;
switch (event.state)
{
case 'paused':
row[0] = '<img src="images/skin/paused.png"/>';
break;
case 'seeding':
row[0] = '<img src="images/skin/seeding.png"/>';
break;
case 'checking':
row[0] = '<img src="images/time.png"/>';
break;
case 'downloading':
row[0] = '<img src="images/skin/downloading.png"/>';
break;
case 'connecting':
row[0] = '<img src="images/skin/connecting.png"/>';
break;
case 'stalled':
row[0] = '<img src="images/skin/stalled.png"/>';
break;
case 'queued':
row[0] = '<img src="images/skin/queued.png"/>';
break;
}
row[1] = event.name;
row[2] = fsize(event.size);
if(!event.seed) {
if($defined(event.progress))
{
row[3] = round1(event.progress*100) + ' %';
}
if($defined(event.dlspeed))
row[4] = fspeed(event.dlspeed);
if($defined(event.upspeed))
row[5] = fspeed(event.upspeed);
} else {
if($defined(event.upspeed))
row[3] = fspeed(event.upspeed);
}
if(event.seed)
myTableUP.insertRow(event.hash, row);
else
myTable.insertRow(event.hash, row);
break;
case 'modify':
var row = new Array();
if($defined(event.state))
{
switch (event.state)
{
case 'paused':
row[0] = '<img src="images/skin/paused.png"/>';
break;
case 'seeding':
row[0] = '<img src="images/skin/seeding.png"/>';
break;
case 'checking':
row[0] = '<img src="images/time.png"/>';
break;
case 'downloading':
row[0] = '<img src="images/skin/downloading.png"/>';
break;
case 'connecting':
row[0] = '<img src="images/skin/connecting.png"/>';
break;
case 'stalled':
row[0] = '<img src="images/skin/stalled.png"/>';
break;
case 'queued':
row[0] = '<img src="images/skin/queued.png"/>';
break;
}
}
if($defined(event.name)) {
row[1] = event.name;
}
if($defined(event.size)){
row[2] = fsize(event.size);
}
if(!event.seed) {
if($defined(event.progress))
{
row[3] = round1(event.progress*100) + ' %';
}
if($defined(event.dlspeed))
row[4] = fspeed(event.dlspeed);
if($defined(event.upspeed))
row[5] = fspeed(event.upspeed);
} else {
if($defined(event.upspeed))
row[3] = fspeed(event.upspeed);
}
if(event.seed)
myTableUP.updateRow(event.hash, row);
else
myTable.updateRow(event.hash, row);
break;
case 'delete':
if(event.seed)
myTableUP.removeRow(event.hash);
else
myTable.removeRow(event.hash);
break;
case 'finish':
myTable.removeRow(event.hash);
var row = new Array(); var row = new Array();
row.length = 4; row.length = 4;
switch (event.state) row[0] = stateToImg(event.state);
{ row[1] = event.name;
case 'paused':
row[0] = '<img src="images/skin/paused.png"/>';
break;
case 'seeding':
row[0] = '<img src="images/skin/seeding.png"/>';
break;
case 'checking':
row[0] = '<img src="images/time.png"/>';
break;
case 'downloading':
row[0] = '<img src="images/skin/downloading.png"/>';
break;
case 'connecting':
row[0] = '<img src="images/skin/connecting.png"/>';
break;
case 'stalled':
row[0] = '<img src="images/skin/stalled.png"/>';
break;
case 'queued':
row[0] = '<img src="images/skin/queued.png"/>';
break;
}
row[1] = event.name;
row[2] = fsize(event.size); row[2] = fsize(event.size);
row[3] = fspeed(event.upspeed); row[3] = fspeed(event.upspeed);
myTableUP.insertRow(event.hash, row); if(!finished_hashes.contains(event.hash)) {
break; // New finished torrent
case 'unfinish': finished_hashes[finished_hashes.length] = event.hash;
myTableUP.removeRow(event.hash); myTableUP.insertRow(event.hash, row);
var row = new Array(); if(unfinished_hashes.contains(event.hash)) {
row.length = 6; // Torrent used to be in unfinished list
switch (event.state) // Remove it
{ myTable.removeRow(event.hash);
case 'paused': unfinished_hashes.erase(event.hash);
row[0] = '<img src="images/skin/paused.png"/>'; }
break; } else {
case 'seeding': // Update torrent data
row[0] = '<img src="images/skin/seeding.png"/>'; myTableUP.updateRow(event.hash, row);
break;
case 'checking':
row[0] = '<img src="images/time.png"/>';
break;
case 'downloading':
row[0] = '<img src="images/skin/downloading.png"/>';
break;
case 'connecting':
row[0] = '<img src="images/skin/connecting.png"/>';
break;
case 'stalled':
row[0] = '<img src="images/skin/stalled.png"/>';
break;
case 'queued':
row[0] = '<img src="images/skin/queued.png"/>';
break;
} }
row[1] = event.name; } else {
var row = new Array();
row.length = 6;
row[0] = stateToImg(event.state);
row[1] = event.name;
row[2] = fsize(event.size); row[2] = fsize(event.size);
row[3] = round1(event.progress*100) + ' %'; row[3] = round1(event.progress*100) + ' %';
row[4] = fspeed(event.dlspeed); row[4] = fspeed(event.dlspeed);
row[5] = fspeed(event.upspeed); row[5] = fspeed(event.upspeed);
myTable.insertRow(event.hash, row); if(!unfinished_hashes.contains(event.hash)) {
break; // New unfinished torrent
} unfinished_hashes[unfinished_hashes.length] = event.hash;
}); myTable.insertRow(event.hash, row);
if(finished_hashes.contains(event.hash)) {
// Torrent used to be in unfinished list
// Remove it
myTableUP.removeRow(event.hash);
finished_hashes.erase(event.hash);
}
} else {
// Update torrent data
myTable.updateRow(event.hash, row);
}
}
});
// Remove deleted torrents
unfinished_hashes.each(function(hash){
if(!events_hashes.contains(hash)) {
myTable.removeRow(hash);
}
});
finished_hashes.each(function(hash){
if(!events_hashes.contains(hash)) {
myTableUP.removeRow(hash);
}
});
} }
waiting=false; waiting=false;
ajaxfn.delay(1000); ajaxfn.delay(1000);