Index: kdevplatform/project/CMakeLists.txt =================================================================== --- kdevplatform/project/CMakeLists.txt +++ kdevplatform/project/CMakeLists.txt @@ -15,6 +15,7 @@ abstractfilemanagerplugin.cpp filemanagerlistjob.cpp projectfiltermanager.cpp + projectwatcher.cpp interfaces/iprojectbuilder.cpp interfaces/iprojectfilemanager.cpp interfaces/ibuildsystemmanager.cpp Index: kdevplatform/project/abstractfilemanagerplugin.cpp =================================================================== --- kdevplatform/project/abstractfilemanagerplugin.cpp +++ kdevplatform/project/abstractfilemanagerplugin.cpp @@ -1,3 +1,4 @@ +#define TIME_IMPORT_JOB /*************************************************************************** * This file is part of KDevelop * * Copyright 2010-2012 Milian Wolff * @@ -41,6 +42,7 @@ #include #include "projectfiltermanager.h" +#include "projectwatcher.h" #include "debug.h" #define ifDebug(x) @@ -74,6 +76,7 @@ public: explicit AbstractFileManagerPluginPrivate(AbstractFileManagerPlugin* qq) : q(qq) + , m_intreeDirWatching(qEnvironmentVariableIsSet("KDEV_PROJECT_INTREE_DIRWATCHING_MODE")) { } @@ -83,13 +86,13 @@ * The just returned must be started in one way or another for this method * to have any affect. The job will then auto-delete itself upon completion. */ - Q_REQUIRED_RESULT KIO::Job* eventuallyReadFolder(ProjectFolderItem* item); + Q_REQUIRED_RESULT FileManagerListJob* eventuallyReadFolder(ProjectFolderItem* item, bool recursive = true); void addJobItems(FileManagerListJob* job, ProjectFolderItem* baseItem, const KIO::UDSEntryList& entries); void deleted(const QString &path); - void created(const QString &path); + void dirty(const QString &path, bool isCreated = false); void projectClosing(IProject* project); void jobFinished(KJob* job); @@ -103,22 +106,28 @@ void removeFolder(ProjectFolderItem* folder); - QHash m_watchers; + QHash m_watchers; QHash > m_projectJobs; QVector m_stoppedFolders; ProjectFilterManager m_filters; + // intree dirwatching is the original mode that feeds all files + // and directories to the dirwatcher. It takes longer to load but + // works better for certain (large) projects that use in-tree builds. + bool m_intreeDirWatching; }; void AbstractFileManagerPluginPrivate::projectClosing(IProject* project) { if ( m_projectJobs.contains(project) ) { // make sure the import job does not live longer than the project // see also addLotsOfFiles test - foreach( FileManagerListJob* job, m_projectJobs[project] ) { + auto jobList = m_projectJobs[project]; + m_projectJobs.remove(project); + foreach( FileManagerListJob* job, jobList ) { qCDebug(FILEMANAGER) << "killing project job:" << job; job->abort(); } - m_projectJobs.remove(project); + jobList.clear(); } #ifdef TIME_IMPORT_JOB QElapsedTimer timer; @@ -129,34 +138,83 @@ delete m_watchers.take(project); #ifdef TIME_IMPORT_JOB if (timer.isValid()) { - qCDebug(FILEMANAGER) << "Deleting dir watcher took" << timer.elapsed() / 1000.0 << "seconds for project" << project->name(); + qCInfo(FILEMANAGER) << "Deleting dir watcher took" << timer.elapsed() / 1000.0 << "seconds for project" << project->name(); } #endif m_filters.remove(project); } -KIO::Job* AbstractFileManagerPluginPrivate::eventuallyReadFolder(ProjectFolderItem* item) +FileManagerListJob* AbstractFileManagerPluginPrivate::eventuallyReadFolder(ProjectFolderItem* item, bool recursive) { - FileManagerListJob* listJob = new FileManagerListJob( item ); - m_projectJobs[ item->project() ] << listJob; - qCDebug(FILEMANAGER) << "adding job" << listJob << item << item->path() << "for project" << item->project(); + IProject* project = item->project(); + ProjectWatcher* watcher = m_watchers.value( project, nullptr ); + + // Before we create a new list job it's a good idea to + // walk the list backwards, checking for duplicates and aborting + // any previously started jobs loading the same directory. + const auto jobList = QList(m_projectJobs[project]); + auto jobListIt = jobList.constEnd(); + auto jobListHead = jobList.constBegin(); + const auto path = item->path().path(); + while (jobListIt != jobListHead) { + auto job = *(--jobListIt); + // check the other jobs that are still running (baseItem() != NULL) + // abort them if justified, but only if marked as disposable (= jobs + // started in reaction to a KDirWatch change notification). + if (job->basePath().isValid() && (job->isDisposable() || !job->started())) { + const auto jobPath = job->basePath().path(); + if (jobPath == path || (job->isRecursive() && jobPath.startsWith(path))) { + // this job is already reloading @p item or one of its subdirs: abort it + // because the new job will provide a more up-to-date representation. + // Annoyingly pure watching of only directory change often gives multiple + // dirty signals in succession. + qCDebug(FILEMANAGER) << "aborting old job" << job << "before starting job for" << item->path(); + m_projectJobs[project].removeOne(job); + job->abort(); + } else if (job->itemQueue().contains(item)) { + job->removeSubDir(item); + qCDebug(FILEMANAGER) << "unqueueing reload of old job" << job << "before starting one for" << item->path(); + } else if (job->item() == item) { + qCWarning(FILEMANAGER) << "old job" << job << "is already reloading" << item->path(); + if (job->started()) { + // not much more we can do here, we have to return a valid FileManagerListJob. + job->setRecursive(false); + } else { + job->abort(); + } + } + } + } + + // FileManagerListJob detects KDEV_PROJECT_INTREE_DIRWATCHING_MODE itself + // this is safe as long as it's not feasible to change our own env. variables + FileManagerListJob* listJob = new FileManagerListJob( item, recursive ); + m_projectJobs[ project ] << listJob; + qCDebug(FILEMANAGER) << "adding job" << listJob << item << item->path() << "for project" << project; q->connect( listJob, &FileManagerListJob::finished, q, [&] (KJob* job) { jobFinished(job); } ); q->connect( listJob, &FileManagerListJob::entries, q, [&] (FileManagerListJob* job, ProjectFolderItem* baseItem, const KIO::UDSEntryList& entries) { addJobItems(job, baseItem, entries); } ); + if (!m_intreeDirWatching) { + q->connect( listJob, &FileManagerListJob::watchDir, + watcher, [watcher] (const QString& path) { + watcher->addDir(path); } ); + } + // set a relevant name (for listing in the runController) + listJob->setObjectName(i18n("Reload of %1:%2", project->name(), item->path().toLocalFile())); return listJob; } void AbstractFileManagerPluginPrivate::jobFinished(KJob* job) { FileManagerListJob* gmlJob = qobject_cast(job); - if (gmlJob) { + if (gmlJob && gmlJob->project()) { ifDebug(qCDebug(FILEMANAGER) << job << gmlJob << gmlJob->item();) - m_projectJobs[ gmlJob->item()->project() ].removeOne( gmlJob ); + m_projectJobs[ gmlJob->project() ].removeOne( gmlJob ); } else { // job emitted its finished signal from its destructor // ensure we don't keep a dangling point in our list @@ -252,22 +310,27 @@ ProjectFolderItem* folder = q->createFolderItem( baseItem->project(), path, baseItem ); if (folder) { emit q->folderAdded( folder ); - job->addSubDir( folder ); + // new folder, so ignore the job's recursion setting. + job->addSubDir( folder, true ); } } } -void AbstractFileManagerPluginPrivate::created(const QString& path_) +void AbstractFileManagerPluginPrivate::dirty(const QString& path_, bool isCreated) { - qCDebug(FILEMANAGER) << "created:" << path_; + if (isCreated) { + qCDebug(FILEMANAGER) << "created:" << path_; + } else { + qCDebug(FILEMANAGER) << "dirty:" << path_; + } QFileInfo info(path_); ///FIXME: share memory with parent const Path path(path_); const IndexedString indexedPath(path.pathOrUrl()); const IndexedString indexedParent(path.parent().pathOrUrl()); - QHashIterator it(m_watchers); + QHashIterator it(m_watchers); while (it.hasNext()) { const auto p = it.next().key(); if ( !p->projectItem()->model() ) { @@ -283,9 +346,15 @@ foreach ( ProjectFolderItem* folder, p->foldersForPath(indexedPath) ) { // exists already in this project, happens e.g. when we restart the dirwatcher // or if we delete and remove folders consecutively https://bugs.kde.org/show_bug.cgi?id=260741 + // or when a change is signalled in the directory contents. qCDebug(FILEMANAGER) << "force reload of" << path << folder; - auto job = eventuallyReadFolder( folder ); - job->start(); + // schedule a reload, a priori non-recursively because we have already been + // listed and the current change notification is not for one of our subfolders. + // The recursion setting will be overridden if the change notification is for + // newly created directories. + auto job = eventuallyReadFolder( folder, false ); + job->setDisposable(true); + job->start(1000); found = true; } if ( found ) { @@ -295,18 +364,21 @@ // also gets triggered for kate's backup files continue; } - foreach ( ProjectFolderItem* parentItem, p->foldersForPath(indexedParent) ) { - if ( info.isDir() ) { - ProjectFolderItem* folder = q->createFolderItem( p, path, parentItem ); - if (folder) { - emit q->folderAdded( folder ); - auto job = eventuallyReadFolder( folder ); - job->start(); - } - } else { - ProjectFileItem* file = q->createFileItem( p, path, parentItem ); - if (file) { - emit q->fileAdded( file ); + if (isCreated) { + foreach ( ProjectFolderItem* parentItem, p->foldersForPath(indexedParent) ) { + if ( info.isDir() ) { + ProjectFolderItem* folder = q->createFolderItem( p, path, parentItem ); + if (folder) { + emit q->folderAdded( folder ); + auto job = eventuallyReadFolder( folder ); + job->setDisposable(true); + job->start(1000); + } + } else { + ProjectFileItem* file = q->createFileItem( p, path, parentItem ); + if (file) { + emit q->fileAdded( file ); + } } } } @@ -330,7 +402,7 @@ const Path path(QUrl::fromLocalFile(path_)); const IndexedString indexed(path.pathOrUrl()); - QHashIterator it(m_watchers); + QHashIterator it(m_watchers); while (it.hasNext()) { const auto p = it.next().key(); if (path == p->path()) { @@ -440,12 +512,17 @@ foreach(FileManagerListJob* job, m_projectJobs[folder->project()]) { if (isChildItem(folder, job->item())) { qCDebug(FILEMANAGER) << "killing list job for removed folder" << job << folder->path(); + m_projectJobs[folder->project()].removeOne(job); job->abort(); - Q_ASSERT(!m_projectJobs.value(folder->project()).contains(job)); } else { job->removeSubDir(folder); } } + if (!m_intreeDirWatching && folder->path().isLocalFile()) { + ProjectWatcher* watcher = m_watchers.value(folder->project(), nullptr); + Q_ASSERT(watcher); + watcher->removeDir(folder->path().toLocalFile()); + } folder->parent()->removeRow( folder->row() ); } @@ -487,14 +564,19 @@ ///TODO: check if this works for remote files when something gets changed through another KDE app if ( project->path().isLocalFile() ) { - auto watcher = new KDirWatch( project ); + auto watcher = new ProjectWatcher(project); - // set up the signal handling - connect(watcher, &KDirWatch::created, - this, [&] (const QString& path_) { d->created(path_); }); + // set up the signal handling; feeding the dirwatcher is handled by FileManagerListJob. connect(watcher, &KDirWatch::deleted, this, [&] (const QString& path_) { d->deleted(path_); }); - watcher->addDir(project->path().toLocalFile(), KDirWatch::WatchSubDirs | KDirWatch:: WatchFiles ); + if (d->m_intreeDirWatching) { + connect(watcher, &KDirWatch::created, + this, [&] (const QString& path_) { d->dirty(path_, true); }); + watcher->addDir(project->path().toLocalFile(), KDirWatch::WatchSubDirs | KDirWatch:: WatchFiles ); + } else { + connect(watcher, &KDirWatch::dirty, + this, [&] (const QString& path_) { d->dirty(path_); }); + } d->m_watchers[project] = watcher; } Index: kdevplatform/project/filemanagerlistjob.h =================================================================== --- kdevplatform/project/filemanagerlistjob.h +++ kdevplatform/project/filemanagerlistjob.h @@ -30,28 +30,59 @@ #include #endif +#include "path.h" + namespace KDevelop { class ProjectFolderItem; + class RunControllerProxy; + class IProject; class FileManagerListJob : public KIO::Job { Q_OBJECT public: - explicit FileManagerListJob(ProjectFolderItem* item); + /** + * KIO::Job variant that lists the files in a project folder. The list + * is generated recursively unless @a recursive==false and the folder + * has already been indexed. + **/ + explicit FileManagerListJob(ProjectFolderItem* item, bool recursive = true); + virtual ~FileManagerListJob(); ProjectFolderItem* item() const; + IProject* project() const; + QQueue itemQueue() const; + Path basePath() const; - void addSubDir(ProjectFolderItem* item); + void addSubDir(ProjectFolderItem* item, bool forceRecursive = false); void removeSubDir(ProjectFolderItem* item); void abort(); void start() override; + void start(int msDelay); + bool started() const { return m_started; } + + /** + * will/should this job recurse over all subdirs? + */ + bool isRecursive() const { return m_recursive; } + void setRecursive(bool enabled); + + /** + * Is/should this be a job that can be aborted, for + * instance when it's not been started on the user's + * explicit request? + * Jobs are never disposable by default. + */ + bool isDisposable() const { return m_disposable; } + void setDisposable(bool enabled); Q_SIGNALS: void entries(FileManagerListJob* job, ProjectFolderItem* baseItem, const KIO::UDSEntryList& entries); void nextJob(); + void watchDir(const QString& path); private Q_SLOTS: void slotEntries(KIO::Job* job, const KIO::UDSEntryList& entriesIn ); @@ -64,6 +95,10 @@ QQueue m_listQueue; /// current base dir ProjectFolderItem* m_item; + /// current project + IProject* m_project; + /// entrypoint + Path m_basePath; KIO::UDSEntryList entryList; // kill does not delete the job instantaniously QAtomicInt m_aborted; @@ -73,6 +108,14 @@ QElapsedTimer m_subTimer; qint64 m_subWaited = 0; #endif + bool m_emitWatchDir; + int m_killCount = 0; + bool m_recursive; + bool m_started; + bool m_disposable; +protected: + RunControllerProxy* m_rcProxy; + friend class RunControllerProxy; }; } Index: kdevplatform/project/filemanagerlistjob.cpp =================================================================== --- kdevplatform/project/filemanagerlistjob.cpp +++ kdevplatform/project/filemanagerlistjob.cpp @@ -24,16 +24,75 @@ #include "path.h" #include "debug.h" +#include "indexedstring.h" // KF #include // Qt #include #include +#include + +#include +#include using namespace KDevelop; -FileManagerListJob::FileManagerListJob(ProjectFolderItem* item) - : KIO::Job(), m_item(item), m_aborted(false) +class KDevelop::RunControllerProxy : public KJob +{ +public: + RunControllerProxy(FileManagerListJob* proxied) + : KJob() + , m_proxied(proxied) + { + setCapabilities(KJob::Killable); + setObjectName(proxied->objectName()); + ICore::self()->runController()->registerJob(this); + } + + ~RunControllerProxy() + { + if (m_proxied) { + m_proxied->m_rcProxy = nullptr; + done(); + } + } + + // we don't run anything ourselves + void start() + {} + + bool doKill() + { + if (m_proxied) { + qCWarning(PROJECT) << "Aborting" << m_proxied; + auto proxied = m_proxied; + done(); + proxied->abort(); + } + return true; + } + + void done() + { + if (m_proxied) { + ICore::self()->runController()->unregisterJob(this); + m_proxied = nullptr; + } + } + + FileManagerListJob* m_proxied; +}; + +FileManagerListJob::FileManagerListJob(ProjectFolderItem* item, bool recursive) + : KIO::Job(), m_item(item), m_project(item->project()) + // cache *a copy* of the item info, without parent info so we own it completely + , m_basePath(item->path()) + , m_aborted(false) + , m_emitWatchDir(!qEnvironmentVariableIsSet("KDEV_PROJECT_INTREE_DIRWATCHING_MODE")) + , m_recursive(true) // sic! + , m_started(false) + , m_disposable(false) + , m_rcProxy(nullptr) { qRegisterMetaType("KIO::UDSEntryList"); qRegisterMetaType(); @@ -45,27 +104,68 @@ connect( this, &FileManagerListJob::nextJob, this, &FileManagerListJob::startNextJob, Qt::QueuedConnection ); addSubDir(item); + // now we can set m_recursive to the requested value + m_recursive = recursive; #ifdef TIME_IMPORT_JOB m_timer.start(); #endif } +FileManagerListJob::~FileManagerListJob() +{ + if (m_rcProxy) { + m_rcProxy->done(); + m_rcProxy->deleteLater(); + } + m_item = nullptr; + m_project = nullptr; + m_rcProxy = nullptr; + m_basePath = Path(); +} + ProjectFolderItem* FileManagerListJob::item() const { return m_item; } -void FileManagerListJob::addSubDir( ProjectFolderItem* item ) +IProject* FileManagerListJob::project() const +{ + return m_project; +} + +QQueue FileManagerListJob::itemQueue() const +{ + return m_listQueue; +} + +Path FileManagerListJob::basePath() const +{ + return m_basePath; +} + +void FileManagerListJob::addSubDir( ProjectFolderItem* item, bool forceRecursive ) { + if (m_aborted) { + return; + } + if (!m_recursive && !forceRecursive) { + qCDebug(FILEMANAGER) << "Ignoring known subdir" << item->path() << "because non-recursive"; + return; + } + Q_ASSERT(!m_listQueue.contains(item)); Q_ASSERT(!m_item || m_item == item || m_item->path().isDirectParentOf(item->path())); m_listQueue.enqueue(item); } void FileManagerListJob::removeSubDir(ProjectFolderItem* item) { + if (m_aborted) { + return; + } + m_listQueue.removeAll(item); } @@ -86,6 +186,7 @@ #endif m_item = m_listQueue.dequeue(); + m_project = m_item->project(); if (m_item->path().isLocalFile()) { // optimized version for local projects using QDir directly QtConcurrent::run([this] (const Path& path) { @@ -97,8 +198,12 @@ if (m_aborted) { return; } + if (m_emitWatchDir) { + // signal that this directory has to be watched + emit watchDir(path.toLocalFile()); + } KIO::UDSEntryList results; - std::transform(entries.begin(), entries.end(), std::back_inserter(results), [] (const QFileInfo& info) -> KIO::UDSEntry { + std::transform(entries.begin(), entries.end(), std::back_inserter(results), [=] (const QFileInfo& info) -> KIO::UDSEntry { KIO::UDSEntry entry; #if KIO_VERSION < QT_VERSION_CHECK(5,48,0) entry.insert(KIO::UDSEntry::UDS_NAME, info.fileName()); @@ -121,7 +226,9 @@ } return entry; }); - QMetaObject::invokeMethod(this, "handleResults", Q_ARG(KIO::UDSEntryList, results)); + if (!m_aborted) { + QMetaObject::invokeMethod(this, "handleResults", Q_ARG(KIO::UDSEntryList, results)); + } }, m_item->path()); } else { KIO::ListJob* job = KIO::listDir( m_item->path().toUrl(), KIO::HideProgressInfo ); @@ -165,7 +272,13 @@ emit entries(this, m_item, entriesIn); if( m_listQueue.isEmpty() ) { + m_basePath = Path(); emitResult(); + if (m_rcProxy) { + m_rcProxy->done(); + } + m_item = nullptr; + m_project = nullptr; #ifdef TIME_IMPORT_JOB qCDebug(PROJECT) << "TIME FOR LISTJOB:" << m_timer.elapsed(); @@ -178,13 +291,56 @@ void FileManagerListJob::abort() { m_aborted = true; + m_listQueue.clear(); + + if (m_rcProxy) { + m_rcProxy->done(); + } + + m_item = nullptr; + m_project = nullptr; + m_basePath = Path(); bool killed = kill(); + m_killCount += 1; + if (!killed) { + // let's check if kill failures aren't because of trying to kill a corpse + qCWarning(PROJECT) << "failed to kill" << this << "kill count=" << m_killCount; + } Q_ASSERT(killed); Q_UNUSED(killed); } +void FileManagerListJob::start(int msDelay) +{ + if (m_aborted) { + return; + } + if (msDelay > 0) { + QTimer::singleShot(msDelay, this, SLOT(start())); + } else { + start(); + } +} + void FileManagerListJob::start() { + if (m_aborted) { + return; + } + if (!m_rcProxy) { + m_rcProxy = new RunControllerProxy(this); + } startNextJob(); + m_started = true; +} + +void FileManagerListJob::setRecursive(bool enabled) +{ + m_recursive = enabled; +} + +void FileManagerListJob::setDisposable(bool enabled) +{ + m_disposable = enabled; } Index: kdevplatform/project/projectmodel.cpp =================================================================== --- kdevplatform/project/projectmodel.cpp +++ kdevplatform/project/projectmodel.cpp @@ -414,7 +414,7 @@ IProject* ProjectBaseItem::project() const { Q_D(const ProjectBaseItem); - return d->project; + return d ? d->project : nullptr; } void ProjectBaseItem::appendRow( ProjectBaseItem* item ) @@ -447,7 +447,7 @@ Path ProjectBaseItem::path() const { Q_D(const ProjectBaseItem); - return d->m_path; + return d ? d->m_path : Path(); } QString ProjectBaseItem::baseName() const Index: kdevplatform/project/projectwatcher.h =================================================================== --- /dev/null +++ kdevplatform/project/projectwatcher.h @@ -0,0 +1,66 @@ +/*************************************************************************** + * This file is part of KDevelop * + * Copyright 2017 René Bertin * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Library 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 Library 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 KDEVPLATFORM_PROJECTWATCHER_H +#define KDEVPLATFORM_PROJECTWATCHER_H + +#include "projectexport.h" + +#include + +namespace KDevelop { + +class IProject; +class ProjectFilterManager; + +class KDEVPLATFORMPROJECT_EXPORT ProjectWatcher : public KDirWatch +{ + Q_OBJECT +public: + /** + * Create a dirwatcher for @p project based on KDirWatch but + * that will add or remove each directory only once. + */ + explicit ProjectWatcher(IProject* project); + + /** + * Add directory @p path to the project dirwatcher if it is not + * already being watched. + */ + void addDir(const QString& path, WatchModes watchModes = WatchDirOnly); + void removeDir(const QString& path); + /** + * Add file @p file to the project dirwatcher if it is not + * already being watched. + */ + void addFile(const QString& file); + void removeFile(const QString& file); + + /** + * return the current number of directories being watched. + */ + int size() const; + +private: + int m_watchedCount; +}; + +} +#endif //KDEVPLATFORM_PROJECTWATCHER_H Index: kdevplatform/project/projectwatcher.cpp =================================================================== --- /dev/null +++ kdevplatform/project/projectwatcher.cpp @@ -0,0 +1,81 @@ +/*************************************************************************** + * This file is part of KDevelop * + * Copyright 2017 René Bertin * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Library 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 Library 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 "projectwatcher.h" +#include "iproject.h" +#include "projectfiltermanager.h" +#include "path.h" + +#include +#include + +#include + +using namespace KDevelop; + +KDevelop::ProjectWatcher::ProjectWatcher(IProject* project) + : KDirWatch(project) + , m_watchedCount(0) +{ + if (QCoreApplication::instance()) { + // stop monitoring project directories when the IDE is about to quit + // triggering a full project reload just before closing would be counterproductive. + connect(QCoreApplication::instance(), &QCoreApplication::aboutToQuit, this, &KDirWatch::stopScan); + } +} + +void KDevelop::ProjectWatcher::addDir(const QString& path, WatchModes watchModes) +{ + if (!contains(path)) { + KDirWatch::addDir(path, watchModes); + m_watchedCount += 1; + } +} + +void KDevelop::ProjectWatcher::removeDir(const QString& path) +{ + if (contains(path)) { + KDirWatch::removeDir(path); + m_watchedCount -= 1; + } +} + +void KDevelop::ProjectWatcher::addFile(const QString& file) +{ + if (!contains(file)) { + qWarning() << "KDW::addFile" << file; + KDirWatch::addFile(file); + m_watchedCount += 1; + } +} + +void KDevelop::ProjectWatcher::removeFile(const QString& file) +{ + if (contains(file)) { + KDirWatch::removeFile(file); + m_watchedCount -= 1; + } +} + +int KDevelop::ProjectWatcher::size() const +{ + return m_watchedCount; +} + Index: kdevplatform/project/tests/abstractfilemanagerpluginimportbenchmark.cpp =================================================================== --- kdevplatform/project/tests/abstractfilemanagerpluginimportbenchmark.cpp +++ kdevplatform/project/tests/abstractfilemanagerpluginimportbenchmark.cpp @@ -23,6 +23,7 @@ #include #include +#include #include @@ -110,8 +111,14 @@ { Q_UNUSED(job); int elapsed = m_timer.elapsed(); + // The number of items in the dirwatcher can be obtained as follows, + // provided dirs are added one by one and not with a recursive call + // to ProjectWatcher::addDir(): + ProjectWatcher* watcher = qobject_cast(m_manager->projectWatcher(m_project)); + int watched = watcher ? watcher->size() : -1; m_out << "importing " << m_project->fileSet().size() << " items into project #" << m_projectNumber + << " with " << watched << " watched directories" << " took " << elapsed / 1000.0 << " seconds" << endl; s_numBenchmarksRunning -= 1; @@ -166,6 +173,20 @@ << " seconds total\n"; QCoreApplication::instance()->quit(); }); + if (qEnvironmentVariableIsSet("BENCHMARK_ORIGINAL_DIRWATCHER")) { + // benchmark the creation and deletion of the original dirwatcher: + KDirWatch *watcher = new KDirWatch(benchmark->m_project); + qout << "Benchmarking KDirWatch for all of " << argv[i]; + benchmark->m_timer.start(); + watcher->addDir(benchmark->m_project->path().toLocalFile(), KDirWatch::WatchSubDirs | KDirWatch:: WatchFiles ); + int elapsed = benchmark->m_timer.elapsed(); + qout << "\tfeeding the watcher: " << elapsed / 1000.0 << " seconds\n"; + benchmark->m_timer.restart(); + delete watcher; + elapsed = benchmark->m_timer.elapsed(); + qout << "\tdeleting the watcher: " << elapsed / 1000.0 << " seconds\n"; + qout.flush(); + } } }