***************************************************************************/
#include "kdirectorycontentscounter.h"
-
-#include "kdirectorycontentscounterworker.h"
-#include <kitemviews/kfileitemmodel.h>
+#include "kitemviews/kfileitemmodel.h"
#include <KDirWatch>
+
+#include <QFileInfo>
+#include <QDir>
#include <QThread>
+namespace {
+ /// cache of directory counting result
+ static QHash<QString, QPair<int, long>> *s_cache;
+}
+
KDirectoryContentsCounter::KDirectoryContentsCounter(KFileItemModel* model, QObject* parent) :
QObject(parent),
m_model(model),
m_queue(),
- m_workerThread(0),
- m_worker(0),
+ m_worker(nullptr),
m_workerIsBusy(false),
- m_dirWatcher(0),
+ m_dirWatcher(nullptr),
m_watchedDirs()
{
- connect(m_model, SIGNAL(itemsRemoved(KItemRangeList)),
- this, SLOT(slotItemsRemoved()));
+ connect(m_model, &KFileItemModel::itemsRemoved,
+ this, &KDirectoryContentsCounter::slotItemsRemoved);
+
+ if (!m_workerThread) {
+ m_workerThread = new QThread();
+ m_workerThread->start();
+ }
+
+ if (s_cache == nullptr) {
+ s_cache = new QHash<QString, QPair<int, long>>();
+ }
- m_workerThread = new QThread(this);
m_worker = new KDirectoryContentsCounterWorker();
m_worker->moveToThread(m_workerThread);
- connect(this, SIGNAL(requestDirectoryContentsCount(QString,KDirectoryContentsCounterWorker::Options)),
- m_worker, SLOT(countDirectoryContents(QString,KDirectoryContentsCounterWorker::Options)));
- connect(m_worker, SIGNAL(result(QString,int)),
- this, SLOT(slotResult(QString,int)));
-
- m_workerThread->start();
+ connect(this, &KDirectoryContentsCounter::requestDirectoryContentsCount,
+ m_worker, &KDirectoryContentsCounterWorker::countDirectoryContents);
+ connect(m_worker, &KDirectoryContentsCounterWorker::result,
+ this, &KDirectoryContentsCounter::slotResult);
m_dirWatcher = new KDirWatch(this);
- connect(m_dirWatcher, SIGNAL(dirty(QString)), this, SLOT(slotDirWatchDirty(QString)));
+ connect(m_dirWatcher, &KDirWatch::dirty, this, &KDirectoryContentsCounter::slotDirWatchDirty);
}
KDirectoryContentsCounter::~KDirectoryContentsCounter()
{
- m_workerThread->quit();
- m_workerThread->wait();
-
- delete m_worker;
+ if (m_workerThread->isRunning()) {
+ // The worker thread will continue running. It could even be running
+ // a method of m_worker at the moment, so we delete it using
+ // deleteLater() to prevent a crash.
+ m_worker->deleteLater();
+ } else {
+ // There are no remaining workers -> stop the worker thread.
+ m_workerThread->quit();
+ m_workerThread->wait();
+ delete m_workerThread;
+ m_workerThread = nullptr;
+
+ // The worker thread has finished running now, so it's safe to delete
+ // m_worker. deleteLater() would not work at all because the event loop
+ // which would deliver the event to m_worker is not running any more.
+ delete m_worker;
+ }
}
-void KDirectoryContentsCounter::addDirectory(const QString& path)
+void KDirectoryContentsCounter::scanDirectory(const QString& path)
{
startWorker(path);
}
-int KDirectoryContentsCounter::countDirectoryContentsSynchronously(const QString& path)
+void KDirectoryContentsCounter::slotResult(const QString& path, int count, long size)
{
- if (!m_dirWatcher->contains(path)) {
- m_dirWatcher->addDir(path);
- m_watchedDirs.insert(path);
- }
+ m_workerIsBusy = false;
- KDirectoryContentsCounterWorker::Options options;
+ const QFileInfo info = QFileInfo(path);
+ const QString resolvedPath = info.canonicalFilePath();
- if (m_model->showHiddenFiles()) {
- options |= KDirectoryContentsCounterWorker::CountHiddenFiles;
+ if (!m_dirWatcher->contains(resolvedPath)) {
+ m_dirWatcher->addDir(resolvedPath);
+ m_watchedDirs.insert(resolvedPath);
}
- if (m_model->showDirectoriesOnly()) {
- options |= KDirectoryContentsCounterWorker::CountDirectoriesOnly;
+ if (!m_queue.isEmpty()) {
+ startWorker(m_queue.takeFirst());
}
- return KDirectoryContentsCounterWorker::subItemsCount(path, options);
-}
-
-void KDirectoryContentsCounter::slotResult(const QString& path, int count)
-{
- m_workerIsBusy = false;
-
- if (!m_dirWatcher->contains(path)) {
- m_dirWatcher->addDir(path);
- m_watchedDirs.insert(path);
+ if (s_cache->contains(resolvedPath)) {
+ const auto pair = s_cache->value(resolvedPath);
+ if (pair.first == count && pair.second == size) {
+ // no change no need to send another result event
+ return;
+ }
}
- if (!m_queue.isEmpty()) {
- startWorker(m_queue.dequeue());
+ if (info.dir().path() == m_model->rootItem().url().path()) {
+ // update cache or overwrite value
+ // when path is a direct children of the current model root
+ s_cache->insert(resolvedPath, QPair<int, long>(count, size));
}
- emit result(path, count);
+ // sends the results
+ emit result(resolvedPath, count, size);
}
void KDirectoryContentsCounter::slotDirWatchDirty(const QString& path)
{
- const int index = m_model->index(KUrl(path));
+ const int index = m_model->index(QUrl::fromLocalFile(path));
if (index >= 0) {
if (!m_model->fileItem(index).isDir()) {
// If INotify is used, KDirWatch issues the dirty() signal
if (!m_watchedDirs.isEmpty()) {
// Don't let KDirWatch watch for removed items
if (allItemsRemoved) {
- foreach (const QString& path, m_watchedDirs) {
+ for (const QString& path : qAsConst(m_watchedDirs)) {
m_dirWatcher->removeDir(path);
}
m_watchedDirs.clear();
QMutableSetIterator<QString> it(m_watchedDirs);
while (it.hasNext()) {
const QString& path = it.next();
- if (m_model->index(KUrl(path)) < 0) {
+ if (m_model->index(QUrl::fromLocalFile(path)) < 0) {
m_dirWatcher->removeDir(path);
it.remove();
}
void KDirectoryContentsCounter::startWorker(const QString& path)
{
+ if (s_cache->contains(path)) {
+ // fast path when in cache
+ // will be updated later if result has changed
+ const auto pair = s_cache->value(path);
+ emit result(path, pair.first, pair.second);
+ }
+
if (m_workerIsBusy) {
- m_queue.enqueue(path);
+ if (!m_queue.contains(path)) {
+ m_queue.append(path);
+ }
} else {
KDirectoryContentsCounterWorker::Options options;
m_workerIsBusy = true;
}
}
+
+QThread* KDirectoryContentsCounter::m_workerThread = nullptr;