Version: unspecified (using KDE 4.4.3) OS: Linux Ktorrent 4.0.5 with patches from bug 261903 compiled from sources. Reproducible: Always Steps to Reproduce: Load 4000-5000 torrents. Exit ktorrent. Start ktorrent, sort list on names (maybe not needed). Go to about 10% below top of list, click on torrent to select. go to about 10% above bottom of list, shift-click to select all torrents in between. Now use scroll-wheel on mouse to quickly scroll through the list. GUI will normally lockup/hang within a minute. Actual Results: Sometimes GUI hangs fore some seconds, sometimes it seems to hang forever (have not waited more than a few hours). When terminating the application i sometimes get a segfault error, but otherwise no useful info. Expected Results: GUI should not lock up/hang. Probably some kind of timing problem, I normally run from another machine (kde 3.5.9) with SSH over a 100 mbit/s ethernet, then it often hangs even when you are scrolling slowly/carefully with only a few 100 selected torrents. When I do this I often have torrents completing loading and seeding, they are small, typically from 1 k to 1 meg each.
If it hangs do this: killall -11 ktorrent That will trigger a crash, the kde crash dialog should then appear, so I can see where it hangs. I will do some expirements with a lot of torrents, to see if performance can be improved.
Created attachment 55771 [details] infro from four killall -11 ktorrent cases with hung gui. Hi, I am now thinking the problem has to to more with the resorting of the list than the scrolling, only that you do not notice the problem until it wont scroll. You can also get the hang by selecting torrents as above, then re-sort the list several times by clicking on the various sort-options. (the last two crash-infos were made that way).
It would appear that getting the selected rows, is the big bottleneck.
Looking more closely at the backtraces, what is happening is this: - QM decides a torrent is stalled for too long - QM decreases it's priority - This triggers an update in the GUI to update tool buttons and menu items - The update asks the current selection My guess is that given the amount of torrents, this scenario is happening many times in a short period of time. Due to the fact that running a lot of torrents at the same time, will probably lead to a lot of stalled torrents. It might be minutes before a torrent actually gets a chance to connect to a peer.
Created attachment 55877 [details] First attempt at a solution
Could you try this patch ? This will postpone the GUI update until the QM is finished. And thus will only do one GUI update.
commit 09c02baef0bc9a9925367f50e86b44c6c9d2ae70 branch master Author: Joris <joris.guisson@gmail.com> Date: Wed Jan 12 18:35:51 2011 +0100 Don't update GUI actions when queue is being ordered, improves performance CCBUG: 262571 diff --git a/ChangeLog b/ChangeLog index 510bf53..9110e1a 100644 --- a/ChangeLog +++ b/ChangeLog @@ -3,6 +3,7 @@ Changes in 4.2: Changes in 4.1rc1: - Make sure that apply button of config dialog is enabled properly when the group changes in the scanfolder settings +- Don't update GUI actions when queue is being ordered, improves performance (262571) Changes in 4.1beta1: - Remove libktupnp it is now part of libktorrent diff --git a/ktorrent/core.cpp b/ktorrent/core.cpp index 45b5a75..4d4cb99 100644 --- a/ktorrent/core.cpp +++ b/ktorrent/core.cpp @@ -68,7 +68,12 @@ namespace kt { const Uint32 CORE_UPDATE_INTERVAL = 250; - Core::Core(kt::GUI* gui) : gui(gui),keep_seeding(true),sleep_suppression_cookie(-1),exiting(false) + Core::Core(kt::GUI* gui) + : gui(gui), + keep_seeding(true), + sleep_suppression_cookie(-1), + exiting(false), + reordering_queue(false) { UpdateCurrentTime(); qman = new QueueManager(); @@ -78,6 +83,8 @@ namespace kt this, SLOT(enqueueTorrentOverMaxRatio( bt::TorrentInterface* ))); connect(qman, SIGNAL(lowDiskSpace(bt::TorrentInterface*, bool)), this, SLOT(onLowDiskSpace(bt::TorrentInterface*, bool))); + connect(qman, SIGNAL(orderingQueue()), this, SLOT(beforeQueueReorder())); + connect(qman, SIGNAL(queueOrdered()), this, SLOT(afterQueueReorder())); data_dir = Settings::tempDir().toLocalFile(); bool dd_not_exist = !bt::Exists(data_dir); @@ -118,7 +125,6 @@ namespace kt applySettings(); gman->loadGroups(); - connect(qman,SIGNAL(queueOrdered()),this,SLOT(startUpdateTimer())); connect(magnet,SIGNAL(metadataFound(bt::MagnetLink,QByteArray,bool)), this,SLOT(onMetadataDownloaded(bt::MagnetLink,QByteArray,bool))); @@ -1062,7 +1068,9 @@ namespace kt magnet->updateMagnetDownloaders(); // check if the priority of stalled torrents must be decreased if (Settings::decreasePriorityOfStalledTorrents()) + { qman->checkStalledTorrents(bt::CurrentTime(),Settings::stallTimer()); + } } } catch (bt::Error & err) @@ -1380,7 +1388,20 @@ namespace kt void Core::onStatusChanged(bt::TorrentInterface* tc) { Q_UNUSED(tc); + if (!reordering_queue) + gui->updateActions(); + } + + void Core::beforeQueueReorder() + { + reordering_queue = true; + } + + void Core::afterQueueReorder() + { + reordering_queue = false; gui->updateActions(); + startUpdateTimer(); } void Core::load(const bt::MagnetLink& mlink,const QString & group) diff --git a/ktorrent/core.h b/ktorrent/core.h index 4a3eb76..0deaf28 100644 --- a/ktorrent/core.h +++ b/ktorrent/core.h @@ -246,6 +246,8 @@ namespace kt void checkForKDE3Torrents(); void delayedRemove(bt::TorrentInterface* tc); void delayedStart(); + void beforeQueueReorder(); + void afterQueueReorder(); private: GUI* gui; @@ -262,6 +264,7 @@ namespace kt int sleep_suppression_cookie; QMap<bt::TorrentInterface*,bool> delayed_removal; bool exiting; + bool reordering_queue; }; } diff --git a/libktcore/torrent/queuemanager.cpp b/libktcore/torrent/queuemanager.cpp index a8197bb..375dde7 100644 --- a/libktcore/torrent/queuemanager.cpp +++ b/libktcore/torrent/queuemanager.cpp @@ -547,6 +547,8 @@ namespace kt if (ordering || !downloads.count() || exiting) return; + emit orderingQueue(); + downloads.sort(); // sort downloads, even when suspended so that the QM widget is updated if (Settings::manuallyControlTorrents() || suspended_state) { diff --git a/libktcore/torrent/queuemanager.h b/libktcore/torrent/queuemanager.h index ef99585..b8c5ee7 100644 --- a/libktcore/torrent/queuemanager.h +++ b/libktcore/torrent/queuemanager.h @@ -255,8 +255,11 @@ namespace kt */ void lowDiskSpace(bt::TorrentInterface* tc, bool stopped); + /// Emitted before the queue is reordered + void orderingQueue(); + /** - * Emitted when the QM reorders it's queue + * Emitted when the QM has reordered it's queue */ void queueOrdered();
Result of testing patch in comment #5 and #6: Tried it on the 4.0.5 version with the timer update patch in libktorrent. (4.0.5 because libktorrent-1.1beta wont build on my system) When patching I got: patching file ktorrent/core.cpp Hunk #3 FAILED at 125. So tried both variants of it, but no luck, still hangs for hours. (This was on a 4x2.2 GHz machine so its a bit slower than on previous test)
commit e217541dbc20b6259dabf666d526e67d501073ee branch 4.1 Author: Joris <joris.guisson@gmail.com> Date: Wed Jan 12 18:35:51 2011 +0100 Merge to 1.1: Don't update GUI actions when queue is being ordered, improves performance CCBUG: 262571 diff --git a/ChangeLog b/ChangeLog index 444ee8b..9659fa2 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,6 @@ Changes in 4.1rc1: - Make sure that apply button of config dialog is enabled properly when the group changes in the scanfolder settings +- Don't update GUI actions when queue is being ordered, improves performance (262571) Changes in 4.1beta1: - Remove libktupnp it is now part of libktorrent diff --git a/ktorrent/core.cpp b/ktorrent/core.cpp index e3a4342..9eaedcc 100644 --- a/ktorrent/core.cpp +++ b/ktorrent/core.cpp @@ -68,7 +68,12 @@ namespace kt { const Uint32 CORE_UPDATE_INTERVAL = 250; - Core::Core(kt::GUI* gui) : gui(gui),keep_seeding(true),sleep_suppression_cookie(-1),exiting(false) + Core::Core(kt::GUI* gui) + : gui(gui), + keep_seeding(true), + sleep_suppression_cookie(-1), + exiting(false), + reordering_queue(false) { UpdateCurrentTime(); qman = new QueueManager(); @@ -78,6 +83,8 @@ namespace kt this, SLOT(enqueueTorrentOverMaxRatio( bt::TorrentInterface* ))); connect(qman, SIGNAL(lowDiskSpace(bt::TorrentInterface*, bool)), this, SLOT(onLowDiskSpace(bt::TorrentInterface*, bool))); + connect(qman, SIGNAL(orderingQueue()), this, SLOT(beforeQueueReorder())); + connect(qman, SIGNAL(queueOrdered()), this, SLOT(afterQueueReorder())); data_dir = Settings::tempDir().toLocalFile(); bool dd_not_exist = !bt::Exists(data_dir); @@ -118,7 +125,6 @@ namespace kt applySettings(); gman->loadGroups(); - connect(qman,SIGNAL(queueOrdered()),this,SLOT(startUpdateTimer())); connect(magnet,SIGNAL(metadataFound(bt::MagnetLink,QByteArray,bool)), this,SLOT(onMetadataDownloaded(bt::MagnetLink,QByteArray,bool))); @@ -1062,7 +1068,9 @@ namespace kt magnet->updateMagnetDownloaders(); // check if the priority of stalled torrents must be decreased if (Settings::decreasePriorityOfStalledTorrents()) + { qman->checkStalledTorrents(bt::CurrentTime(),Settings::stallTimer()); + } } } catch (bt::Error & err) @@ -1380,7 +1388,20 @@ namespace kt void Core::onStatusChanged(bt::TorrentInterface* tc) { Q_UNUSED(tc); + if (!reordering_queue) + gui->updateActions(); + } + + void Core::beforeQueueReorder() + { + reordering_queue = true; + } + + void Core::afterQueueReorder() + { + reordering_queue = false; gui->updateActions(); + startUpdateTimer(); } void Core::load(const bt::MagnetLink& mlink,const QString & group) diff --git a/ktorrent/core.h b/ktorrent/core.h index 4a3eb76..0deaf28 100644 --- a/ktorrent/core.h +++ b/ktorrent/core.h @@ -246,6 +246,8 @@ namespace kt void checkForKDE3Torrents(); void delayedRemove(bt::TorrentInterface* tc); void delayedStart(); + void beforeQueueReorder(); + void afterQueueReorder(); private: GUI* gui; @@ -262,6 +264,7 @@ namespace kt int sleep_suppression_cookie; QMap<bt::TorrentInterface*,bool> delayed_removal; bool exiting; + bool reordering_queue; }; } diff --git a/libktcore/torrent/queuemanager.cpp b/libktcore/torrent/queuemanager.cpp index a8197bb..375dde7 100644 --- a/libktcore/torrent/queuemanager.cpp +++ b/libktcore/torrent/queuemanager.cpp @@ -547,6 +547,8 @@ namespace kt if (ordering || !downloads.count() || exiting) return; + emit orderingQueue(); + downloads.sort(); // sort downloads, even when suspended so that the QM widget is updated if (Settings::manuallyControlTorrents() || suspended_state) { diff --git a/libktcore/torrent/queuemanager.h b/libktcore/torrent/queuemanager.h index ef99585..b8c5ee7 100644 --- a/libktcore/torrent/queuemanager.h +++ b/libktcore/torrent/queuemanager.h @@ -255,8 +255,11 @@ namespace kt */ void lowDiskSpace(bt::TorrentInterface* tc, bool stopped); + /// Emitted before the queue is reordered + void orderingQueue(); + /** - * Emitted when the QM reorders it's queue + * Emitted when the QM has reordered it's queue */ void queueOrdered();
Created attachment 56078 [details] Info from KDE crashhandler, with patch #5 after killall -11
Created attachment 56079 [details] Info from KDE crashhandler, with patch #7 after killall -11
commit b60a28c9d0a017d840010c347d633f61d48ba121 branch master Author: Joris <joris.guisson@gmail.com> Date: Tue Jan 18 18:08:18 2011 +0100 Improve performance of ktorrent in situations where there are many torrents: - Log viewer no longer chokes on many log messages - Load torrents earlier to prevent unnecessary gui updates - Make GUI updates more efficient - Don't sort when appending torrents to QM, seeing that orderQueue does that anyway - Do not load all discovered torrents in one go in scanfolder plugin BUG: 262571 diff --git a/ChangeLog b/ChangeLog index a035c48..9d27535 100644 --- a/ChangeLog +++ b/ChangeLog @@ -3,8 +3,8 @@ Changes in 4.2: Changes in 4.1rc1: - Make sure that apply button of config dialog is enabled properly when the group changes in the scanfolder settings -- Don't update GUI actions when queue is being ordered, improves performance (262571) - Add kio-magnet +- Improve performance of ktorrent in situations where there are many torrents (262571) Changes in 4.1beta1: - Remove libktupnp it is now part of libktorrent diff --git a/ktorrent/gui.cpp b/ktorrent/gui.cpp index 14d842f..8fd67bf 100644 --- a/ktorrent/gui.cpp +++ b/ktorrent/gui.cpp @@ -88,6 +88,8 @@ namespace kt part_manager = new KParts::PartManager(this); connect(part_manager,SIGNAL(activePartChanged(KParts::Part*)),this,SLOT(activePartChanged(KParts::Part*))); core = new Core(this); + core->loadTorrents(); + tray_icon = new TrayIcon(core,this); central = new CentralWidget(this); @@ -104,7 +106,7 @@ namespace kt setStatusBar(status_bar); //Marker mark("core->loadTorrents()"); - core->loadTorrents(); + //core->loadTorrents(); //mark.update(); connect(&timer,SIGNAL(timeout()),this,SLOT(update())); diff --git a/ktorrent/view/view.cpp b/ktorrent/view/view.cpp index 74b88c9..fc5e2a0 100644 --- a/ktorrent/view/view.cpp +++ b/ktorrent/view/view.cpp @@ -145,27 +145,34 @@ namespace kt QAbstractItemView::update(idx); } } - - bool View::needToUpdateCaption() + + struct RunningCounter { - Uint32 torrents = 0; - Uint32 running = 0; - QList<bt::TorrentInterface*> all; - model->allTorrents(all); - foreach (bt::TorrentInterface* ti,all) + public: + Uint32 torrents; + Uint32 running; + + RunningCounter() : torrents(0),running(0) + {} + + bool operator()(bt::TorrentInterface* tc) { - if (!group || (group && group->isMember(ti))) - { - torrents++; - if (ti->getStats().running) - running++; - } + torrents++; + if (tc->getStats().running) + running++; + return true; } + }; + + bool View::needToUpdateCaption() + { + RunningCounter rc; + model->visit(rc); - if (num_running != running || num_torrents != torrents) + if (num_running != rc.running || num_torrents != rc.torrents) { - num_running = running; - num_torrents = torrents; + num_running = rc.running; + num_torrents = rc.torrents; return true; } diff --git a/ktorrent/view/viewmanager.cpp b/ktorrent/view/viewmanager.cpp index 249a571..5dadd85 100644 --- a/ktorrent/view/viewmanager.cpp +++ b/ktorrent/view/viewmanager.cpp @@ -411,6 +411,28 @@ namespace kt return kt::JobTracker::createJobWidget(job); } + struct StartAndStopAllVisitor + { + QAction* start_all; + QAction* stop_all; + + StartAndStopAllVisitor(QAction* start_all,QAction* stop_all) : start_all(start_all),stop_all(stop_all) + {} + + bool operator()(bt::TorrentInterface* tc) + { + if (tc->getJobQueue()->runningJobs()) + return true; + + const TorrentStats & s = tc->getStats(); + if (s.running || (tc->isAllowedToStart() && !tc->overMaxRatio() && !tc->overMaxSeedTime())) + stop_all->setEnabled(true); + else + start_all->setEnabled(true); + + return !stop_all->isEnabled() || !start_all->isEnabled(); + } + }; void ViewManager::updateActions() { @@ -517,24 +539,10 @@ namespace kt if (qm_enabled) { - QList<bt::TorrentInterface*> all; - current->viewModel()->allTorrents(all); start_all->setEnabled(false); stop_all->setEnabled(false); - foreach (bt::TorrentInterface* tc,all) - { - if (tc->getJobQueue()->runningJobs()) - continue; - - const TorrentStats & s = tc->getStats(); - if (s.running || (tc->isAllowedToStart() && !tc->overMaxRatio() && !tc->overMaxSeedTime())) - stop_all->setEnabled(true); - else - start_all->setEnabled(true); - - if (stop_all->isEnabled() && start_all->isEnabled()) - break; - } + StartAndStopAllVisitor v(start_all,stop_all); + current->viewModel()->visit(v); } else { diff --git a/ktorrent/view/viewmodel.h b/ktorrent/view/viewmodel.h index dcfc036..71b305c 100644 --- a/ktorrent/view/viewmodel.h +++ b/ktorrent/view/viewmodel.h @@ -119,6 +119,20 @@ namespace kt */ void allTorrents(QList<bt::TorrentInterface*> & tlist) const; + /** + * Visit all visible torrents in the model, and apply an action to them + */ + template<class Action> + void visit(Action & a) + { + foreach (Item* item,torrents) + { + if (item->member(group)) + if (!a(item->tc)) + break; + } + } + /// Get the list of indexes which need to be updated const QModelIndexList & updateList() const {return update_list;} diff --git a/libktcore/torrent/queuemanager.cpp b/libktcore/torrent/queuemanager.cpp index 375dde7..813e711 100644 --- a/libktcore/torrent/queuemanager.cpp +++ b/libktcore/torrent/queuemanager.cpp @@ -67,7 +67,6 @@ namespace kt void QueueManager::append(bt::TorrentInterface* tc) { downloads.append(tc); - downloads.sort(); connect(tc, SIGNAL(diskSpaceLow(bt::TorrentInterface*, bool)), this, SLOT(onLowDiskSpace(bt::TorrentInterface*, bool))); connect(tc, SIGNAL(torrentStopped(bt::TorrentInterface*)), this, SLOT(torrentStopped(bt::TorrentInterface*))); connect(tc,SIGNAL(updateQueue()),this,SLOT(orderQueue())); diff --git a/plugins/logviewer/logviewer.cpp b/plugins/logviewer/logviewer.cpp index 6b6e0c3..f8d08bf 100644 --- a/plugins/logviewer/logviewer.cpp +++ b/plugins/logviewer/logviewer.cpp @@ -31,22 +31,8 @@ namespace kt { - const int LOG_EVENT_TYPE = 65432; - - class LogEvent : public QEvent - { - QString str; - public: - LogEvent(const QString & str) : QEvent((QEvent::Type)LOG_EVENT_TYPE),str(str) - {} - - virtual ~LogEvent() - {} - - const QString & msg() const {return str;} - }; - LogViewer::LogViewer(LogFlags* flags,QWidget *parent) : Activity(i18n("Log"),"utilities-log-viewer",100,parent),use_rich_text(true),flags(flags),suspended(false),menu(0) + LogViewer::LogViewer(LogFlags* flags,QWidget *parent) : Activity(i18n("Log"),"utilities-log-viewer",100,parent),use_rich_text(true),flags(flags),suspended(false),menu(0),max_block_count(200) { setToolTip(i18n("View the logging output generated by KTorrent")); QVBoxLayout* layout = new QVBoxLayout(this); @@ -54,7 +40,7 @@ namespace kt layout->setMargin(0); layout->setSpacing(0); layout->addWidget(output); - output->document()->setMaximumBlockCount(200); + output->document()->setMaximumBlockCount(max_block_count); output->setContextMenuPolicy(Qt::CustomContextMenu); connect(output,SIGNAL(customContextMenuRequested(QPoint)),this,SLOT(showMenu(QPoint))); @@ -76,34 +62,34 @@ namespace kt to add strings to it, this will ensure that strings will only be added in the main application thread. */ - if(arg==0x00 || flags->checkFlags(arg)) + if (arg==0x00 || flags->checkFlags(arg)) { - if(use_rich_text) + QMutexLocker lock(&mutex); + if (use_rich_text) { - QString tmp = line; - LogEvent* le = new LogEvent(flags->getFormattedMessage(arg, tmp)); - QApplication::postEvent(this,le); + pending.append(flags->getFormattedMessage(arg, line)); } else { - LogEvent* le = new LogEvent(line); - QApplication::postEvent(this,le); + pending.append(line); } + + while (pending.size() > max_block_count) + pending.pop_front(); } } - void LogViewer::customEvent(QEvent* ev) + void LogViewer::processPending() { - if (ev->type() == LOG_EVENT_TYPE) + QMutexLocker lock(&mutex); + foreach (const QString & line,pending) { - LogEvent* le = (LogEvent*)ev; - if (!suspended) - { - QTextCharFormat fm = output->currentCharFormat(); - output->append(le->msg()); - output->setCurrentCharFormat(fm); - } + QTextCharFormat fm = output->currentCharFormat(); + output->append(line); + output->setCurrentCharFormat(fm); } + + pending.clear(); } void LogViewer::setRichText(bool val) @@ -113,6 +99,7 @@ namespace kt void LogViewer::setMaxBlockCount(int max) { + max_block_count = max; output->document()->setMaximumBlockCount(max); } diff --git a/plugins/logviewer/logviewer.h b/plugins/logviewer/logviewer.h index c1efd87..377426f 100644 --- a/plugins/logviewer/logviewer.h +++ b/plugins/logviewer/logviewer.h @@ -21,10 +21,12 @@ #define KTLOGVIEWER_H #include <QTextBrowser> +#include <QMutex> #include <interfaces/activity.h> #include <interfaces/logmonitorinterface.h> #include "logflags.h" + namespace kt { /** @@ -38,10 +40,10 @@ namespace kt virtual ~LogViewer(); virtual void message(const QString& line, unsigned int arg); - virtual void customEvent(QEvent* ev); void setRichText(bool val); void setMaxBlockCount(int max); + void processPending(); public slots: void showMenu(const QPoint & pos); @@ -54,6 +56,10 @@ namespace kt bool suspended; QMenu* menu; QAction* suspend_action; + int max_block_count; + + QMutex mutex; + QStringList pending; }; } diff --git a/plugins/logviewer/logviewerplugin.cpp b/plugins/logviewer/logviewerplugin.cpp index b2d35dd..5c3e35c 100644 --- a/plugins/logviewer/logviewerplugin.cpp +++ b/plugins/logviewer/logviewerplugin.cpp @@ -144,7 +144,11 @@ namespace kt } } - + void LogViewerPlugin::guiUpdate() + { + if (lv) + lv->processPending(); + } bool LogViewerPlugin::versionCheck(const QString & version) const { diff --git a/plugins/logviewer/logviewerplugin.h b/plugins/logviewer/logviewerplugin.h index 4599de2..cd84d5a 100644 --- a/plugins/logviewer/logviewerplugin.h +++ b/plugins/logviewer/logviewerplugin.h @@ -51,6 +51,7 @@ namespace kt virtual void load(); virtual void unload(); virtual bool versionCheck(const QString& version) const; + virtual void guiUpdate(); private slots: void applySettings(); diff --git a/plugins/scanfolder/scanfolder.cpp b/plugins/scanfolder/scanfolder.cpp index 141b29d..b2f7f49 100644 --- a/plugins/scanfolder/scanfolder.cpp +++ b/plugins/scanfolder/scanfolder.cpp @@ -139,23 +139,44 @@ namespace kt m_incomplePollingTimer.start(10000); } } - else + else if (!m_to_load.contains(source) && !m_pendingURLs.contains(source)) { bt::Out(SYS_SNF|LOG_NOTICE) << "ScanFolder : found " << source << endl; - //Add pending entry... - m_pendingURLs.push_back(source); - - QString group; - if (ScanFolderPluginSettings::addToGroup()) - group = ScanFolderPluginSettings::group(); - //Load torrent - if (ScanFolderPluginSettings::openSilently()) - m_core->loadSilently(source,group); - else - m_core->load(source,group); + m_to_load.append(source); } } + + loadDelayed(); + } + + void ScanFolder::loadDelayed() + { + // Don't load to many in a row + int loaded = 0; + KUrl::List::iterator i = m_to_load.begin(); + while (i != m_to_load.end() && loaded < 10) + { + KUrl source = *i; + //Add pending entry... + m_pendingURLs.push_back(source); + QString group; + if (ScanFolderPluginSettings::addToGroup()) + group = ScanFolderPluginSettings::group(); + + //Load torrent + if (ScanFolderPluginSettings::openSilently()) + m_core->loadSilently(source,group); + else + m_core->load(source,group); + + i = m_to_load.erase(i); + loaded++; + } + + if (!m_to_load.isEmpty()) + QTimer::singleShot(2000, this, SLOT(loadDelayed())); } + void ScanFolder::onLoadingFinished(const KUrl & url, bool success, bool canceled) { @@ -195,7 +216,7 @@ namespace kt QFile::remove(dirname + "." + name); // NetAccess considered harmfull !!! - KIO::file_move(url, destination); + KIO::file_move(url, destination, -1, KIO::HideProgressInfo); break; case defaultAction: QFile f(dirname + "." + name); diff --git a/plugins/scanfolder/scanfolder.h b/plugins/scanfolder/scanfolder.h index b811c7e..c161954 100644 --- a/plugins/scanfolder/scanfolder.h +++ b/plugins/scanfolder/scanfolder.h @@ -55,48 +55,50 @@ namespace kt */ class ScanFolder : public QObject { - Q_OBJECT - public: - - /** - * Default constructor. - * @param core Pointer to core interface - * @param dir Full directory path - * @param action Action to perform on loaded torrents. - */ - ScanFolder(CoreInterface* core, const QString& dir, LoadedTorrentAction action = defaultAction); - ~ScanFolder(); + Q_OBJECT + public: + + /** + * Default constructor. + * @param core Pointer to core interface + * @param dir Full directory path + * @param action Action to perform on loaded torrents. + */ + ScanFolder(CoreInterface* core, const QString& dir, LoadedTorrentAction action = defaultAction); + ~ScanFolder(); - ///Accessor method for m_loadedAction. - void setLoadedAction(const LoadedTorrentAction& theValue); - ///Accessor method for m_loadedAction. - LoadedTorrentAction loadedAction() const { return m_loadedAction; } + ///Accessor method for m_loadedAction. + void setLoadedAction(const LoadedTorrentAction& theValue); + ///Accessor method for m_loadedAction. + LoadedTorrentAction loadedAction() const { return m_loadedAction; } - ///Returns true if this object is valid, that is - weather directory is valid and this object does its work. - bool isValid() const { return m_valid; } - - ///Sets directory path - void setFolderUrl(QString& url); + ///Returns true if this object is valid, that is - weather directory is valid and this object does its work. + bool isValid() const { return m_valid; } + + ///Sets directory path + void setFolderUrl(QString& url); - public slots: - void onNewItems(const KFileItemList &items); - void onLoadingFinished(const KUrl & url,bool success,bool canceled); - void onIncompletePollingTimeout(); - - private: - /// Check if the URL is a complete file - bool incomplete(const KUrl & src); + public slots: + void onNewItems(const KFileItemList &items); + void onLoadingFinished(const KUrl & url,bool success,bool canceled); + void onIncompletePollingTimeout(); + void loadDelayed(); + + private: + /// Check if the URL is a complete file + bool incomplete(const KUrl & src); - private: - QString m_root_dir; - CoreInterface* m_core; - bool m_valid; - KDirLister* m_dir; - LoadedTorrentAction m_loadedAction; - QList<KUrl> m_pendingURLs; - QList<KUrl> m_incompleteURLs; - QTimer m_incomplePollingTimer; + private: + QString m_root_dir; + CoreInterface* m_core; + bool m_valid; + KDirLister* m_dir; + LoadedTorrentAction m_loadedAction; + KUrl::List m_to_load; + KUrl::List m_pendingURLs; + KUrl::List m_incompleteURLs; + QTimer m_incomplePollingTimer; }; } #endif
commit a3399fa89ee372aaa55c217b45ebdb827f55f42b branch 4.1 Author: Joris <joris.guisson@gmail.com> Date: Tue Jan 18 18:09:48 2011 +0100 Improve performance of ktorrent in situations where there are many torrents: - Log viewer no longer chokes on many log messages - Load torrents earlier to prevent unnecessary gui updates - Make GUI updates more efficient - Don't sort when appending torrents to QM, seeing that orderQueue does that anyway - Do not load all discovered torrents in one go in scanfolder plugin CCBUG: 262571 Conflicts: ChangeLog diff --git a/ChangeLog b/ChangeLog index 444ee8b..1455529 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,7 @@ Changes in 4.1rc1: - Make sure that apply button of config dialog is enabled properly when the group changes in the scanfolder settings +- Add kio-magnet +- Improve performance of ktorrent in situations where there are many torrents (262571) Changes in 4.1beta1: - Remove libktupnp it is now part of libktorrent diff --git a/ktorrent/gui.cpp b/ktorrent/gui.cpp index 14d842f..8fd67bf 100644 --- a/ktorrent/gui.cpp +++ b/ktorrent/gui.cpp @@ -88,6 +88,8 @@ namespace kt part_manager = new KParts::PartManager(this); connect(part_manager,SIGNAL(activePartChanged(KParts::Part*)),this,SLOT(activePartChanged(KParts::Part*))); core = new Core(this); + core->loadTorrents(); + tray_icon = new TrayIcon(core,this); central = new CentralWidget(this); @@ -104,7 +106,7 @@ namespace kt setStatusBar(status_bar); //Marker mark("core->loadTorrents()"); - core->loadTorrents(); + //core->loadTorrents(); //mark.update(); connect(&timer,SIGNAL(timeout()),this,SLOT(update())); diff --git a/ktorrent/view/view.cpp b/ktorrent/view/view.cpp index 74b88c9..fc5e2a0 100644 --- a/ktorrent/view/view.cpp +++ b/ktorrent/view/view.cpp @@ -145,27 +145,34 @@ namespace kt QAbstractItemView::update(idx); } } - - bool View::needToUpdateCaption() + + struct RunningCounter { - Uint32 torrents = 0; - Uint32 running = 0; - QList<bt::TorrentInterface*> all; - model->allTorrents(all); - foreach (bt::TorrentInterface* ti,all) + public: + Uint32 torrents; + Uint32 running; + + RunningCounter() : torrents(0),running(0) + {} + + bool operator()(bt::TorrentInterface* tc) { - if (!group || (group && group->isMember(ti))) - { - torrents++; - if (ti->getStats().running) - running++; - } + torrents++; + if (tc->getStats().running) + running++; + return true; } + }; + + bool View::needToUpdateCaption() + { + RunningCounter rc; + model->visit(rc); - if (num_running != running || num_torrents != torrents) + if (num_running != rc.running || num_torrents != rc.torrents) { - num_running = running; - num_torrents = torrents; + num_running = rc.running; + num_torrents = rc.torrents; return true; } diff --git a/ktorrent/view/viewmanager.cpp b/ktorrent/view/viewmanager.cpp index 249a571..5dadd85 100644 --- a/ktorrent/view/viewmanager.cpp +++ b/ktorrent/view/viewmanager.cpp @@ -411,6 +411,28 @@ namespace kt return kt::JobTracker::createJobWidget(job); } + struct StartAndStopAllVisitor + { + QAction* start_all; + QAction* stop_all; + + StartAndStopAllVisitor(QAction* start_all,QAction* stop_all) : start_all(start_all),stop_all(stop_all) + {} + + bool operator()(bt::TorrentInterface* tc) + { + if (tc->getJobQueue()->runningJobs()) + return true; + + const TorrentStats & s = tc->getStats(); + if (s.running || (tc->isAllowedToStart() && !tc->overMaxRatio() && !tc->overMaxSeedTime())) + stop_all->setEnabled(true); + else + start_all->setEnabled(true); + + return !stop_all->isEnabled() || !start_all->isEnabled(); + } + }; void ViewManager::updateActions() { @@ -517,24 +539,10 @@ namespace kt if (qm_enabled) { - QList<bt::TorrentInterface*> all; - current->viewModel()->allTorrents(all); start_all->setEnabled(false); stop_all->setEnabled(false); - foreach (bt::TorrentInterface* tc,all) - { - if (tc->getJobQueue()->runningJobs()) - continue; - - const TorrentStats & s = tc->getStats(); - if (s.running || (tc->isAllowedToStart() && !tc->overMaxRatio() && !tc->overMaxSeedTime())) - stop_all->setEnabled(true); - else - start_all->setEnabled(true); - - if (stop_all->isEnabled() && start_all->isEnabled()) - break; - } + StartAndStopAllVisitor v(start_all,stop_all); + current->viewModel()->visit(v); } else { diff --git a/ktorrent/view/viewmodel.h b/ktorrent/view/viewmodel.h index dcfc036..71b305c 100644 --- a/ktorrent/view/viewmodel.h +++ b/ktorrent/view/viewmodel.h @@ -119,6 +119,20 @@ namespace kt */ void allTorrents(QList<bt::TorrentInterface*> & tlist) const; + /** + * Visit all visible torrents in the model, and apply an action to them + */ + template<class Action> + void visit(Action & a) + { + foreach (Item* item,torrents) + { + if (item->member(group)) + if (!a(item->tc)) + break; + } + } + /// Get the list of indexes which need to be updated const QModelIndexList & updateList() const {return update_list;} diff --git a/libktcore/torrent/queuemanager.cpp b/libktcore/torrent/queuemanager.cpp index a8197bb..bb0a629 100644 --- a/libktcore/torrent/queuemanager.cpp +++ b/libktcore/torrent/queuemanager.cpp @@ -67,7 +67,6 @@ namespace kt void QueueManager::append(bt::TorrentInterface* tc) { downloads.append(tc); - downloads.sort(); connect(tc, SIGNAL(diskSpaceLow(bt::TorrentInterface*, bool)), this, SLOT(onLowDiskSpace(bt::TorrentInterface*, bool))); connect(tc, SIGNAL(torrentStopped(bt::TorrentInterface*)), this, SLOT(torrentStopped(bt::TorrentInterface*))); connect(tc,SIGNAL(updateQueue()),this,SLOT(orderQueue())); diff --git a/plugins/logviewer/logviewer.cpp b/plugins/logviewer/logviewer.cpp index 6b6e0c3..f8d08bf 100644 --- a/plugins/logviewer/logviewer.cpp +++ b/plugins/logviewer/logviewer.cpp @@ -31,22 +31,8 @@ namespace kt { - const int LOG_EVENT_TYPE = 65432; - - class LogEvent : public QEvent - { - QString str; - public: - LogEvent(const QString & str) : QEvent((QEvent::Type)LOG_EVENT_TYPE),str(str) - {} - - virtual ~LogEvent() - {} - - const QString & msg() const {return str;} - }; - LogViewer::LogViewer(LogFlags* flags,QWidget *parent) : Activity(i18n("Log"),"utilities-log-viewer",100,parent),use_rich_text(true),flags(flags),suspended(false),menu(0) + LogViewer::LogViewer(LogFlags* flags,QWidget *parent) : Activity(i18n("Log"),"utilities-log-viewer",100,parent),use_rich_text(true),flags(flags),suspended(false),menu(0),max_block_count(200) { setToolTip(i18n("View the logging output generated by KTorrent")); QVBoxLayout* layout = new QVBoxLayout(this); @@ -54,7 +40,7 @@ namespace kt layout->setMargin(0); layout->setSpacing(0); layout->addWidget(output); - output->document()->setMaximumBlockCount(200); + output->document()->setMaximumBlockCount(max_block_count); output->setContextMenuPolicy(Qt::CustomContextMenu); connect(output,SIGNAL(customContextMenuRequested(QPoint)),this,SLOT(showMenu(QPoint))); @@ -76,34 +62,34 @@ namespace kt to add strings to it, this will ensure that strings will only be added in the main application thread. */ - if(arg==0x00 || flags->checkFlags(arg)) + if (arg==0x00 || flags->checkFlags(arg)) { - if(use_rich_text) + QMutexLocker lock(&mutex); + if (use_rich_text) { - QString tmp = line; - LogEvent* le = new LogEvent(flags->getFormattedMessage(arg, tmp)); - QApplication::postEvent(this,le); + pending.append(flags->getFormattedMessage(arg, line)); } else { - LogEvent* le = new LogEvent(line); - QApplication::postEvent(this,le); + pending.append(line); } + + while (pending.size() > max_block_count) + pending.pop_front(); } } - void LogViewer::customEvent(QEvent* ev) + void LogViewer::processPending() { - if (ev->type() == LOG_EVENT_TYPE) + QMutexLocker lock(&mutex); + foreach (const QString & line,pending) { - LogEvent* le = (LogEvent*)ev; - if (!suspended) - { - QTextCharFormat fm = output->currentCharFormat(); - output->append(le->msg()); - output->setCurrentCharFormat(fm); - } + QTextCharFormat fm = output->currentCharFormat(); + output->append(line); + output->setCurrentCharFormat(fm); } + + pending.clear(); } void LogViewer::setRichText(bool val) @@ -113,6 +99,7 @@ namespace kt void LogViewer::setMaxBlockCount(int max) { + max_block_count = max; output->document()->setMaximumBlockCount(max); } diff --git a/plugins/logviewer/logviewer.h b/plugins/logviewer/logviewer.h index c1efd87..377426f 100644 --- a/plugins/logviewer/logviewer.h +++ b/plugins/logviewer/logviewer.h @@ -21,10 +21,12 @@ #define KTLOGVIEWER_H #include <QTextBrowser> +#include <QMutex> #include <interfaces/activity.h> #include <interfaces/logmonitorinterface.h> #include "logflags.h" + namespace kt { /** @@ -38,10 +40,10 @@ namespace kt virtual ~LogViewer(); virtual void message(const QString& line, unsigned int arg); - virtual void customEvent(QEvent* ev); void setRichText(bool val); void setMaxBlockCount(int max); + void processPending(); public slots: void showMenu(const QPoint & pos); @@ -54,6 +56,10 @@ namespace kt bool suspended; QMenu* menu; QAction* suspend_action; + int max_block_count; + + QMutex mutex; + QStringList pending; }; } diff --git a/plugins/logviewer/logviewerplugin.cpp b/plugins/logviewer/logviewerplugin.cpp index b2d35dd..5c3e35c 100644 --- a/plugins/logviewer/logviewerplugin.cpp +++ b/plugins/logviewer/logviewerplugin.cpp @@ -144,7 +144,11 @@ namespace kt } } - + void LogViewerPlugin::guiUpdate() + { + if (lv) + lv->processPending(); + } bool LogViewerPlugin::versionCheck(const QString & version) const { diff --git a/plugins/logviewer/logviewerplugin.h b/plugins/logviewer/logviewerplugin.h index 4599de2..cd84d5a 100644 --- a/plugins/logviewer/logviewerplugin.h +++ b/plugins/logviewer/logviewerplugin.h @@ -51,6 +51,7 @@ namespace kt virtual void load(); virtual void unload(); virtual bool versionCheck(const QString& version) const; + virtual void guiUpdate(); private slots: void applySettings(); diff --git a/plugins/scanfolder/scanfolder.cpp b/plugins/scanfolder/scanfolder.cpp index 141b29d..b2f7f49 100644 --- a/plugins/scanfolder/scanfolder.cpp +++ b/plugins/scanfolder/scanfolder.cpp @@ -139,23 +139,44 @@ namespace kt m_incomplePollingTimer.start(10000); } } - else + else if (!m_to_load.contains(source) && !m_pendingURLs.contains(source)) { bt::Out(SYS_SNF|LOG_NOTICE) << "ScanFolder : found " << source << endl; - //Add pending entry... - m_pendingURLs.push_back(source); - - QString group; - if (ScanFolderPluginSettings::addToGroup()) - group = ScanFolderPluginSettings::group(); - //Load torrent - if (ScanFolderPluginSettings::openSilently()) - m_core->loadSilently(source,group); - else - m_core->load(source,group); + m_to_load.append(source); } } + + loadDelayed(); + } + + void ScanFolder::loadDelayed() + { + // Don't load to many in a row + int loaded = 0; + KUrl::List::iterator i = m_to_load.begin(); + while (i != m_to_load.end() && loaded < 10) + { + KUrl source = *i; + //Add pending entry... + m_pendingURLs.push_back(source); + QString group; + if (ScanFolderPluginSettings::addToGroup()) + group = ScanFolderPluginSettings::group(); + + //Load torrent + if (ScanFolderPluginSettings::openSilently()) + m_core->loadSilently(source,group); + else + m_core->load(source,group); + + i = m_to_load.erase(i); + loaded++; + } + + if (!m_to_load.isEmpty()) + QTimer::singleShot(2000, this, SLOT(loadDelayed())); } + void ScanFolder::onLoadingFinished(const KUrl & url, bool success, bool canceled) { @@ -195,7 +216,7 @@ namespace kt QFile::remove(dirname + "." + name); // NetAccess considered harmfull !!! - KIO::file_move(url, destination); + KIO::file_move(url, destination, -1, KIO::HideProgressInfo); break; case defaultAction: QFile f(dirname + "." + name); diff --git a/plugins/scanfolder/scanfolder.h b/plugins/scanfolder/scanfolder.h index b811c7e..c161954 100644 --- a/plugins/scanfolder/scanfolder.h +++ b/plugins/scanfolder/scanfolder.h @@ -55,48 +55,50 @@ namespace kt */ class ScanFolder : public QObject { - Q_OBJECT - public: - - /** - * Default constructor. - * @param core Pointer to core interface - * @param dir Full directory path - * @param action Action to perform on loaded torrents. - */ - ScanFolder(CoreInterface* core, const QString& dir, LoadedTorrentAction action = defaultAction); - ~ScanFolder(); + Q_OBJECT + public: + + /** + * Default constructor. + * @param core Pointer to core interface + * @param dir Full directory path + * @param action Action to perform on loaded torrents. + */ + ScanFolder(CoreInterface* core, const QString& dir, LoadedTorrentAction action = defaultAction); + ~ScanFolder(); - ///Accessor method for m_loadedAction. - void setLoadedAction(const LoadedTorrentAction& theValue); - ///Accessor method for m_loadedAction. - LoadedTorrentAction loadedAction() const { return m_loadedAction; } + ///Accessor method for m_loadedAction. + void setLoadedAction(const LoadedTorrentAction& theValue); + ///Accessor method for m_loadedAction. + LoadedTorrentAction loadedAction() const { return m_loadedAction; } - ///Returns true if this object is valid, that is - weather directory is valid and this object does its work. - bool isValid() const { return m_valid; } - - ///Sets directory path - void setFolderUrl(QString& url); + ///Returns true if this object is valid, that is - weather directory is valid and this object does its work. + bool isValid() const { return m_valid; } + + ///Sets directory path + void setFolderUrl(QString& url); - public slots: - void onNewItems(const KFileItemList &items); - void onLoadingFinished(const KUrl & url,bool success,bool canceled); - void onIncompletePollingTimeout(); - - private: - /// Check if the URL is a complete file - bool incomplete(const KUrl & src); + public slots: + void onNewItems(const KFileItemList &items); + void onLoadingFinished(const KUrl & url,bool success,bool canceled); + void onIncompletePollingTimeout(); + void loadDelayed(); + + private: + /// Check if the URL is a complete file + bool incomplete(const KUrl & src); - private: - QString m_root_dir; - CoreInterface* m_core; - bool m_valid; - KDirLister* m_dir; - LoadedTorrentAction m_loadedAction; - QList<KUrl> m_pendingURLs; - QList<KUrl> m_incompleteURLs; - QTimer m_incomplePollingTimer; + private: + QString m_root_dir; + CoreInterface* m_core; + bool m_valid; + KDirLister* m_dir; + LoadedTorrentAction m_loadedAction; + KUrl::List m_to_load; + KUrl::List m_pendingURLs; + KUrl::List m_incompleteURLs; + QTimer m_incomplePollingTimer; }; } #endif