File indexing completed on 2024-04-28 04:37:03

0001 /*
0002     SPDX-FileCopyrightText: 2010-2012 Milian Wolff <mail@milianw.de>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 #include "abstractfilemanagerplugin.h"
0008 
0009 #include "filemanagerlistjob.h"
0010 #include "projectmodel.h"
0011 #include "helper.h"
0012 
0013 #include <QHashIterator>
0014 #include <QFileInfo>
0015 #include <QApplication>
0016 #include <QTimer>
0017 #ifdef TIME_IMPORT_JOB
0018 #include <QElapsedTimer>
0019 #endif
0020 
0021 #include <KMessageBox>
0022 #include <KLocalizedString>
0023 #include <KDirWatch>
0024 
0025 #include <interfaces/iproject.h>
0026 #include <interfaces/icore.h>
0027 #include <interfaces/iprojectcontroller.h>
0028 #include <serialization/indexedstring.h>
0029 
0030 #include "projectfiltermanager.h"
0031 #include "debug.h"
0032 
0033 #define ifDebug(x)
0034 
0035 using namespace KDevelop;
0036 
0037 //BEGIN Helper
0038 
0039 namespace {
0040 
0041 /**
0042  * Returns the parent folder item for a given item or the project root item if there is no parent.
0043  */
0044 ProjectFolderItem* parentFolder(ProjectBaseItem* item)
0045 {
0046     if ( item->parent() ) {
0047         return static_cast<ProjectFolderItem*>(item->parent());
0048     } else {
0049         return item->project()->projectItem();
0050     }
0051 }
0052 
0053 }
0054 
0055 //END Helper
0056 
0057 //BEGIN Private
0058 
0059 class KDevelop::AbstractFileManagerPluginPrivate
0060 {
0061 public:
0062     explicit AbstractFileManagerPluginPrivate(AbstractFileManagerPlugin* qq)
0063         : q(qq)
0064     {
0065     }
0066 
0067     AbstractFileManagerPlugin* q;
0068 
0069     /**
0070      * The just returned must be started in one way or another for this method
0071      * to have any affect. The job will then auto-delete itself upon completion.
0072      */
0073     [[nodiscard]] KJob* eventuallyReadFolder(ProjectFolderItem* item);
0074     void addJobItems(FileManagerListJob* job,
0075                      ProjectFolderItem* baseItem,
0076                      const KIO::UDSEntryList& entries);
0077 
0078     void deleted(const QString &path);
0079     void created(const QString &path);
0080 
0081     void projectClosing(IProject* project);
0082     void jobFinished(KJob* job);
0083 
0084     /// Stops watching the given folder for changes, only useful for local files.
0085     void stopWatcher(ProjectFolderItem* folder);
0086     /// Continues watching the given folder for changes.
0087     void continueWatcher(ProjectFolderItem* folder);
0088     /// Common renaming function.
0089     bool rename(ProjectBaseItem* item, const Path& newPath);
0090 
0091     QHash<IProject*, KDirWatch*> m_watchers;
0092     QHash<IProject*, QList<FileManagerListJob*> > m_projectJobs;
0093     QVector<QString> m_stoppedFolders;
0094     ProjectFilterManager m_filters;
0095 };
0096 
0097 void AbstractFileManagerPluginPrivate::projectClosing(IProject* project)
0098 {
0099     const auto projectJobIt = m_projectJobs.constFind(project);
0100     if (projectJobIt != m_projectJobs.constEnd()) {
0101         // make sure the import job does not live longer than the project
0102         // see also addLotsOfFiles test
0103         for (FileManagerListJob* job : *projectJobIt) {
0104             qCDebug(FILEMANAGER) << "killing project job:" << job;
0105             job->kill();
0106         }
0107         m_projectJobs.remove(project);
0108     }
0109 #ifdef TIME_IMPORT_JOB
0110     QElapsedTimer timer;
0111     if (m_watchers.contains(project)) {
0112         timer.start();
0113     }
0114 #endif
0115     delete m_watchers.take(project);
0116 #ifdef TIME_IMPORT_JOB
0117     if (timer.isValid()) {
0118         qCDebug(FILEMANAGER) << "Deleting dir watcher took" << timer.elapsed() / 1000.0 << "seconds for project" << project->name();
0119     }
0120 #endif
0121     m_filters.remove(project);
0122 }
0123 
0124 KJob* AbstractFileManagerPluginPrivate::eventuallyReadFolder(ProjectFolderItem* item)
0125 {
0126     auto* listJob = new FileManagerListJob( item );
0127     m_projectJobs[ item->project() ] << listJob;
0128     qCDebug(FILEMANAGER) << "adding job" << listJob << item << item->path() << "for project" << item->project();
0129 
0130     q->connect( listJob, &FileManagerListJob::finished,
0131                 q, [&] (KJob* job) { jobFinished(job); } );
0132 
0133     q->connect( listJob, &FileManagerListJob::entries,
0134                 q, [&] (FileManagerListJob* job, ProjectFolderItem* baseItem, const KIO::UDSEntryList& entries) {
0135                     addJobItems(job, baseItem, entries); } );
0136 
0137     return listJob;
0138 }
0139 
0140 void AbstractFileManagerPluginPrivate::jobFinished(KJob* job)
0141 {
0142     // ensure we don't keep a dangling point in our list
0143     // NOTE: job is potentially emitting its finished signal from its destructor
0144     // or the item that was used internally may have been deleted already
0145     for (auto& jobs : m_projectJobs) {
0146         if (jobs.removeOne(reinterpret_cast<FileManagerListJob*>(job))) {
0147             break;
0148         }
0149     }
0150 }
0151 
0152 void AbstractFileManagerPluginPrivate::addJobItems(FileManagerListJob* job,
0153                                                      ProjectFolderItem* baseItem,
0154                                                      const KIO::UDSEntryList& entries)
0155 {
0156     qCDebug(FILEMANAGER) << "reading entries of" << baseItem->path();
0157 
0158     // build lists of valid files and folders with paths relative to the project folder
0159     Path::List files;
0160     Path::List folders;
0161     for (const KIO::UDSEntry& entry : entries) {
0162         QString name = entry.stringValue( KIO::UDSEntry::UDS_NAME );
0163         if (name == QLatin1String(".") || name == QLatin1String("..")) {
0164             continue;
0165         }
0166 
0167         Path path(baseItem->path(), name);
0168 
0169         if ( !q->isValid( path, entry.isDir(), baseItem->project() ) ) {
0170             continue;
0171         } else {
0172             if ( entry.isDir() ) {
0173                 if( entry.isLink() ) {
0174                     const Path linkedPath = baseItem->path().cd(entry.stringValue( KIO::UDSEntry::UDS_LINK_DEST ));
0175                     // make sure we don't end in an infinite loop
0176                     if( linkedPath.isParentOf( baseItem->project()->path() ) ||
0177                         baseItem->project()->path().isParentOf( linkedPath ) ||
0178                         linkedPath == baseItem->project()->path() )
0179                     {
0180                         continue;
0181                     }
0182                 }
0183                 folders << path;
0184             } else {
0185                 files << path;
0186             }
0187         }
0188     }
0189 
0190     ifDebug(qCDebug(FILEMANAGER) << "valid folders:" << folders;)
0191     ifDebug(qCDebug(FILEMANAGER) << "valid files:" << files;)
0192 
0193     // remove obsolete rows
0194     for ( int j = 0; j < baseItem->rowCount(); ++j ) {
0195         if ( ProjectFolderItem* f = baseItem->child(j)->folder() ) {
0196             // check if this is still a valid folder
0197             int index = folders.indexOf( f->path() );
0198             if ( index == -1 ) {
0199                 // folder got removed or is now invalid
0200                 delete f;
0201                 --j;
0202             } else {
0203                 // this folder already exists in the view
0204                 folders.remove( index );
0205                 // no need to add this item, but we still want to recurse into it
0206                 job->addSubDir( f );
0207                 emit q->reloadedFolderItem( f );
0208             }
0209         } else if ( ProjectFileItem* f =  baseItem->child(j)->file() ) {
0210             // check if this is still a valid file
0211             int index = files.indexOf( f->path() );
0212             if ( index == -1 ) {
0213                 // file got removed or is now invalid
0214                 ifDebug(qCDebug(FILEMANAGER) << "removing file:" << f << f->path();)
0215                 delete f;
0216                 --j;
0217             } else {
0218                 // this file already exists in the view
0219                 files.remove( index );
0220                 emit q->reloadedFileItem( f );
0221             }
0222         }
0223     }
0224 
0225     // add new rows
0226     for (const Path& path : qAsConst(files)) {
0227         ProjectFileItem* file = q->createFileItem( baseItem->project(), path, baseItem );
0228         if (file) {
0229             emit q->fileAdded( file );
0230         }
0231     }
0232     for (const Path& path : qAsConst(folders)) {
0233         ProjectFolderItem* folder = q->createFolderItem( baseItem->project(), path, baseItem );
0234         if (folder) {
0235             emit q->folderAdded( folder );
0236             job->addSubDir( folder );
0237         }
0238     }
0239 }
0240 
0241 void AbstractFileManagerPluginPrivate::created(const QString& path_)
0242 {
0243     qCDebug(FILEMANAGER) << "created:" << path_;
0244     QFileInfo info(path_);
0245     if (!info.exists()) {
0246         // we delay handling of the signal, so maybe the path actually got removed again
0247         return;
0248     }
0249 
0250     ///FIXME: share memory with parent
0251     const Path path(path_);
0252     const IndexedString indexedPath(path.pathOrUrl());
0253     const IndexedString indexedParent(path.parent().pathOrUrl());
0254 
0255     QHashIterator<IProject*, KDirWatch*> it(m_watchers);
0256     while (it.hasNext()) {
0257         const auto p = it.next().key();
0258         if ( !p->projectItem()->model() ) {
0259             // not yet finished with loading
0260             // FIXME: how should this be handled? see unit test
0261             continue;
0262         }
0263         if ( !q->isValid(path, info.isDir(), p) ) {
0264             continue;
0265         }
0266         if ( info.isDir() ) {
0267             bool found = false;
0268             const auto folderItems = p->foldersForPath(indexedPath);
0269             for (ProjectFolderItem* folder : folderItems) {
0270                 // exists already in this project, happens e.g. when we restart the dirwatcher
0271                 // or if we delete and remove folders consecutively https://bugs.kde.org/show_bug.cgi?id=260741
0272                 qCDebug(FILEMANAGER) << "force reload of" << path << folder;
0273                 auto job = eventuallyReadFolder( folder );
0274                 job->start();
0275                 found = true;
0276             }
0277             if ( found ) {
0278                 continue;
0279             }
0280         } else if (!p->filesForPath(indexedPath).isEmpty()) {
0281             // also gets triggered for kate's backup files
0282             continue;
0283         }
0284         const auto parentItems = p->foldersForPath(indexedParent);
0285         for (ProjectFolderItem* parentItem : parentItems) {
0286             if ( info.isDir() ) {
0287                 ProjectFolderItem* folder = q->createFolderItem( p, path, parentItem );
0288                 if (folder) {
0289                     emit q->folderAdded( folder );
0290                     auto job = eventuallyReadFolder( folder );
0291                     job->start();
0292                 }
0293             } else {
0294                 ProjectFileItem* file = q->createFileItem( p, path, parentItem );
0295                 if (file) {
0296                     emit q->fileAdded( file );
0297                 }
0298             }
0299         }
0300     }
0301 }
0302 
0303 void AbstractFileManagerPluginPrivate::deleted(const QString& path_)
0304 {
0305     if ( QFile::exists(path_) ) {
0306         // we delay handling of the signal, so maybe the path actually exists again
0307         return;
0308     }
0309     // ensure that the path is not inside a stopped folder
0310     for (const QString& folder : qAsConst(m_stoppedFolders)) {
0311         if (path_.startsWith(folder)) {
0312             return;
0313         }
0314     }
0315     qCDebug(FILEMANAGER) << "deleted:" << path_;
0316 
0317     const Path path(QUrl::fromLocalFile(path_));
0318     const IndexedString indexed(path.pathOrUrl());
0319 
0320     QHashIterator<IProject*, KDirWatch*> it(m_watchers);
0321     while (it.hasNext()) {
0322         const auto p = it.next().key();
0323         if (path == p->path()) {
0324             KMessageBox::error(qApp->activeWindow(),
0325                                i18n("The base folder of project <b>%1</b>"
0326                                     " got deleted or moved outside of KDevelop.\n"
0327                                     "The project has to be closed.", p->name()),
0328                                i18nc("@title:window", "Project Folder Deleted") );
0329             ICore::self()->projectController()->closeProject(p);
0330             continue;
0331         }
0332         if ( !p->projectItem()->model() ) {
0333             // not yet finished with loading
0334             // FIXME: how should this be handled? see unit test
0335             continue;
0336         }
0337         const auto folderItems = p->foldersForPath(indexed);
0338         for (ProjectFolderItem* item : folderItems) {
0339             delete item;
0340         }
0341         const auto fileItems = p->filesForPath(indexed);
0342         for (ProjectFileItem* item : fileItems) {
0343             emit q->fileRemoved(item);
0344             ifDebug(qCDebug(FILEMANAGER) << "removing file" << item;)
0345             delete item;
0346         }
0347     }
0348 }
0349 
0350 bool AbstractFileManagerPluginPrivate::rename(ProjectBaseItem* item, const Path& newPath)
0351 {
0352     if ( !q->isValid(newPath, true, item->project()) ) {
0353         int cancel = KMessageBox::warningContinueCancel( qApp->activeWindow(),
0354             i18n("You tried to rename '%1' to '%2', but the latter is filtered and will be hidden.\n"
0355                  "Do you want to continue?", item->text(), newPath.lastPathSegment()),
0356             QString(), KStandardGuiItem::cont(), KStandardGuiItem::cancel(), QStringLiteral("GenericManagerRenameToFiltered")
0357         );
0358         if ( cancel == KMessageBox::Cancel ) {
0359             return false;
0360         }
0361     }
0362     const auto parentItems = item->project()->foldersForPath(IndexedString(newPath.parent().pathOrUrl()));
0363     for (ProjectFolderItem* parent : parentItems) {
0364         if ( parent->folder() ) {
0365             stopWatcher(parent);
0366             const Path source = item->path();
0367             bool success = renameUrl( item->project(), source.toUrl(), newPath.toUrl() );
0368             if ( success ) {
0369                 item->setPath( newPath );
0370                 item->parent()->takeRow( item->row() );
0371                 parent->appendRow( item );
0372                 if (item->file()) {
0373                     emit q->fileRenamed(source, item->file());
0374                 } else {
0375                     Q_ASSERT(item->folder());
0376                     emit q->folderRenamed(source, item->folder());
0377                 }
0378             }
0379             continueWatcher(parent);
0380             return success;
0381         }
0382     }
0383     return false;
0384 }
0385 
0386 void AbstractFileManagerPluginPrivate::stopWatcher(ProjectFolderItem* folder)
0387 {
0388     if ( !folder->path().isLocalFile() ) {
0389         return;
0390     }
0391     Q_ASSERT(m_watchers.contains(folder->project()));
0392     const QString path = folder->path().toLocalFile();
0393     m_watchers[folder->project()]->stopDirScan(path);
0394     m_stoppedFolders.append(path);
0395 }
0396 
0397 void AbstractFileManagerPluginPrivate::continueWatcher(ProjectFolderItem* folder)
0398 {
0399     if ( !folder->path().isLocalFile() ) {
0400         return;
0401     }
0402     auto watcher = m_watchers.value(folder->project(), nullptr);
0403     Q_ASSERT(watcher);
0404     const QString path = folder->path().toLocalFile();
0405     if (!watcher->restartDirScan(path)) {
0406         // path wasn't being watched yet - can we be 100% certain of that will never happen?
0407         qCWarning(FILEMANAGER) << "Folder" << path << "in project" << folder->project()->name() << "wasn't yet being watched";
0408         watcher->addDir(path);
0409     }
0410     const int idx = m_stoppedFolders.indexOf(path);
0411     if (idx != -1) {
0412         m_stoppedFolders.remove(idx);
0413     }
0414 }
0415 //END Private
0416 
0417 //BEGIN Plugin
0418 
0419 AbstractFileManagerPlugin::AbstractFileManagerPlugin( const QString& componentName,
0420                                                       QObject *parent,
0421                                                       const QVariantList & /*args*/ )
0422     : IProjectFileManager(),
0423       IPlugin( componentName, parent ),
0424       d_ptr(new AbstractFileManagerPluginPrivate(this))
0425 {
0426     connect(core()->projectController(), &IProjectController::projectClosing,
0427             this, [this] (IProject* project) { Q_D(AbstractFileManagerPlugin); d->projectClosing(project); });
0428     connect(core()->projectController()->projectModel(), &ProjectModel::rowsAboutToBeRemoved,
0429             this, [this] (const QModelIndex& parent, int first, int last) {
0430                 Q_D(AbstractFileManagerPlugin);
0431                 // cleanup list jobs to remove about-to-be-dangling pointers
0432                 auto* model = core()->projectController()->projectModel();
0433                 for (int i = first; i <= last; ++i) {
0434                     const auto index = model->index(i, 0, parent);
0435                     auto* item = index.data(ProjectModel::ProjectItemRole).value<ProjectBaseItem*>();
0436                     Q_ASSERT(item);
0437                     for (auto* job : d->m_projectJobs.value(item->project())) {
0438                         job->handleRemovedItem(item);
0439                     }
0440                 }
0441             });
0442 }
0443 
0444 AbstractFileManagerPlugin::~AbstractFileManagerPlugin() = default;
0445 
0446 IProjectFileManager::Features AbstractFileManagerPlugin::features() const
0447 {
0448     return Features( Folders | Files );
0449 }
0450 
0451 QList<ProjectFolderItem*> AbstractFileManagerPlugin::parse( ProjectFolderItem *item )
0452 {
0453     // we are async, can't return anything here
0454     qCDebug(FILEMANAGER) << "note: parse will always return an empty list";
0455     Q_UNUSED(item);
0456     return QList<ProjectFolderItem*>();
0457 }
0458 
0459 ProjectFolderItem *AbstractFileManagerPlugin::import( IProject *project )
0460 {
0461     Q_D(AbstractFileManagerPlugin);
0462 
0463     ProjectFolderItem *projectRoot = createFolderItem( project, project->path(), nullptr );
0464     emit folderAdded( projectRoot );
0465     qCDebug(FILEMANAGER) << "imported new project" << project->name() << "at" << projectRoot->path();
0466 
0467     ///TODO: check if this works for remote files when something gets changed through another KDE app
0468     if ( project->path().isLocalFile() ) {
0469         auto watcher = new KDirWatch( project );
0470 
0471         // set up the signal handling
0472         // NOTE: We delay handling of the creation/deletion events here by one second to prevent
0473         //       useless or even outright wrong handling of events during common git workflows.
0474         //       I.e. sometimes we used to get a 'delete' event during a rebase which was never
0475         //       followed up by a 'created' signal, even though the file actually exists after
0476         //       the rebase.
0477         //       see also: https://bugs.kde.org/show_bug.cgi?id=404184
0478         connect(watcher, &KDirWatch::created,
0479                 this, [this] (const QString& path) {
0480                     QTimer::singleShot(1000, this, [this, path]() {
0481                         Q_D(AbstractFileManagerPlugin);
0482                         d->created(path);
0483                     });
0484                 });
0485         connect(watcher, &KDirWatch::deleted,
0486                 this, [this] (const QString& path) {
0487                     QTimer::singleShot(1000, this, [this, path]() {
0488                         Q_D(AbstractFileManagerPlugin);
0489                         d->deleted(path);
0490                     });
0491                 });
0492         watcher->addDir(project->path().toLocalFile(), KDirWatch::WatchSubDirs | KDirWatch:: WatchFiles );
0493         d->m_watchers[project] = watcher;
0494     }
0495 
0496     d->m_filters.add(project);
0497 
0498     return projectRoot;
0499 }
0500 
0501 KJob* AbstractFileManagerPlugin::createImportJob(ProjectFolderItem* item)
0502 {
0503     Q_D(AbstractFileManagerPlugin);
0504 
0505     return d->eventuallyReadFolder(item);
0506 }
0507 
0508 bool AbstractFileManagerPlugin::reload( ProjectFolderItem* item )
0509 {
0510     Q_D(AbstractFileManagerPlugin);
0511 
0512     qCDebug(FILEMANAGER) << "reloading item" << item->path();
0513     auto job = d->eventuallyReadFolder( item->folder() );
0514     job->start();
0515     return true;
0516 }
0517 
0518 ProjectFolderItem* AbstractFileManagerPlugin::addFolder( const Path& folder,
0519         ProjectFolderItem * parent )
0520 {
0521     Q_D(AbstractFileManagerPlugin);
0522 
0523     qCDebug(FILEMANAGER) << "adding folder" << folder << "to" << parent->path();
0524     ProjectFolderItem* created = nullptr;
0525     d->stopWatcher(parent);
0526     if ( createFolder(folder.toUrl()) ) {
0527         created = createFolderItem( parent->project(), folder, parent );
0528         if (created) {
0529             emit folderAdded(created);
0530         }
0531     }
0532     d->continueWatcher(parent);
0533     return created;
0534 }
0535 
0536 
0537 ProjectFileItem* AbstractFileManagerPlugin::addFile( const Path& file,
0538         ProjectFolderItem * parent )
0539 {
0540     Q_D(AbstractFileManagerPlugin);
0541 
0542     qCDebug(FILEMANAGER) << "adding file" << file << "to" << parent->path();
0543     ProjectFileItem* created = nullptr;
0544     d->stopWatcher(parent);
0545     if ( createFile(file.toUrl()) ) {
0546         created = createFileItem( parent->project(), file, parent );
0547         if (created) {
0548             emit fileAdded(created);
0549         }
0550     }
0551     d->continueWatcher(parent);
0552     return created;
0553 }
0554 
0555 bool AbstractFileManagerPlugin::renameFolder(ProjectFolderItem* folder, const Path& newPath)
0556 {
0557     Q_D(AbstractFileManagerPlugin);
0558 
0559     qCDebug(FILEMANAGER) << "trying to rename a folder:" << folder->path() << newPath;
0560     return d->rename(folder, newPath);
0561 }
0562 
0563 bool AbstractFileManagerPlugin::renameFile(ProjectFileItem* file, const Path& newPath)
0564 {
0565     Q_D(AbstractFileManagerPlugin);
0566 
0567     qCDebug(FILEMANAGER) << "trying to rename a file:" << file->path() << newPath;
0568     return d->rename(file, newPath);
0569 }
0570 
0571 bool AbstractFileManagerPlugin::removeFilesAndFolders(const QList<ProjectBaseItem*> &items)
0572 {
0573     Q_D(AbstractFileManagerPlugin);
0574 
0575     bool success = true;
0576     for (ProjectBaseItem* item : items) {
0577         Q_ASSERT(item->folder() || item->file());
0578 
0579         ProjectFolderItem* parent = parentFolder(item);
0580         d->stopWatcher(parent);
0581 
0582         success &= removeUrl(parent->project(), item->path().toUrl(), true);
0583         if ( success ) {
0584             if (item->file()) {
0585                 emit fileRemoved(item->file());
0586             } else {
0587                 Q_ASSERT(item->folder());
0588                 emit folderRemoved(item->folder());
0589             }
0590             delete item;
0591         }
0592 
0593         d->continueWatcher(parent);
0594         if ( !success )
0595             break;
0596     }
0597     return success;
0598 }
0599 
0600 bool AbstractFileManagerPlugin::moveFilesAndFolders(const QList< ProjectBaseItem* >& items, ProjectFolderItem* newParent)
0601 {
0602     Q_D(AbstractFileManagerPlugin);
0603 
0604     bool success = true;
0605     for (ProjectBaseItem* item : items) {
0606         Q_ASSERT(item->folder() || item->file());
0607 
0608         ProjectFolderItem* oldParent = parentFolder(item);
0609         d->stopWatcher(oldParent);
0610         d->stopWatcher(newParent);
0611 
0612         const Path oldPath = item->path();
0613         const Path newPath(newParent->path(), item->baseName());
0614 
0615         success &= renameUrl(oldParent->project(), oldPath.toUrl(), newPath. toUrl());
0616         if ( success ) {
0617             if (item->file()) {
0618                 emit fileRemoved(item->file());
0619             } else {
0620                 emit folderRemoved(item->folder());
0621             }
0622             delete item;
0623             auto* const readJob = d->eventuallyReadFolder(newParent);
0624             // reload first level synchronously, deeper levels will run async
0625             // this is required for code that expects the new item to exist after
0626             // this method finished
0627             readJob->exec();
0628         }
0629 
0630         d->continueWatcher(oldParent);
0631         d->continueWatcher(newParent);
0632         if ( !success )
0633             break;
0634     }
0635     return success;
0636 }
0637 
0638 bool AbstractFileManagerPlugin::copyFilesAndFolders(const Path::List& items, ProjectFolderItem* newParent)
0639 {
0640     Q_D(AbstractFileManagerPlugin);
0641 
0642     bool success = true;
0643     for (const Path& item : items) {
0644         d->stopWatcher(newParent);
0645 
0646         success &= copyUrl(newParent->project(), item.toUrl(), newParent->path().toUrl());
0647         if ( success ) {
0648             auto* const readJob = d->eventuallyReadFolder(newParent);
0649             // reload first level synchronously, deeper levels will run async
0650             // this is required for code that expects the new item to exist after
0651             // this method finished
0652             readJob->exec();
0653         }
0654 
0655         d->continueWatcher(newParent);
0656         if ( !success )
0657             break;
0658     }
0659     return success;
0660 }
0661 
0662 bool AbstractFileManagerPlugin::isValid( const Path& path, const bool isFolder,
0663                                          IProject* project ) const
0664 {
0665     Q_D(const AbstractFileManagerPlugin);
0666 
0667     return d->m_filters.isValid( path, isFolder, project );
0668 }
0669 
0670 ProjectFileItem* AbstractFileManagerPlugin::createFileItem( IProject* project, const Path& path,
0671                                                             ProjectBaseItem* parent )
0672 {
0673     return new ProjectFileItem( project, path, parent );
0674 }
0675 
0676 ProjectFolderItem* AbstractFileManagerPlugin::createFolderItem( IProject* project, const Path& path,
0677                                                                 ProjectBaseItem* parent )
0678 {
0679     return new ProjectFolderItem( project, path, parent );
0680 }
0681 
0682 KDirWatch* AbstractFileManagerPlugin::projectWatcher( IProject* project ) const
0683 {
0684     Q_D(const AbstractFileManagerPlugin);
0685 
0686     return d->m_watchers.value( project, nullptr );
0687 }
0688 
0689 //END Plugin
0690 
0691 #include "moc_abstractfilemanagerplugin.cpp"