File indexing completed on 2024-04-28 04:37:22
0001 /* 0002 SPDX-FileCopyrightText: 2001 Matthias Hoelzer-Kluepfel <hoelzer@kde.org> 0003 SPDX-FileCopyrightText: 2002-2003 Roberto Raggi <roberto@kdevelop.org> 0004 SPDX-FileCopyrightText: 2002 Simon Hausmann <hausmann@kde.org> 0005 SPDX-FileCopyrightText: 2003 Jens Dagerbo <jens.dagerbo@swipnet.se> 0006 SPDX-FileCopyrightText: 2003 Mario Scalas <mario.scalas@libero.it> 0007 SPDX-FileCopyrightText: 2003-2004 Alexander Dymo <adymo@kdevelop.org> 0008 SPDX-FileCopyrightText: 2006 Matt Rogers <mattr@kde.org> 0009 SPDX-FileCopyrightText: 2007 Andreas Pakulat <apaku@gmx.de> 0010 0011 SPDX-License-Identifier: LGPL-2.0-or-later 0012 */ 0013 0014 #include "project.h" 0015 0016 #include <QSet> 0017 #include <QTemporaryFile> 0018 #include <QTimer> 0019 #include <QPointer> 0020 0021 #include <KConfigGroup> 0022 #include <KIO/FileCopyJob> 0023 #include <KIO/MkdirJob> 0024 #include <KIO/StatJob> 0025 #include <KJobWidgets> 0026 #include <KLocalizedString> 0027 #include <KMessageBox> 0028 0029 #include <project/interfaces/iprojectfilemanager.h> 0030 #include <project/interfaces/ibuildsystemmanager.h> 0031 #include <interfaces/iplugin.h> 0032 #include <interfaces/iplugincontroller.h> 0033 #include <interfaces/iruncontroller.h> 0034 #include <interfaces/iuicontroller.h> 0035 #include <interfaces/isession.h> 0036 #include <project/projectmodel.h> 0037 #include <sublime/message.h> 0038 #include <util/path.h> 0039 #include <serialization/indexedstring.h> 0040 #include <vcs/interfaces/ibasicversioncontrol.h> 0041 0042 #include "core.h" 0043 #include "mainwindow.h" 0044 #include "projectcontroller.h" 0045 #include "uicontroller.h" 0046 #include "debug.h" 0047 0048 namespace KDevelop 0049 { 0050 0051 class ProjectProgress : public QObject, public IStatus 0052 { 0053 Q_OBJECT 0054 Q_INTERFACES(KDevelop::IStatus) 0055 0056 public: 0057 ProjectProgress(); 0058 ~ProjectProgress() override; 0059 QString statusName() const override; 0060 0061 /*! Show indeterminate mode progress bar */ 0062 void setBuzzy(); 0063 0064 /*! Hide progress bar */ 0065 void setDone(); 0066 0067 QString projectName; 0068 0069 private Q_SLOTS: 0070 void slotClean(); 0071 0072 Q_SIGNALS: 0073 void clearMessage(KDevelop::IStatus*) override; 0074 void showMessage(KDevelop::IStatus*,const QString & message, int timeout = 0) override; 0075 void showErrorMessage(const QString& message, int timeout) override; 0076 void hideProgress(KDevelop::IStatus*) override; 0077 void showProgress(KDevelop::IStatus*,int minimum, int maximum, int value) override; 0078 0079 private: 0080 QTimer* m_timer; 0081 }; 0082 0083 0084 0085 ProjectProgress::ProjectProgress() 0086 { 0087 m_timer = new QTimer(this); 0088 m_timer->setSingleShot( true ); 0089 m_timer->setInterval( 1000 ); 0090 connect(m_timer, &QTimer::timeout,this, &ProjectProgress::slotClean); 0091 } 0092 0093 ProjectProgress::~ProjectProgress() 0094 { 0095 } 0096 0097 QString ProjectProgress::statusName() const 0098 { 0099 return i18n("Loading Project %1", projectName); 0100 } 0101 0102 void ProjectProgress::setBuzzy() 0103 { 0104 qCDebug(SHELL) << "showing busy progress" << statusName(); 0105 // show an indeterminate progressbar 0106 emit showProgress(this, 0,0,0); 0107 emit showMessage(this, i18nc("%1: Project name", "Loading %1", projectName)); 0108 } 0109 0110 0111 void ProjectProgress::setDone() 0112 { 0113 qCDebug(SHELL) << "showing done progress" << statusName(); 0114 // first show 100% bar for a second, then hide. 0115 emit showProgress(this, 0,1,1); 0116 m_timer->start(); 0117 } 0118 0119 void ProjectProgress::slotClean() 0120 { 0121 emit hideProgress(this); 0122 emit clearMessage(this); 0123 } 0124 0125 class ProjectPrivate 0126 { 0127 public: 0128 Path projectPath; 0129 Path projectFile; 0130 Path developerFile; 0131 QString developerTempFile; 0132 QTemporaryFile projectTempFile; 0133 IPlugin* manager = nullptr; 0134 QPointer<IPlugin> vcsPlugin; 0135 ProjectFolderItem* topItem = nullptr; 0136 QString name; 0137 KSharedConfigPtr m_cfg; 0138 Project * const project; 0139 QSet<KDevelop::IndexedString> fileSet; 0140 bool loading = false; 0141 bool fullReload; 0142 bool scheduleReload = false; 0143 ProjectProgress* progress; 0144 0145 public: 0146 explicit ProjectPrivate(Project* project) 0147 : project(project) 0148 {} 0149 0150 void reloadDone(KJob* job) 0151 { 0152 progress->setDone(); 0153 loading = false; 0154 0155 ProjectController* projCtrl = Core::self()->projectControllerInternal(); 0156 if (job->error() == 0 && !Core::self()->shuttingDown()) { 0157 0158 if(fullReload) 0159 projCtrl->projectModel()->appendRow(topItem); 0160 0161 if (scheduleReload) { 0162 scheduleReload = false; 0163 project->reloadModel(); 0164 } 0165 } else { 0166 projCtrl->abortOpeningProject(project); 0167 } 0168 } 0169 0170 QList<ProjectBaseItem*> itemsForPath( const IndexedString& path ) const 0171 { 0172 if ( path.isEmpty() ) { 0173 return QList<ProjectBaseItem*>(); 0174 } 0175 0176 if (!topItem->model()) { 0177 // this gets hit when the project has not yet been added to the model 0178 // i.e. during import phase 0179 // TODO: should we handle this somehow? 0180 // possible idea: make the item<->path hash per-project 0181 return QList<ProjectBaseItem*>(); 0182 } 0183 0184 Q_ASSERT(topItem->model()); 0185 QList<ProjectBaseItem*> items = topItem->model()->itemsForPath(path); 0186 0187 QList<ProjectBaseItem*>::iterator it = items.begin(); 0188 while(it != items.end()) { 0189 if ((*it)->project() != project) { 0190 it = items.erase(it); 0191 } else { 0192 ++it; 0193 } 0194 } 0195 0196 return items; 0197 } 0198 0199 0200 void importDone( KJob* job) 0201 { 0202 progress->setDone(); 0203 ProjectController* projCtrl = Core::self()->projectControllerInternal(); 0204 0205 if(job->error() == 0 && !Core::self()->shuttingDown()) { 0206 loading=false; 0207 projCtrl->projectModel()->appendRow(topItem); 0208 projCtrl->projectImportingFinished( project ); 0209 } else { 0210 projCtrl->abortOpeningProject(project); 0211 } 0212 } 0213 0214 void initProject(const Path& projectFile_) 0215 { 0216 // helper method for open() 0217 projectFile = projectFile_; 0218 } 0219 0220 bool initProjectFiles() 0221 { 0222 KIO::StatJob* statJob = KIO::stat( projectFile.toUrl(), KIO::HideProgressInfo ); 0223 if ( !statJob->exec() ) //be sync for right now 0224 { 0225 const QString messageText = 0226 i18n("Unable to load the project file %1.<br>" 0227 "The project has been removed from the session.", 0228 projectFile.pathOrUrl()); 0229 auto* message = new Sublime::Message(messageText, Sublime::Message::Error); 0230 ICore::self()->uiController()->postMessage(message); 0231 return false; 0232 } 0233 0234 // developerfile == dirname(projectFileUrl) ."/.kdev4/". basename(projectfileUrl) 0235 developerFile = projectFile; 0236 developerFile.setLastPathSegment( QStringLiteral(".kdev4") ); 0237 developerFile.addPath( projectFile.lastPathSegment() ); 0238 0239 statJob = KIO::stat( developerFile.toUrl(), KIO::HideProgressInfo ); 0240 if( !statJob->exec() ) 0241 { 0242 // the developerfile does not exist yet, check if its folder exists 0243 // the developerfile itself will get created below 0244 QUrl dir = developerFile.parent().toUrl(); 0245 statJob = KIO::stat( dir, KIO::HideProgressInfo ); 0246 if( !statJob->exec() ) 0247 { 0248 KIO::SimpleJob* mkdirJob = KIO::mkdir( dir ); 0249 if( !mkdirJob->exec() ) 0250 { 0251 const QString messageText = 0252 i18n("Unable to create hidden dir (%1) for developer file", 0253 dir.toDisplayString(QUrl::PreferLocalFile)); 0254 auto* message = new Sublime::Message(messageText, Sublime::Message::Error); 0255 ICore::self()->uiController()->postMessage(message); 0256 return false; 0257 } 0258 } 0259 } 0260 0261 projectTempFile.open(); 0262 auto copyJob = KIO::file_copy(projectFile.toUrl(), QUrl::fromLocalFile(projectTempFile.fileName()), -1, KIO::HideProgressInfo | KIO::Overwrite); 0263 KJobWidgets::setWindow(copyJob, Core::self()->uiController()->activeMainWindow()); 0264 if (!copyJob->exec()) 0265 { 0266 qCDebug(SHELL) << "Job failed:" << copyJob->errorString(); 0267 0268 const QString messageText = i18n("Unable to get project file: %1", projectFile.pathOrUrl()); 0269 auto* message = new Sublime::Message(messageText, Sublime::Message::Error); 0270 ICore::self()->uiController()->postMessage(message); 0271 return false; 0272 } 0273 0274 if(developerFile.isLocalFile()) 0275 { 0276 developerTempFile = developerFile.toLocalFile(); 0277 } 0278 else { 0279 QTemporaryFile tmp; 0280 tmp.open(); 0281 developerTempFile = tmp.fileName(); 0282 0283 auto job = KIO::file_copy(developerFile.toUrl(), QUrl::fromLocalFile(developerTempFile), -1, KIO::HideProgressInfo | KIO::Overwrite); 0284 KJobWidgets::setWindow(job, Core::self()->uiController()->activeMainWindow()); 0285 job->exec(); 0286 } 0287 return true; 0288 } 0289 0290 KConfigGroup initKConfigObject() 0291 { 0292 // helper method for open() 0293 qCDebug(SHELL) << "Creating KConfig object for project files" << developerTempFile << projectTempFile.fileName(); 0294 m_cfg = KSharedConfig::openConfig( developerTempFile ); 0295 m_cfg->addConfigSources( QStringList() << projectTempFile.fileName() ); 0296 KConfigGroup projectGroup( m_cfg, "Project" ); 0297 return projectGroup; 0298 } 0299 0300 bool projectNameUsed(const KConfigGroup& projectGroup) 0301 { 0302 // helper method for open() 0303 name = projectGroup.readEntry( "Name", projectFile.lastPathSegment() ); 0304 progress->projectName = name; 0305 if( Core::self()->projectController()->isProjectNameUsed( name ) ) 0306 { 0307 const QString messageText = 0308 i18n("Could not load %1, a project with the same name '%2' is already open.", projectFile.pathOrUrl(), name); 0309 auto* message = new Sublime::Message(messageText, Sublime::Message::Error); 0310 ICore::self()->uiController()->postMessage(message); 0311 0312 qCWarning(SHELL) << "Trying to open a project with a name that is already used by another open project"; 0313 return true; 0314 } 0315 return false; 0316 } 0317 0318 IProjectFileManager* fetchFileManager(const KConfigGroup& projectGroup) 0319 { 0320 if (manager) 0321 { 0322 auto* iface = manager->extension<KDevelop::IProjectFileManager>(); 0323 Q_ASSERT(iface); 0324 return iface; 0325 } 0326 0327 // helper method for open() 0328 QString managerSetting = projectGroup.readEntry( "Manager", "KDevGenericManager" ); 0329 0330 //Get our importer 0331 IPluginController* pluginManager = Core::self()->pluginController(); 0332 manager = pluginManager->pluginForExtension( QStringLiteral("org.kdevelop.IProjectFileManager"), managerSetting ); 0333 IProjectFileManager* iface = nullptr; 0334 if ( manager ) 0335 iface = manager->extension<IProjectFileManager>(); 0336 else 0337 { 0338 const QString messageText = 0339 i18n("Could not load project management plugin <b>%1</b>.<br>Check that the required programs are installed," 0340 " or see console output for more information.", managerSetting); 0341 auto* message = new Sublime::Message(messageText, Sublime::Message::Error); 0342 ICore::self()->uiController()->postMessage(message); 0343 manager = nullptr; 0344 return nullptr; 0345 } 0346 if (iface == nullptr) 0347 { 0348 const QString messageText = 0349 i18n("The project importing plugin (%1) does not support the IProjectFileManager interface.", managerSetting); 0350 auto* message = new Sublime::Message(messageText, Sublime::Message::Error); 0351 ICore::self()->uiController()->postMessage(message); 0352 delete manager; 0353 manager = nullptr; 0354 } 0355 return iface; 0356 } 0357 0358 void loadVersionControlPlugin(KConfigGroup& projectGroup) 0359 { 0360 // helper method for open() 0361 IPluginController* pluginManager = Core::self()->pluginController(); 0362 if( projectGroup.hasKey( "VersionControlSupport" ) ) 0363 { 0364 QString vcsPluginName = projectGroup.readEntry("VersionControlSupport", ""); 0365 if( !vcsPluginName.isEmpty() ) 0366 { 0367 vcsPlugin = pluginManager->pluginForExtension( QStringLiteral( "org.kdevelop.IBasicVersionControl" ), vcsPluginName ); 0368 } 0369 } else 0370 { 0371 const QList<IPlugin*> plugins = pluginManager->allPluginsForExtension( QStringLiteral( "org.kdevelop.IBasicVersionControl" ) ); 0372 for (IPlugin* p : plugins) { 0373 auto* iface = p->extension<KDevelop::IBasicVersionControl>(); 0374 if (!iface) { 0375 continue; 0376 } 0377 0378 const auto url = topItem->path().toUrl(); 0379 qCDebug(SHELL) << "Checking whether" << url << "is version controlled by" << iface->name(); 0380 if(iface->isVersionControlled(url)) 0381 { 0382 qCDebug(SHELL) << "Detected that" << url << "is a" << iface->name() << "project"; 0383 0384 vcsPlugin = p; 0385 projectGroup.writeEntry("VersionControlSupport", pluginManager->pluginInfo(p).pluginId()); 0386 projectGroup.sync(); 0387 } 0388 } 0389 } 0390 0391 } 0392 0393 bool importTopItem(IProjectFileManager* fileManager) 0394 { 0395 if (!fileManager) 0396 { 0397 return false; 0398 } 0399 topItem = fileManager->import( project ); 0400 if( !topItem ) 0401 { 0402 auto* message = new Sublime::Message(i18n("Could not open project."), Sublime::Message::Error); 0403 ICore::self()->uiController()->postMessage(message); 0404 return false; 0405 } 0406 0407 return true; 0408 } 0409 0410 }; 0411 0412 Project::Project( QObject *parent ) 0413 : IProject( parent ) 0414 , d_ptr(new ProjectPrivate(this)) 0415 { 0416 Q_D(Project); 0417 0418 d->progress = new ProjectProgress; 0419 Core::self()->uiController()->registerStatus( d->progress ); 0420 } 0421 0422 Project::~Project() 0423 { 0424 Q_D(Project); 0425 0426 delete d->progress; 0427 } 0428 0429 QString Project::name() const 0430 { 0431 Q_D(const Project); 0432 0433 return d->name; 0434 } 0435 0436 QString Project::developerTempFile() const 0437 { 0438 Q_D(const Project); 0439 0440 return d->developerTempFile; 0441 } 0442 0443 QString Project::projectTempFile() const 0444 { 0445 Q_D(const Project); 0446 0447 return d->projectTempFile.fileName(); 0448 } 0449 0450 KSharedConfigPtr Project::projectConfiguration() const 0451 { 0452 Q_D(const Project); 0453 0454 return d->m_cfg; 0455 } 0456 0457 Path Project::path() const 0458 { 0459 Q_D(const Project); 0460 0461 return d->projectPath; 0462 } 0463 0464 void Project::reloadModel() 0465 { 0466 Q_D(Project); 0467 0468 if (d->loading) { 0469 d->scheduleReload = true; 0470 return; 0471 } 0472 d->loading = true; 0473 d->fileSet.clear(); 0474 0475 // delete topItem and remove it from model 0476 ProjectModel* model = Core::self()->projectController()->projectModel(); 0477 model->removeRow( d->topItem->row() ); 0478 d->topItem = nullptr; 0479 0480 auto* iface = d->manager->extension<IProjectFileManager>(); 0481 if (!d->importTopItem(iface)) 0482 { 0483 d->loading = false; 0484 d->scheduleReload = false; 0485 return; 0486 } 0487 0488 KJob* importJob = iface->createImportJob(d->topItem ); 0489 setReloadJob(importJob); 0490 d->fullReload = true; 0491 Core::self()->runController()->registerJob( importJob ); 0492 } 0493 0494 void Project::setReloadJob(KJob* job) 0495 { 0496 Q_D(Project); 0497 0498 d->loading = true; 0499 d->fullReload = false; 0500 d->progress->setBuzzy(); 0501 connect(job, &KJob::finished, 0502 this, [this] (KJob* job) { Q_D(Project); d->reloadDone(job); }); 0503 } 0504 0505 bool Project::open( const Path& projectFile ) 0506 { 0507 Q_D(Project); 0508 0509 d->initProject(projectFile); 0510 if (!d->initProjectFiles()) 0511 return false; 0512 0513 KConfigGroup projectGroup = d->initKConfigObject(); 0514 if (d->projectNameUsed(projectGroup)) 0515 return false; 0516 0517 d->projectPath = d->projectFile.parent(); 0518 0519 IProjectFileManager* iface = d->fetchFileManager(projectGroup); 0520 if (!iface) 0521 return false; 0522 0523 Q_ASSERT(d->manager); 0524 0525 emit aboutToOpen(this); 0526 if (!d->importTopItem(iface) ) { 0527 return false; 0528 } 0529 0530 d->loading=true; 0531 d->loadVersionControlPlugin(projectGroup); 0532 d->progress->setBuzzy(); 0533 KJob* importJob = iface->createImportJob(d->topItem ); 0534 connect(importJob, &KJob::result, 0535 this, [this] (KJob* job) { Q_D(Project); d->importDone(job); } ); 0536 Core::self()->runController()->registerJob( importJob ); 0537 return true; 0538 } 0539 0540 void Project::close() 0541 { 0542 Q_D(Project); 0543 0544 Q_ASSERT(d->topItem); 0545 if (d->topItem->row() == -1) { 0546 qCWarning(SHELL) << "Something went wrong. ProjectFolderItem detached. Project closed during reload?"; 0547 return; 0548 } 0549 0550 Core::self()->projectController()->projectModel()->removeRow( d->topItem->row() ); 0551 0552 if (!d->developerFile.isLocalFile()) 0553 { 0554 auto copyJob = KIO::file_copy(QUrl::fromLocalFile(d->developerTempFile), d->developerFile.toUrl(), -1, KIO::HideProgressInfo); 0555 KJobWidgets::setWindow(copyJob, Core::self()->uiController()->activeMainWindow()); 0556 if (!copyJob->exec()) { 0557 qCDebug(SHELL) << "Job failed:" << copyJob->errorString(); 0558 0559 KMessageBox::error(Core::self()->uiController()->activeMainWindow(), 0560 i18n("Could not store developer specific project configuration.\n" 0561 "Attention: The project settings you changed will be lost.")); 0562 } 0563 } 0564 } 0565 0566 bool Project::inProject( const IndexedString& path ) const 0567 { 0568 Q_D(const Project); 0569 0570 if (d->fileSet.contains( path )) { 0571 return true; 0572 } 0573 return !d->itemsForPath( path ).isEmpty(); 0574 } 0575 0576 QList< ProjectBaseItem* > Project::itemsForPath(const IndexedString& path) const 0577 { 0578 Q_D(const Project); 0579 0580 return d->itemsForPath(path); 0581 } 0582 0583 QList< ProjectFileItem* > Project::filesForPath(const IndexedString& file) const 0584 { 0585 Q_D(const Project); 0586 0587 QList<ProjectFileItem*> fileItems; 0588 const auto items = d->itemsForPath(file); 0589 for (ProjectBaseItem* item : items) { 0590 if( item->type() == ProjectBaseItem::File ) 0591 fileItems << static_cast<ProjectFileItem*>(item); 0592 } 0593 return fileItems; 0594 } 0595 0596 QList<ProjectFolderItem*> Project::foldersForPath(const IndexedString& folder) const 0597 { 0598 Q_D(const Project); 0599 0600 QList<ProjectFolderItem*> folderItems; 0601 const auto items = d->itemsForPath(folder); 0602 for (ProjectBaseItem* item : items) { 0603 if( item->type() == ProjectBaseItem::Folder || item->type() == ProjectBaseItem::BuildFolder ) 0604 folderItems << static_cast<ProjectFolderItem*>(item); 0605 } 0606 return folderItems; 0607 } 0608 0609 IProjectFileManager* Project::projectFileManager() const 0610 { 0611 Q_D(const Project); 0612 0613 return d->manager->extension<IProjectFileManager>(); 0614 } 0615 0616 IBuildSystemManager* Project::buildSystemManager() const 0617 { 0618 Q_D(const Project); 0619 0620 return d->manager->extension<IBuildSystemManager>(); 0621 } 0622 0623 IPlugin* Project::managerPlugin() const 0624 { 0625 Q_D(const Project); 0626 0627 return d->manager; 0628 } 0629 0630 void Project::setManagerPlugin( IPlugin* manager ) 0631 { 0632 Q_D(Project); 0633 0634 d->manager = manager; 0635 } 0636 0637 Path Project::projectFile() const 0638 { 0639 Q_D(const Project); 0640 0641 return d->projectFile; 0642 } 0643 0644 Path Project::developerFile() const 0645 { 0646 Q_D(const Project); 0647 0648 return d->developerFile; 0649 } 0650 0651 ProjectFolderItem* Project::projectItem() const 0652 { 0653 Q_D(const Project); 0654 0655 return d->topItem; 0656 } 0657 0658 IPlugin* Project::versionControlPlugin() const 0659 { 0660 Q_D(const Project); 0661 0662 return d->vcsPlugin.data(); 0663 } 0664 0665 void Project::addToFileSet( ProjectFileItem* file ) 0666 { 0667 Q_D(Project); 0668 0669 if (d->fileSet.contains(file->indexedPath())) { 0670 return; 0671 } 0672 0673 d->fileSet.insert( file->indexedPath() ); 0674 emit fileAddedToSet( file ); 0675 } 0676 0677 void Project::removeFromFileSet( ProjectFileItem* file ) 0678 { 0679 Q_D(Project); 0680 0681 QSet<IndexedString>::iterator it = d->fileSet.find(file->indexedPath()); 0682 if (it == d->fileSet.end()) { 0683 return; 0684 } 0685 0686 d->fileSet.erase( it ); 0687 emit fileRemovedFromSet( file ); 0688 } 0689 0690 QSet<IndexedString> Project::fileSet() const 0691 { 0692 Q_D(const Project); 0693 0694 return d->fileSet; 0695 } 0696 0697 bool Project::isReady() const 0698 { 0699 Q_D(const Project); 0700 0701 return !d->loading; 0702 } 0703 0704 } // namespace KDevelop 0705 0706 #include "project.moc" 0707 #include "moc_project.cpp"