Index: kdevplatform/interfaces/iprojectcontroller.h =================================================================== --- kdevplatform/interfaces/iprojectcontroller.h +++ kdevplatform/interfaces/iprojectcontroller.h @@ -117,6 +117,11 @@ */ static bool parseAllProjectSources(); + /** + * @returns whether project directories should be monitored for changes or not + */ + virtual bool watchAllProjectDirectories(); + public Q_SLOTS: /** * Tries finding a project-file for the given source-url and opens it. Index: kdevplatform/interfaces/iprojectcontroller.cpp =================================================================== --- kdevplatform/interfaces/iprojectcontroller.cpp +++ kdevplatform/interfaces/iprojectcontroller.cpp @@ -42,5 +42,12 @@ return group.readEntry( "Parse All Project Sources", true ); } +bool IProjectController::watchAllProjectDirectories() +{ + KConfigGroup group = ICore::self()->activeSession()->config()->group( "Project Manager" ); + return group.readEntry( "Monitor All Project Directories", true ); +} + + } 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.h =================================================================== --- kdevplatform/project/abstractfilemanagerplugin.h +++ kdevplatform/project/abstractfilemanagerplugin.h @@ -35,6 +35,8 @@ class AbstractFileManagerPluginPrivate; +class ProjectWatcher; + /** * This class can be used as a common base for file managers. * Index: kdevplatform/project/abstractfilemanagerplugin.cpp =================================================================== --- kdevplatform/project/abstractfilemanagerplugin.cpp +++ kdevplatform/project/abstractfilemanagerplugin.cpp @@ -27,6 +27,9 @@ #include #include #include +#ifdef TIME_IMPORT_JOB +#include +#endif #include #include @@ -38,6 +41,7 @@ #include #include "projectfiltermanager.h" +#include "projectwatcher.h" #include "debug.h" #define ifDebug(x) @@ -86,7 +90,7 @@ const KIO::UDSEntryList& entries); void deleted(const QString &path); - void created(const QString &path); + void dirty(const QString &path); void projectClosing(IProject* project); void jobFinished(KJob* job); @@ -100,7 +104,7 @@ void removeFolder(ProjectFolderItem* folder); - QHash m_watchers; + QHash m_watchers; QHash > m_projectJobs; QVector m_stoppedFolders; ProjectFilterManager m_filters; @@ -117,13 +121,26 @@ } m_projectJobs.remove(project); } - delete m_watchers.take(project); +#ifdef TIME_IMPORT_JOB + QElapsedTimer timer; + if (m_watchers.contains(project)) { + timer.start(); + } +#endif + delete m_watchers.value(project, nullptr); + m_watchers.remove(project); +#ifdef TIME_IMPORT_JOB + if (timer.isValid()) { + 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* listJob = new FileManagerListJob( item ); + ProjectWatcher* watcher = m_watchers.value( item->project(), nullptr ); + FileManagerListJob* listJob = new FileManagerListJob( item, watcher ); m_projectJobs[ item->project() ] << listJob; qCDebug(FILEMANAGER) << "adding job" << listJob << item << item->path() << "for project" << item->project(); @@ -247,17 +264,17 @@ } } -void AbstractFileManagerPluginPrivate::created(const QString& path_) +void AbstractFileManagerPluginPrivate::dirty(const QString& path_) { - qCDebug(FILEMANAGER) << "created:" << path_; + 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() ) { @@ -285,21 +302,6 @@ // 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 ); - } - } - } } } @@ -320,7 +322,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()) { @@ -388,9 +390,11 @@ if ( !folder->path().isLocalFile() ) { return; } - Q_ASSERT(m_watchers.contains(folder->project())); const QString path = folder->path().toLocalFile(); - m_watchers[folder->project()]->stopDirScan(path); + ProjectWatcher* watcher = m_watchers.value(folder->project(), nullptr); + if (watcher && watcher->contains(path)) { + m_watchers[folder->project()]->stopDirScan(path); + } m_stoppedFolders.append(path); } @@ -401,7 +405,12 @@ } Q_ASSERT(m_watchers.contains(folder->project())); const QString path = folder->path().toLocalFile(); - m_watchers[folder->project()]->restartDirScan(path); + if (!m_watchers[folder->project()]->restartDirScan(path)) { + // path wasn't being watched yet - should never happen + // (but can we be 100% certain of that?) + qCWarning(FILEMANAGER) << "Folder" << path << "in project" << folder->project()->name() << "wasn't yet being watched"; + m_watchers[folder->project()]->addDir(path); + } const int idx = m_stoppedFolders.indexOf(path); if (idx != -1) { m_stoppedFolders.remove(idx); @@ -431,6 +440,9 @@ job->removeSubDir(folder); } } + ProjectWatcher* watcher = m_watchers.value(folder->project(), nullptr); + Q_ASSERT(watcher); + watcher->removeDir(folder->path().toLocalFile()); folder->parent()->removeRow( folder->row() ); } @@ -472,14 +484,13 @@ ///TODO: check if this works for remote files when something gets changed through another KDE app if ( project->path().isLocalFile() ) { - d->m_watchers[project] = new KDirWatch( project ); + d->m_watchers[project] = new ProjectWatcher(project); - connect(d->m_watchers[project], &KDirWatch::created, - this, [&] (const QString& path_) { d->created(path_); }); + // set up the signal handling; feeding the dirwatcher is handled by FileManagerListJob. + connect(d->m_watchers[project], &KDirWatch::dirty, + this, [&] (const QString& path_) { d->dirty(path_); }); connect(d->m_watchers[project], &KDirWatch::deleted, this, [&] (const QString& path_) { d->deleted(path_); }); - - d->m_watchers[project]->addDir(project->path().toLocalFile(), KDirWatch::WatchSubDirs | KDirWatch:: WatchFiles ); } d->m_filters.add(project); Index: kdevplatform/project/filemanagerlistjob.h =================================================================== --- kdevplatform/project/filemanagerlistjob.h +++ kdevplatform/project/filemanagerlistjob.h @@ -33,13 +33,14 @@ namespace KDevelop { class ProjectFolderItem; + class ProjectWatcher; class FileManagerListJob : public KIO::Job { Q_OBJECT public: - explicit FileManagerListJob(ProjectFolderItem* item); + explicit FileManagerListJob(ProjectFolderItem* item, ProjectWatcher* watcher); ProjectFolderItem* item() const; void addSubDir(ProjectFolderItem* item); @@ -73,6 +74,7 @@ QElapsedTimer m_subTimer; qint64 m_subWaited = 0; #endif + ProjectWatcher* m_watcher; }; } Index: kdevplatform/project/filemanagerlistjob.cpp =================================================================== --- kdevplatform/project/filemanagerlistjob.cpp +++ kdevplatform/project/filemanagerlistjob.cpp @@ -18,6 +18,7 @@ */ #include "filemanagerlistjob.h" +#include "projectwatcher.h" #include #include @@ -28,10 +29,12 @@ #include #include +#include + using namespace KDevelop; -FileManagerListJob::FileManagerListJob(ProjectFolderItem* item) - : KIO::Job(), m_item(item), m_aborted(false) +FileManagerListJob::FileManagerListJob(ProjectFolderItem* item, ProjectWatcher* watcher) + : KIO::Job(), m_item(item), m_aborted(false), m_watcher(watcher) { qRegisterMetaType("KIO::UDSEntryList"); qRegisterMetaType(); @@ -95,12 +98,14 @@ if (m_aborted) { return; } + m_watcher->addDir(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), [this] (const QFileInfo& info) -> KIO::UDSEntry { KIO::UDSEntry entry; entry.insert(KIO::UDSEntry::UDS_NAME, info.fileName()); if (info.isDir()) { entry.insert(KIO::UDSEntry::UDS_FILE_TYPE, QT_STAT_DIR); + m_watcher->addDir(info.absoluteFilePath()); } if (info.isSymLink()) { entry.insert(KIO::UDSEntry::UDS_LINK_DEST, info.symLinkTarget()); Index: kdevplatform/project/projectwatcher.h =================================================================== --- /dev/null +++ kdevplatform/project/projectwatcher.h @@ -0,0 +1,43 @@ +/*************************************************************************** + * 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 + +namespace KDevelop { + +class ProjectWatcher : public KDirWatch +{ +public: + explicit ProjectWatcher(QObject* parent = nullptr); + virtual ~ProjectWatcher(); + + void addDir(const QString& path, WatchModes watchModes=WatchDirOnly); + void removeDir(const QString& path); + +private: + class Private; + Private* d; +}; + +} +#endif //KDEVPLATFORM_PROJECTWATCHER_H Index: kdevplatform/project/projectwatcher.cpp =================================================================== --- /dev/null +++ kdevplatform/project/projectwatcher.cpp @@ -0,0 +1,67 @@ +/*************************************************************************** + * 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 +#include + +#include + +using namespace KDevelop; + +class KDevelop::ProjectWatcher::Private +{ +public: + static QMutex m_barrier; +}; + +QMutex KDevelop::ProjectWatcher::Private::m_barrier; + +KDevelop::ProjectWatcher::ProjectWatcher(QObject* parent) + : KDirWatch(parent) + , d(new Private) +{} + +KDevelop::ProjectWatcher::~ProjectWatcher() +{ + delete d; +} + +void KDevelop::ProjectWatcher::addDir(const QString& path, WatchModes watchModes) +{ + // The QFileSystemWatcher backend doesn't like to be called + // too often/concurrently; prevent concurrent calls (can happen + // in trees with lots of few-element directories). + // (On Mac, this improves speeds, on Linux I've seen the occasional + // memory allocation issue without this barrier.) + QMutexLocker lower(&Private::m_barrier); + if (!contains(path)) { + KDirWatch::addDir(path, watchModes); + } +} + +void KDevelop::ProjectWatcher::removeDir(const QString& path) +{ + QMutexLocker lower(&Private::m_barrier); + if (contains(path)) { + KDirWatch::removeDir(path); + } +} Index: kdevplatform/shell/settings/projectconfig.kcfg =================================================================== --- kdevplatform/shell/settings/projectconfig.kcfg +++ kdevplatform/shell/settings/projectconfig.kcfg @@ -23,5 +23,10 @@ If this option is set all open documents will be automatically saved before any build is started. + + true + + If this option is set, KDevelop will monitor all project directories for changes. This keeps the overview in the Projects toolview in sync with the directory on disk. It can also really slow down the import of very big projects and consume considerable resources. + Index: kdevplatform/shell/settings/projectpreferences.ui =================================================================== --- kdevplatform/shell/settings/projectpreferences.ui +++ kdevplatform/shell/settings/projectpreferences.ui @@ -62,6 +62,16 @@ + + + Monitor all directories of a project for changes. + + + Monitor all project directories for changes + + + + Qt::Vertical