Count the items inside directories in another thread

This prevents that the GUI freezes if there are many files inside the
directory, or if the access to the directory is slow for some other
reason.

BUG: 318518
REVIEW: 111920
FIXED-IN: 4.12.0
This commit is contained in:
Frank Reininghaus 2013-09-04 21:49:01 +02:00
parent 8f7c3619ec
commit 4bfc28cb4b
7 changed files with 442 additions and 124 deletions

View file

@ -58,6 +58,8 @@ set(dolphinprivate_LIB_SRCS
kitemviews/kstandarditemlistwidget.cpp
kitemviews/kstandarditemlistview.cpp
kitemviews/kstandarditemmodel.cpp
kitemviews/private/kdirectorycontentscounter.cpp
kitemviews/private/kdirectorycontentscounterworker.cpp
kitemviews/private/kfileitemclipboard.cpp
kitemviews/private/kfileitemmodeldirlister.cpp
kitemviews/private/kfileitemmodelfilter.cpp

View file

@ -24,13 +24,13 @@
#include <KConfig>
#include <KConfigGroup>
#include <KDebug>
#include <KDirWatch>
#include <KFileItem>
#include <KGlobal>
#include <KIO/JobUiDelegate>
#include <KIO/PreviewJob>
#include "private/kpixmapmodifier.h"
#include "private/kdirectorycontentscounter.h"
#include <QApplication>
#include <QPainter>
@ -46,14 +46,6 @@
#include <Nepomuk2/ResourceManager>
#endif
// Required includes for subItemsCount():
#ifdef Q_WS_WIN
#include <QDir>
#else
#include <dirent.h>
#include <QFile>
#endif
// #define KFILEITEMMODELROLESUPDATER_DEBUG
namespace {
@ -95,8 +87,7 @@ KFileItemModelRolesUpdater::KFileItemModelRolesUpdater(KFileItemModel* model, QO
m_recentlyChangedItemsTimer(0),
m_recentlyChangedItems(),
m_changedItems(),
m_dirWatcher(0),
m_watchedDirs()
m_directoryContentsCounter(0)
#ifdef HAVE_NEPOMUK
, m_nepomukResourceWatcher(0),
m_nepomukUriItems()
@ -135,10 +126,9 @@ KFileItemModelRolesUpdater::KFileItemModelRolesUpdater(KFileItemModel* model, QO
m_resolvableRoles += KNepomukRolesProvider::instance().roles();
#endif
// When folders are expandable or the item-count is shown for folders, it is necessary
// to watch the number of items of the sub-folder to be able to react on changes.
m_dirWatcher = new KDirWatch(this);
connect(m_dirWatcher, SIGNAL(dirty(QString)), this, SLOT(slotDirWatchDirty(QString)));
m_directoryContentsCounter = new KDirectoryContentsCounter(m_model, this);
connect(m_directoryContentsCounter, SIGNAL(result(QString,int)),
this, SLOT(slotDirectoryContentsCountReceived(QString,int)));
}
KFileItemModelRolesUpdater::~KFileItemModelRolesUpdater()
@ -367,25 +357,6 @@ void KFileItemModelRolesUpdater::slotItemsRemoved(const KItemRangeList& itemRang
const bool allItemsRemoved = (m_model->count() == 0);
if (!m_watchedDirs.isEmpty()) {
// Don't let KDirWatch watch for removed items
if (allItemsRemoved) {
foreach (const QString& path, m_watchedDirs) {
m_dirWatcher->removeDir(path);
}
m_watchedDirs.clear();
} else {
QMutableSetIterator<QString> it(m_watchedDirs);
while (it.hasNext()) {
const QString& path = it.next();
if (m_model->index(KUrl(path)) < 0) {
m_dirWatcher->removeDir(path);
it.remove();
}
}
}
}
#ifdef HAVE_NEPOMUK
if (m_nepomukResourceWatcher) {
// Don't let the ResourceWatcher watch for removed items
@ -783,7 +754,7 @@ void KFileItemModelRolesUpdater::applyChangedNepomukRoles(const Nepomuk2::Resour
#endif
}
void KFileItemModelRolesUpdater::slotDirWatchDirty(const QString& path)
void KFileItemModelRolesUpdater::slotDirectoryContentsCountReceived(const QString& path, int count)
{
const bool getSizeRole = m_roles.contains("size");
const bool getIsExpandableRole = m_roles.contains("isExpandable");
@ -791,16 +762,9 @@ void KFileItemModelRolesUpdater::slotDirWatchDirty(const QString& path)
if (getSizeRole || getIsExpandableRole) {
const int index = m_model->index(KUrl(path));
if (index >= 0) {
if (!m_model->fileItem(index).isDir()) {
// If INotify is used, KDirWatch issues the dirty() signal
// also for changed files inside the directory, even if we
// don't enable this behavior explicitly (see bug 309740).
return;
}
QHash<QByteArray, QVariant> data;
const int count = subItemsCount(path);
if (getSizeRole) {
data.insert("size", count);
}
@ -808,9 +772,11 @@ void KFileItemModelRolesUpdater::slotDirWatchDirty(const QString& path)
data.insert("isExpandable", count > 0);
}
// Note that we do not block the itemsChanged signal here.
// This ensures that a new preview will be generated.
disconnect(m_model, SIGNAL(itemsChanged(KItemRangeList,QSet<QByteArray>)),
this, SLOT(slotItemsChanged(KItemRangeList,QSet<QByteArray>)));
m_model->setData(index, data);
connect(m_model, SIGNAL(itemsChanged(KItemRangeList,QSet<QByteArray>)),
this, SLOT(slotItemsChanged(KItemRangeList,QSet<QByteArray>)));
}
}
}
@ -1037,7 +1003,7 @@ void KFileItemModelRolesUpdater::applySortRole(int index)
data.insert("type", item.mimeComment());
} else if (m_model->sortRole() == "size" && item.isLocalFile() && item.isDir()) {
const QString path = item.localPath();
data.insert("size", subItemsCount(path));
data.insert("size", m_directoryContentsCounter->countDirectoryContentsSynchronously(path));
} else {
// Probably the sort role is a Nepomuk role - just determine all roles.
data = rolesData(item);
@ -1105,7 +1071,7 @@ bool KFileItemModelRolesUpdater::applyResolvedRoles(const KFileItem& item, Resol
return false;
}
QHash<QByteArray, QVariant> KFileItemModelRolesUpdater::rolesData(const KFileItem& item) const
QHash<QByteArray, QVariant> KFileItemModelRolesUpdater::rolesData(const KFileItem& item)
{
QHash<QByteArray, QVariant> data;
@ -1114,19 +1080,10 @@ QHash<QByteArray, QVariant> KFileItemModelRolesUpdater::rolesData(const KFileIte
if ((getSizeRole || getIsExpandableRole) && item.isDir()) {
if (item.isLocalFile()) {
// Tell m_directoryContentsCounter that we want to count the items
// inside the directory. The result will be received in slotDirectoryContentsCountReceived.
const QString path = item.localPath();
const int count = subItemsCount(path);
if (getSizeRole) {
data.insert("size", count);
}
if (getIsExpandableRole) {
data.insert("isExpandable", count > 0);
}
if (!m_dirWatcher->contains(path)) {
m_dirWatcher->addDir(path);
m_watchedDirs.insert(path);
}
m_directoryContentsCounter->addDirectory(path);
} else if (getSizeRole) {
data.insert("size", -1); // -1 indicates an unknown number of items
}
@ -1170,61 +1127,6 @@ QHash<QByteArray, QVariant> KFileItemModelRolesUpdater::rolesData(const KFileIte
return data;
}
int KFileItemModelRolesUpdater::subItemsCount(const QString& path) const
{
const bool countHiddenFiles = m_model->showHiddenFiles();
const bool showFoldersOnly = m_model->showDirectoriesOnly();
#ifdef Q_WS_WIN
QDir dir(path);
QDir::Filters filters = QDir::NoDotAndDotDot | QDir::System;
if (countHiddenFiles) {
filters |= QDir::Hidden;
}
if (showFoldersOnly) {
filters |= QDir::Dirs;
} else {
filters |= QDir::AllEntries;
}
return dir.entryList(filters).count();
#else
// Taken from kdelibs/kio/kio/kdirmodel.cpp
// Copyright (C) 2006 David Faure <faure@kde.org>
int count = -1;
DIR* dir = ::opendir(QFile::encodeName(path));
if (dir) { // krazy:exclude=syscalls
count = 0;
struct dirent *dirEntry = 0;
while ((dirEntry = ::readdir(dir))) {
if (dirEntry->d_name[0] == '.') {
if (dirEntry->d_name[1] == '\0' || !countHiddenFiles) {
// Skip "." or hidden files
continue;
}
if (dirEntry->d_name[1] == '.' && dirEntry->d_name[2] == '\0') {
// Skip ".."
continue;
}
}
// If only directories are counted, consider an unknown file type and links also
// as directory instead of trying to do an expensive stat()
// (see bugs 292642 and 299997).
const bool countEntry = !showFoldersOnly ||
dirEntry->d_type == DT_DIR ||
dirEntry->d_type == DT_LNK ||
dirEntry->d_type == DT_UNKNOWN;
if (countEntry) {
++count;
}
}
::closedir(dir);
}
return count;
#endif
}
void KFileItemModelRolesUpdater::updateAllPreviews()
{
if (m_state == Paused) {

View file

@ -32,7 +32,7 @@
#include <QSize>
#include <QStringList>
class KDirWatch;
class KDirectoryContentsCounter;
class KFileItemModel;
class KJob;
class QPixmap;
@ -218,12 +218,7 @@ private slots:
void applyChangedNepomukRoles(const Nepomuk2::Resource& resource, const Nepomuk2::Types::Property& property);
/**
* Is invoked if a directory watched by KDirWatch got dirty. Updates
* the "isExpandable"- and "size"-roles of the item that matches to
* the given path.
*/
void slotDirWatchDirty(const QString& path);
void slotDirectoryContentsCountReceived(const QString& path, int count);
private:
/**
@ -267,7 +262,7 @@ private:
ResolveAll
};
bool applyResolvedRoles(const KFileItem& item, ResolveHint hint);
QHash<QByteArray, QVariant> rolesData(const KFileItem& item) const;
QHash<QByteArray, QVariant> rolesData(const KFileItem& item);
/**
* @return The number of items of the path \a path.
@ -349,9 +344,8 @@ private:
// Items which have not been changed repeatedly recently.
QSet<KFileItem> m_changedItems;
KDirWatch* m_dirWatcher;
mutable QSet<QString> m_watchedDirs; // Required as sadly KDirWatch does not offer a getter method
// to get all watched directories.
KDirectoryContentsCounter* m_directoryContentsCounter;
#ifdef HAVE_NEPOMUK
Nepomuk2::ResourceWatcher* m_nepomukResourceWatcher;
mutable QHash<QUrl, KUrl> m_nepomukUriItems;

View file

@ -0,0 +1,164 @@
/***************************************************************************
* Copyright (C) 2011 by Peter Penz <peter.penz19@gmail.com> *
* Copyright (C) 2013 by Frank Reininghaus <frank78ac@googlemail.com> *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program; if not, write to the *
* Free Software Foundation, Inc., *
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA *
***************************************************************************/
#include "kdirectorycontentscounter.h"
#include "kdirectorycontentscounterworker.h"
#include <kitemviews/kfileitemmodel.h>
#include <KDirWatch>
#include <QThread>
KDirectoryContentsCounter::KDirectoryContentsCounter(KFileItemModel* model, QObject* parent) :
QObject(parent),
m_model(model),
m_queue(),
m_workerThread(0),
m_worker(0),
m_workerIsBusy(false),
m_dirWatcher(0),
m_watchedDirs()
{
connect(m_model, SIGNAL(itemsRemoved(KItemRangeList)),
this, SLOT(slotItemsRemoved()));
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();
m_dirWatcher = new KDirWatch(this);
connect(m_dirWatcher, SIGNAL(dirty(QString)), this, SLOT(slotDirWatchDirty(QString)));
}
KDirectoryContentsCounter::~KDirectoryContentsCounter()
{
m_workerThread->quit();
m_workerThread->wait();
delete m_worker;
}
void KDirectoryContentsCounter::addDirectory(const QString& path)
{
startWorker(path);
}
int KDirectoryContentsCounter::countDirectoryContentsSynchronously(const QString& path)
{
if (!m_dirWatcher->contains(path)) {
m_dirWatcher->addDir(path);
m_watchedDirs.insert(path);
}
KDirectoryContentsCounterWorker::Options options;
if (m_model->showHiddenFiles()) {
options |= KDirectoryContentsCounterWorker::CountHiddenFiles;
}
if (m_model->showDirectoriesOnly()) {
options |= KDirectoryContentsCounterWorker::CountDirectoriesOnly;
}
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 (!m_queue.isEmpty()) {
startWorker(m_queue.dequeue());
}
emit result(path, count);
}
void KDirectoryContentsCounter::slotDirWatchDirty(const QString& path)
{
const int index = m_model->index(KUrl(path));
if (index >= 0) {
if (!m_model->fileItem(index).isDir()) {
// If INotify is used, KDirWatch issues the dirty() signal
// also for changed files inside the directory, even if we
// don't enable this behavior explicitly (see bug 309740).
return;
}
startWorker(path);
}
}
void KDirectoryContentsCounter::slotItemsRemoved()
{
const bool allItemsRemoved = (m_model->count() == 0);
if (!m_watchedDirs.isEmpty()) {
// Don't let KDirWatch watch for removed items
if (allItemsRemoved) {
foreach (const QString& path, m_watchedDirs) {
m_dirWatcher->removeDir(path);
}
m_watchedDirs.clear();
m_queue.clear();
} else {
QMutableSetIterator<QString> it(m_watchedDirs);
while (it.hasNext()) {
const QString& path = it.next();
if (m_model->index(KUrl(path)) < 0) {
m_dirWatcher->removeDir(path);
it.remove();
}
}
}
}
}
void KDirectoryContentsCounter::startWorker(const QString& path)
{
if (m_workerIsBusy) {
m_queue.enqueue(path);
} else {
KDirectoryContentsCounterWorker::Options options;
if (m_model->showHiddenFiles()) {
options |= KDirectoryContentsCounterWorker::CountHiddenFiles;
}
if (m_model->showDirectoriesOnly()) {
options |= KDirectoryContentsCounterWorker::CountDirectoriesOnly;
}
emit requestDirectoryContentsCount(path, options);
m_workerIsBusy = true;
}
}

View file

@ -0,0 +1,90 @@
/***************************************************************************
* Copyright (C) 2011 by Peter Penz <peter.penz19@gmail.com> *
* Copyright (C) 2013 by Frank Reininghaus <frank78ac@googlemail.com> *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program; if not, write to the *
* Free Software Foundation, Inc., *
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA *
***************************************************************************/
#ifndef KDIRECTORYCONTENTSCOUNTER_H
#define KDIRECTORYCONTENTSCOUNTER_H
#include "kdirectorycontentscounterworker.h"
#include <QSet>
#include <QQueue>
class KDirWatch;
class KFileItemModel;
class QString;
class KDirectoryContentsCounter : public QObject
{
Q_OBJECT
public:
explicit KDirectoryContentsCounter(KFileItemModel* model, QObject* parent = 0);
~KDirectoryContentsCounter();
/**
* Requests the number of items inside the directory \a path. The actual
* counting is done asynchronously, and the result is announced via the
* signal \a result.
*
* The directory \a path is watched for changes, and the signal is emitted
* again if a change occurs.
*/
void addDirectory(const QString& path);
/**
* In contrast to \a addDirectory, this function counts the items inside
* the directory \a path synchronously and returns the result.
*
* The directory is watched for changes, and the signal \a result is
* emitted if a change occurs.
*/
int countDirectoryContentsSynchronously(const QString& path);
signals:
/**
* Signals that the directory \a path contains \a count items.
*/
void result(const QString& path, int count);
void requestDirectoryContentsCount(const QString& path, KDirectoryContentsCounterWorker::Options options);
private slots:
void slotResult(const QString& path, int count);
void slotDirWatchDirty(const QString& path);
void slotItemsRemoved();
private:
void startWorker(const QString& path);
private:
KFileItemModel* m_model;
QQueue<QString> m_queue;
QThread* m_workerThread;
KDirectoryContentsCounterWorker* m_worker;
bool m_workerIsBusy;
KDirWatch* m_dirWatcher;
QSet<QString> m_watchedDirs; // Required as sadly KDirWatch does not offer a getter method
// to get all watched directories.
};
#endif

View file

@ -0,0 +1,95 @@
/***************************************************************************
* Copyright (C) 2011 by Peter Penz <peter.penz19@gmail.com> *
* Copyright (C) 2013 by Frank Reininghaus <frank78ac@googlemail.com> *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program; if not, write to the *
* Free Software Foundation, Inc., *
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA *
***************************************************************************/
#include "kdirectorycontentscounterworker.h"
// Required includes for subItemsCount():
#ifdef Q_WS_WIN
#include <QDir>
#else
#include <dirent.h>
#include <QFile>
#endif
KDirectoryContentsCounterWorker::KDirectoryContentsCounterWorker(QObject* parent) :
QObject(parent)
{
qRegisterMetaType<KDirectoryContentsCounterWorker::Options>();
}
int KDirectoryContentsCounterWorker::subItemsCount(const QString& path, Options options)
{
const bool countHiddenFiles = options & CountHiddenFiles;
const bool countDirectoriesOnly = options & CountDirectoriesOnly;
#ifdef Q_WS_WIN
QDir dir(path);
QDir::Filters filters = QDir::NoDotAndDotDot | QDir::System;
if (countHiddenFiles) {
filters |= QDir::Hidden;
}
if (countDirectoriesOnly) {
filters |= QDir::Dirs;
} else {
filters |= QDir::AllEntries;
}
return dir.entryList(filters).count();
#else
// Taken from kdelibs/kio/kio/kdirmodel.cpp
// Copyright (C) 2006 David Faure <faure@kde.org>
int count = -1;
DIR* dir = ::opendir(QFile::encodeName(path));
if (dir) { // krazy:exclude=syscalls
count = 0;
struct dirent *dirEntry = 0;
while ((dirEntry = ::readdir(dir))) {
if (dirEntry->d_name[0] == '.') {
if (dirEntry->d_name[1] == '\0' || !countHiddenFiles) {
// Skip "." or hidden files
continue;
}
if (dirEntry->d_name[1] == '.' && dirEntry->d_name[2] == '\0') {
// Skip ".."
continue;
}
}
// If only directories are counted, consider an unknown file type and links also
// as directory instead of trying to do an expensive stat()
// (see bugs 292642 and 299997).
const bool countEntry = !countDirectoriesOnly ||
dirEntry->d_type == DT_DIR ||
dirEntry->d_type == DT_LNK ||
dirEntry->d_type == DT_UNKNOWN;
if (countEntry) {
++count;
}
}
::closedir(dir);
}
return count;
#endif
}
void KDirectoryContentsCounterWorker::countDirectoryContents(const QString& path, Options options)
{
emit result(path, subItemsCount(path, options));
}

View file

@ -0,0 +1,71 @@
/***************************************************************************
* Copyright (C) 2013 by Frank Reininghaus <frank78ac@googlemail.com> *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program; if not, write to the *
* Free Software Foundation, Inc., *
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA *
***************************************************************************/
#ifndef KDIRECTORYCONTENTENTSCOUNTERWORKER_H
#define KDIRECTORYCONTENTENTSCOUNTERWORKER_H
#include <QFlags>
#include <QMetaType>
#include <QObject>
class QString;
class KDirectoryContentsCounterWorker : public QObject
{
Q_OBJECT
public:
enum Option {
NoOptions = 0x0,
CountHiddenFiles = 0x1,
CountDirectoriesOnly = 0x2
};
Q_DECLARE_FLAGS(Options, Option)
explicit KDirectoryContentsCounterWorker(QObject* parent = 0);
/**
* Counts the items inside the directory \a path using the options
* \a options.
*
* @return The number of items.
*/
static int subItemsCount(const QString& path, Options options);
signals:
/**
* Signals that the directory \a path contains \a count items.
*/
void result(const QString& path, int count);
public slots:
/**
* Requests the number of items inside the directory \a path using the
* options \a options. The result is announced via the signal \a result.
*/
// Note that the full type name KDirectoryContentsCounterWorker::Options
// is needed here. Just using 'Options' is OK for the compiler, but
// confuses moc.
void countDirectoryContents(const QString& path, KDirectoryContentsCounterWorker::Options options);
};
Q_DECLARE_METATYPE(KDirectoryContentsCounterWorker::Options)
Q_DECLARE_OPERATORS_FOR_FLAGS(KDirectoryContentsCounterWorker::Options)
#endif