From 0879f2c0ca965cbcee275b4acb4766f15eb4b0e0 Mon Sep 17 00:00:00 2001 From: Christophe Dumez Date: Sun, 28 Sep 2008 11:30:24 +0000 Subject: [PATCH] - Totally rewritten Web UI list refresh system (fixed memory leak) --- Changelog | 1 + src/bittorrent.cpp | 4 +- src/bittorrent.h | 4 +- src/eventmanager.cpp | 227 +++----------------------------- src/eventmanager.h | 15 +-- src/httpconnection.cpp | 11 +- src/httpserver.cpp | 10 +- src/webui/scripts/client.js | 250 +++++++++++------------------------- 8 files changed, 109 insertions(+), 413 deletions(-) diff --git a/Changelog b/Changelog index 6e83cee9f..5f49f2f20 100644 --- a/Changelog +++ b/Changelog @@ -6,6 +6,7 @@ - FEATURE: Can have different proxies for Bittorrent and search engine - FEATURE: Allow multiple item selection in Web UI transfer list - 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: Removed "disconnected" connection state, detection was far from perfect - COSMETIC: Transfer speed, ratio, connection status and DHT nodes are displayed in status bar diff --git a/src/bittorrent.cpp b/src/bittorrent.cpp index 47f66b1cd..88e1d80be 100644 --- a/src/bittorrent.cpp +++ b/src/bittorrent.cpp @@ -721,7 +721,7 @@ void bittorrent::setUnfinishedTorrent(QString hash) { updateDownloadQueue(); } } - emit torrentSwitchedtoUnfinished(hash); + //emit torrentSwitchedtoUnfinished(hash); } // Add the given hash to the list of finished torrents @@ -759,7 +759,7 @@ void bittorrent::setFinishedTorrent(QString hash) { } // Save fast resume data saveFastResumeAndRatioData(hash); - emit torrentSwitchedtoFinished(hash); + //emit torrentSwitchedtoFinished(hash); } // Pause a running torrent diff --git a/src/bittorrent.h b/src/bittorrent.h index 69466c5ce..a2d3f413d 100644 --- a/src/bittorrent.h +++ b/src/bittorrent.h @@ -224,8 +224,8 @@ class bittorrent : public QObject { void updateUnfinishedTorrentNumber(); void forceUnfinishedListUpdate(); void forceFinishedListUpdate(); - void torrentSwitchedtoFinished(QString hash); - void torrentSwitchedtoUnfinished(QString hash); + /*void torrentSwitchedtoFinished(QString hash); + void torrentSwitchedtoUnfinished(QString hash);*/ }; #endif diff --git a/src/eventmanager.cpp b/src/eventmanager.cpp index c015b5af0..b58b346c9 100644 --- a/src/eventmanager.cpp +++ b/src/eventmanager.cpp @@ -27,49 +27,20 @@ EventManager::EventManager(QObject *parent, bittorrent *BTSession) : QObject(parent), BTSession(BTSession) { - revision = 0; } -void EventManager::update(QVariantMap event) -{ - ++revision; - events << QPair(revision, event); - emit updated(); - //qDebug("Added the following event"); - //qDebug() << event; -/* QLinkedList >::iterator i; - for (i = events.begin(); i != events.end(); i++) - qDebug() << *i;*/ -} - -QVariant EventManager::querySince(ulong r) const -{ +QVariant EventManager::getEventList() const { QVariantList list; - QLinkedListIterator > i(events); - i.toBack(); - while (i.hasPrevious()) - { - QPair pair = i.previous(); - if (pair.first <= r) - break; - list.prepend(QVariant(pair.second)); + foreach(QVariantMap event, event_list.values()) { + list << QVariant(event); } - QVariantMap map; - map["events"] = QVariant(list); - map["revision"] = QVariant((qulonglong) revision); - return QVariant(map); -} - -bool EventManager::isUpdated(ulong r) const -{ - return (r < revision); + return QVariant(list); } void EventManager::addedTorrent(QTorrentHandle& h) { QVariantMap event; QString hash = h.hash(); - event["type"] = QVariant("add"); event["hash"] = QVariant(hash); event["name"] = QVariant(h.name()); event["seed"] = QVariant(h.is_seed()); @@ -113,62 +84,19 @@ void EventManager::addedTorrent(QTorrentHandle& h) 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::deletedTorrent(QString hash) +{ + event_list.remove(hash); } -void EventManager::torrentSwitchedtoFinished(QString hash) { +void EventManager::modifiedTorrent(QTorrentHandle h) +{ + QString hash = h.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"); @@ -199,133 +127,18 @@ void EventManager::torrentSwitchedtoFinished(QString hash) { event["state"] = QVariant("stalled"); break; default: - qDebug("No status, should not happen!!! status is %d", h.state()); + qDebug("No status, should not happen!!! status is %d", h.state()); event["state"] = QVariant(); } } + event["name"] = QVariant(h.name()); event["size"] = QVariant((qlonglong)h.actual_size()); - event["upspeed"] = QVariant(h.upload_payload_rate()); - update(event); -} - -void EventManager::deletedTorrent(QString hash) -{ - QVariantMap event; - QTorrentHandle h = BTSession->getTorrentHandle(hash); - event["type"] = QVariant("delete"); - event["hash"] = QVariant(hash); - event["seed"] = QVariant(h.is_seed()); - QLinkedList >::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) -{ - QString hash = h.hash(); - QVariantMap event; - QVariant v; - - if(h.is_paused()) { - if(BTSession->isQueueingEnabled() && (BTSession->isDownloadQueued(hash) || BTSession->isUploadQueued(hash))) - v = QVariant("queued"); - else - v = QVariant("paused"); - } else { - switch(h.state()) - { - case torrent_status::finished: - case torrent_status::seeding: - v = QVariant("seeding"); - break; - case torrent_status::checking_files: - case torrent_status::queued_for_checking: - v = QVariant("checking"); - break; - case torrent_status::connecting_to_tracker: - if(h.download_payload_rate() > 0) - v = QVariant("downloading"); - else - v = QVariant("connecting"); - break; - case torrent_status::downloading: - case torrent_status::downloading_metadata: - if(h.download_payload_rate() > 0) - v = QVariant("downloading"); - else - v = QVariant("stalled"); - break; - default: - qDebug("No status, should not happen!!! status is %d", h.state()); - v = QVariant(); - } - } - if(modify(hash, "state", v)) - event["state"] = v; - - 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()) { - v = QVariant(h.progress()); - if(modify(hash, "progress", v)) - 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); - } -} - -bool EventManager::modify(QString hash, QString key, QVariant value) -{ - QLinkedList >::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; - } - } - } + event["progress"] = QVariant(h.progress()); + event["dlspeed"] = QVariant(h.download_payload_rate()); } - return true; + event["upspeed"] = QVariant(h.upload_payload_rate()); + event["seed"] = QVariant(h.is_seed()); + event["hash"] = QVariant(hash); + event_list[hash] = event; } diff --git a/src/eventmanager.h b/src/eventmanager.h index 690b7aa9a..7a3100e0e 100644 --- a/src/eventmanager.h +++ b/src/eventmanager.h @@ -23,8 +23,7 @@ #define EVENTMANAGER_H #include "qtorrenthandle.h" -#include -#include +#include #include struct bittorrent; @@ -33,9 +32,7 @@ class EventManager : public QObject { Q_OBJECT private: - ulong revision; - QLinkedList > events; - bool modify(QString hash, QString key, QVariant value); + QHash event_list; bittorrent* BTSession; protected: @@ -43,18 +40,12 @@ class EventManager : public QObject public: EventManager(QObject *parent, bittorrent* BTSession); - QVariant querySince(ulong r) const; - bool isUpdated(ulong r) const; - - signals: - void updated(); + QVariant getEventList() const; public slots: void addedTorrent(QTorrentHandle& h); void deletedTorrent(QString hash); void modifiedTorrent(QTorrentHandle h); - void torrentSwitchedtoUnfinished(QString hash); - void torrentSwitchedtoFinished(QString hash); }; #endif diff --git a/src/httpconnection.cpp b/src/httpconnection.cpp index 810a19fc4..074a18dd2 100644 --- a/src/httpconnection.cpp +++ b/src/httpconnection.cpp @@ -115,12 +115,7 @@ void HttpConnection::respond() { if (list[1] == "events") { - EventManager* manager = parent->eventManager(); - uint r = parser.get("r").toUInt(); - if(manager->isUpdated(r)) - respondJson(); - else - connect(manager, SIGNAL(updated()), this, SLOT(respondJson())); + respondJson(); return; } } @@ -166,9 +161,7 @@ void HttpConnection::respondNotFound() void HttpConnection::respondJson() { EventManager* manager = parent->eventManager(); - QString temp = parser.get("r"); - uint r = parser.get("r").toUInt(); - QVariant data = manager->querySince(r); + QVariant data = manager->getEventList(); QString string = toJson(data); generator.setStatusLine(200, "OK"); generator.setContentTypeByExt("js"); diff --git a/src/httpserver.cpp b/src/httpserver.cpp index b5f42aff3..26586a13c 100644 --- a/src/httpserver.cpp +++ b/src/httpserver.cpp @@ -37,16 +37,14 @@ HttpServer::HttpServer(bittorrent *BTSession, int msec, QObject* parent) : QTcpS QTorrentHandle h = BTSession->getTorrentHandle(hash); if(h.is_valid()) manager->addedTorrent(h); } - list = BTSession->getFinishedTorrents(); + list = BTSession->getFinishedTorrents(); foreach(QString hash, list) { - QTorrentHandle h = BTSession->getTorrentHandle(hash); - if(h.is_valid()) manager->addedTorrent(h); - } + QTorrentHandle h = BTSession->getTorrentHandle(hash); + if(h.is_valid()) manager->addedTorrent(h); + } //connect BTSession to manager connect(BTSession, SIGNAL(addedTorrent(QTorrentHandle&)), manager, SLOT(addedTorrent(QTorrentHandle&))); 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 timer = new QTimer(this); connect(timer, SIGNAL(timeout()), this, SLOT(onTimer())); diff --git a/src/webui/scripts/client.js b/src/webui/scripts/client.js index c37f9ba52..3ef0b22b9 100644 --- a/src/webui/scripts/client.js +++ b/src/webui/scripts/client.js @@ -32,6 +32,26 @@ window.addEvent('domready', function(){ myTableUP = new dynamicTable('myTableUP', {overCls: 'over', selectCls: 'selected', altCls: 'alt', type: 'UP'}); var r=0; var waiting=false; + var stateToImg = function(state){ + switch (state) + { + case 'paused': + return ''; + case 'seeding': + return ''; + case 'checking': + return ''; + case 'downloading': + return ''; + case 'connecting': + return ''; + case 'stalled': + return ''; + case 'queued': + return ''; + } + return ''; + }; var round1 = function(val){return Math.round(val*10)/10}; var fspeed = function(val){return round1(val/1024) + ' KiB/s';}; var fsize = function(val){ @@ -45,197 +65,77 @@ window.addEvent('domready', function(){ return round1(val) + ' TiB'; }; var ajaxfn = function(){ - var url = 'json/events?r='+r; + var url = 'json/events'; if (!waiting){ waiting=true; var request = new Request.JSON({ url: url, method: 'get', - onComplete: function(jsonObj) { - if(jsonObj){ - r=jsonObj.revision; - var events=jsonObj.events; - events.each(function(event){ - switch(event.type){ - case 'add': - var row = new Array(); - if(event.seed) - row.length = 4; - else - row.length = 6; - switch (event.state) - { - case 'paused': - row[0] = ''; - break; - case 'seeding': - row[0] = ''; - break; - case 'checking': - row[0] = ''; - break; - case 'downloading': - row[0] = ''; - break; - case 'connecting': - row[0] = ''; - break; - case 'stalled': - row[0] = ''; - break; - case 'queued': - row[0] = ''; - break; - } - row[1] = event.name; + onComplete: function(events) { + if(events){ + // Add new torrents or update them + unfinished_hashes = myTable.getRowIds(); + finished_hashes = myTableUP.getRowIds(); + events_hashes = new Array(); + events.each(function(event){ + events_hashes[events_hashes.length] = event.hash; + if(event.seed) { + var row = new Array(); + row.length = 4; + row[0] = stateToImg(event.state); + 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) + row[3] = fspeed(event.upspeed); + if(!finished_hashes.contains(event.hash)) { + // New finished torrent + finished_hashes[finished_hashes.length] = event.hash; 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] = ''; - break; - case 'seeding': - row[0] = ''; - break; - case 'checking': - row[0] = ''; - break; - case 'downloading': - row[0] = ''; - break; - case 'connecting': - row[0] = ''; - break; - case 'stalled': - row[0] = ''; - break; - case 'queued': - row[0] = ''; - 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(unfinished_hashes.contains(event.hash)) { + // Torrent used to be in unfinished list + // Remove it + myTable.removeRow(event.hash); + unfinished_hashes.erase(event.hash); } - 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) + // Update torrent data 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(); - row.length = 4; - switch (event.state) - { - case 'paused': - row[0] = ''; - break; - case 'seeding': - row[0] = ''; - break; - case 'checking': - row[0] = ''; - break; - case 'downloading': - row[0] = ''; - break; - case 'connecting': - row[0] = ''; - break; - case 'stalled': - row[0] = ''; - break; - case 'queued': - row[0] = ''; - break; } - row[1] = event.name; - row[2] = fsize(event.size); - row[3] = fspeed(event.upspeed); - myTableUP.insertRow(event.hash, row); - break; - case 'unfinish': - myTableUP.removeRow(event.hash); + } else { var row = new Array(); - row.length = 6; - switch (event.state) - { - case 'paused': - row[0] = ''; - break; - case 'seeding': - row[0] = ''; - break; - case 'checking': - row[0] = ''; - break; - case 'downloading': - row[0] = ''; - break; - case 'connecting': - row[0] = ''; - break; - case 'stalled': - row[0] = ''; - break; - case 'queued': - row[0] = ''; - break; - } - row[1] = event.name; + row.length = 6; + row[0] = stateToImg(event.state); + row[1] = event.name; row[2] = fsize(event.size); row[3] = round1(event.progress*100) + ' %'; row[4] = fspeed(event.dlspeed); row[5] = fspeed(event.upspeed); - myTable.insertRow(event.hash, row); - break; - } - }); + if(!unfinished_hashes.contains(event.hash)) { + // 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; ajaxfn.delay(1000);