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); + } +}