diff --git a/language/codegen/templatesmodel.cpp b/language/codegen/templatesmodel.cpp index a8753e291..9d4b106e4 100644 --- a/language/codegen/templatesmodel.cpp +++ b/language/codegen/templatesmodel.cpp @@ -1,411 +1,416 @@ /* This file is part of KDevelop Copyright 2007 Alexander Dymo Copyright 2012 Miha Čančula This library 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 library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "templatesmodel.h" #include "templatepreviewicon.h" #include #include #include #include #include #include #include #include #include #include #include #include +#include using namespace KDevelop; class KDevelop::TemplatesModelPrivate { public: explicit TemplatesModelPrivate(const QString& typePrefix); QString typePrefix; QStringList searchPaths; QMap templateItems; /** * Extracts description files from all available template archives and saves them to a location * determined by descriptionResourceSuffix(). **/ void extractTemplateDescriptions(); /** * Creates a model item for the template @p name in category @p category * * @param name the name of the new template * @param category the category of the new template * @param parent the parent item * @return the created item **/ QStandardItem *createItem(const QString& name, const QString& category, QStandardItem* parent); enum ResourceType { Description, Template, Preview }; QString resourceFilter(ResourceType type, const QString &suffix = QString()) { QString filter = typePrefix; switch(type) { case Description: filter += QLatin1String("template_descriptions/"); break; case Template: filter += QLatin1String("templates/"); break; case Preview: filter += QLatin1String("template_previews/"); break; } return filter + suffix; } }; TemplatesModelPrivate::TemplatesModelPrivate(const QString& _typePrefix) : typePrefix(_typePrefix) { if (!typePrefix.endsWith('/')) { typePrefix.append('/'); } } TemplatesModel::TemplatesModel(const QString& typePrefix, QObject* parent) : QStandardItemModel(parent) , d(new TemplatesModelPrivate(typePrefix)) { const QStringList dataPaths = {QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation)}; foreach(const QString& dataPath, dataPaths) { addDataPath(dataPath); } } TemplatesModel::~TemplatesModel() { delete d; } void TemplatesModel::refresh() { clear(); d->templateItems.clear(); d->templateItems[QString()] = invisibleRootItem(); d->extractTemplateDescriptions(); QStringList templateArchives; foreach(const QString& archivePath, d->searchPaths) { const QStringList files = QDir(archivePath).entryList(QDir::Files); foreach(const QString& file, files) { templateArchives.append(archivePath + file); } } QStringList templateDescriptions; const QStringList templatePaths = {QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) +'/'+ d->resourceFilter(TemplatesModelPrivate::Description)}; foreach(const QString& templateDescription, templatePaths) { const QStringList files = QDir(templateDescription).entryList(QDir::Files); foreach(const QString& file, files) { templateDescriptions.append(templateDescription + file); } } foreach (const QString &templateDescription, templateDescriptions) { QFileInfo fi(templateDescription); bool archiveFound = false; foreach( const QString& templateArchive, templateArchives ) { if( QFileInfo(templateArchive).baseName() == fi.baseName() ) { archiveFound = true; KConfig templateConfig(templateDescription); KConfigGroup general(&templateConfig, "General"); QString name = general.readEntry("Name"); QString category = general.readEntry("Category"); QString comment = general.readEntry("Comment"); TemplatePreviewIcon icon(general.readEntry("Icon"), templateArchive, d->resourceFilter(TemplatesModelPrivate::Preview)); QStandardItem *templateItem = d->createItem(name, category, invisibleRootItem()); templateItem->setData(templateDescription, DescriptionFileRole); templateItem->setData(templateArchive, ArchiveFileRole); templateItem->setData(comment, CommentRole); templateItem->setData(QVariant::fromValue(icon), PreviewIconRole); } } if (!archiveFound) { // Template file doesn't exist anymore, so remove the description // saves us the extra lookups for templateExists on the next run QFile(templateDescription).remove(); } } } QStandardItem *TemplatesModelPrivate::createItem(const QString& name, const QString& category, QStandardItem* parent) { QStringList path = category.split('/'); QStringList currentPath; foreach (const QString& entry, path) { currentPath << entry; if (!templateItems.contains(currentPath.join(QLatin1Char('/')))) { QStandardItem *item = new QStandardItem(entry); item->setEditable(false); parent->appendRow(item); templateItems[currentPath.join(QLatin1Char('/'))] = item; parent = item; } else { parent = templateItems[currentPath.join(QLatin1Char('/'))]; } } QStandardItem *templateItem = new QStandardItem(name); templateItem->setEditable(false); parent->appendRow(templateItem); return templateItem; } void TemplatesModelPrivate::extractTemplateDescriptions() { QStringList templateArchives; searchPaths << QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, resourceFilter(Template), QStandardPaths::LocateDirectory); searchPaths.removeDuplicates(); foreach(const QString &archivePath, searchPaths) { const QStringList files = QDir(archivePath).entryList(QDir::Files); foreach(const QString& file, files) { if(file.endsWith(QLatin1String(".zip")) || file.endsWith(QLatin1String(".tar.bz2"))) { QString archfile = archivePath + file; templateArchives.append(archfile); } } } QString localDescriptionsDir = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) +'/'+ resourceFilter(Description); QDir dir(localDescriptionsDir); if(!dir.exists()) dir.mkpath(QStringLiteral(".")); foreach (const QString &archName, templateArchives) { qCDebug(LANGUAGE) << "processing template" << archName; QScopedPointer templateArchive; if (QFileInfo(archName).completeSuffix() == QLatin1String("zip")) { templateArchive.reset(new KZip(archName)); } else { templateArchive.reset(new KTar(archName)); } if (templateArchive->open(QIODevice::ReadOnly)) { /* * This class looks for template description files in the following order * * - "basename.kdevtemplate" * - "*.kdevtemplate" * - "basename.desktop" * - "*.desktop" * * This is done because application templates can contain .desktop files used by the application * so the kdevtemplate suffix must have priority. */ QFileInfo templateInfo(archName); + QString suffix = QStringLiteral(".kdevtemplate"); const KArchiveEntry *templateEntry = - templateArchive->directory()->entry(templateInfo.baseName() + ".kdevtemplate"); + templateArchive->directory()->entry(templateInfo.baseName() + suffix); if (!templateEntry || !templateEntry->isFile()) { /* * First, if the .kdevtemplate file is not found by name, * we check all the files in the archive for any .kdevtemplate file * * This is needed because kde-files.org renames downloaded files */ foreach (const QString& entryName, templateArchive->directory()->entries()) { - if (entryName.endsWith(QLatin1String(".kdevtemplate"))) - { + if (entryName.endsWith(suffix)) { templateEntry = templateArchive->directory()->entry(entryName); break; } } } if (!templateEntry || !templateEntry->isFile()) { - templateEntry = templateArchive->directory()->entry(templateInfo.baseName() + ".desktop"); + suffix = QStringLiteral(".desktop"); + templateEntry = templateArchive->directory()->entry(templateInfo.baseName() + suffix); } if (!templateEntry || !templateEntry->isFile()) { foreach (const QString& entryName, templateArchive->directory()->entries()) { - if (entryName.endsWith(QLatin1String(".desktop"))) - { + if (entryName.endsWith(suffix)) { templateEntry = templateArchive->directory()->entry(entryName); break; } } } if (!templateEntry || !templateEntry->isFile()) { qCDebug(LANGUAGE) << "template" << archName << "does not contain .kdevtemplate or .desktop file"; continue; } const KArchiveFile *templateFile = static_cast(templateEntry); qCDebug(LANGUAGE) << "copy template description to" << localDescriptionsDir; - templateFile->copyTo(localDescriptionsDir); - - /* - * Rename the extracted description - * so that its basename matches the basename of the template archive - */ - QFileInfo descriptionInfo(localDescriptionsDir + templateEntry->name()); - QString destinationName = localDescriptionsDir + templateInfo.baseName() + '.' + descriptionInfo.suffix(); - QFile::rename(descriptionInfo.absoluteFilePath(), destinationName); + const QString descriptionFileName = templateInfo.baseName() + suffix; + if (templateFile->name() == descriptionFileName) { + templateFile->copyTo(localDescriptionsDir); + } else { + // Rename the extracted description + // so that its basename matches the basename of the template archive + // Use temporary dir to not overwrite other files with same name + QTemporaryDir dir; + templateFile->copyTo(dir.path()); + const QString destinationPath = localDescriptionsDir + descriptionFileName; + QFile::remove(destinationPath); + QFile::rename(dir.path() + QLatin1Char('/') + templateFile->name(), destinationPath); + } } else { qCWarning(LANGUAGE) << "could not open template" << archName; } } } QModelIndexList TemplatesModel::templateIndexes(const QString& fileName) const { QFileInfo info(fileName); QString description = QStandardPaths::locate(QStandardPaths::GenericDataLocation, d->resourceFilter(TemplatesModelPrivate::Description, info.baseName() + ".kdevtemplate")); if (description.isEmpty()) { description = QStandardPaths::locate(QStandardPaths::GenericDataLocation, d->resourceFilter(TemplatesModelPrivate::Description, info.baseName() + ".desktop")); } QModelIndexList indexes; if (!description.isEmpty()) { KConfig templateConfig(description); KConfigGroup general(&templateConfig, "General"); QStringList categories = general.readEntry("Category").split('/'); QStringList levels; foreach (const QString& category, categories) { levels << category; indexes << d->templateItems[levels.join(QString('/'))]->index(); } if (!indexes.isEmpty()) { QString name = general.readEntry("Name"); QStandardItem* categoryItem = d->templateItems[levels.join(QString('/'))]; for (int i = 0; i < categoryItem->rowCount(); ++i) { QStandardItem* templateItem = categoryItem->child(i); if (templateItem->text() == name) { indexes << templateItem->index(); break; } } } } return indexes; } QString TemplatesModel::typePrefix() const { return d->typePrefix; } void TemplatesModel::addDataPath(const QString& path) { QString realpath = path + d->resourceFilter(TemplatesModelPrivate::Template); d->searchPaths.append(realpath); } QString TemplatesModel::loadTemplateFile(const QString& fileName) { QString saveLocation = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) +'/'+ d->resourceFilter(TemplatesModelPrivate::Template); QDir dir(saveLocation); if(!dir.exists()) dir.mkpath(QStringLiteral(".")); QFileInfo info(fileName); QString destination = saveLocation + info.baseName(); QMimeType mimeType = QMimeDatabase().mimeTypeForFile(fileName); qCDebug(LANGUAGE) << "Loaded file" << fileName << "with type" << mimeType.name(); if (mimeType.name() == QLatin1String("application/x-desktop")) { qCDebug(LANGUAGE) << "Loaded desktop file" << info.absoluteFilePath() << ", compressing"; #ifdef Q_WS_WIN destination += ".zip"; KZip archive(destination); #else destination += QLatin1String(".tar.bz2"); KTar archive(destination, QStringLiteral("application/x-bzip")); #endif //Q_WS_WIN archive.open(QIODevice::WriteOnly); QDir dir(info.absoluteDir()); QDir::Filters filter = QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot; foreach (const QFileInfo& entry, dir.entryInfoList(filter)) { if (entry.isFile()) { archive.addLocalFile(entry.absoluteFilePath(), entry.fileName()); } else if (entry.isDir()) { archive.addLocalDirectory(entry.absoluteFilePath(), entry.fileName()); } } archive.close(); } else { qCDebug(LANGUAGE) << "Copying" << fileName << "to" << saveLocation; QFile::copy(fileName, saveLocation + info.fileName()); } refresh(); return destination; } diff --git a/plugins/execute/nativeappjob.cpp b/plugins/execute/nativeappjob.cpp index 63dde740e..aa3a27a51 100644 --- a/plugins/execute/nativeappjob.cpp +++ b/plugins/execute/nativeappjob.cpp @@ -1,140 +1,153 @@ /* This file is part of KDevelop Copyright 2009 Andreas Pakulat This library 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 library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "nativeappjob.h" #include #include +#include #include #include #include #include #include #include #include #include #include #include -#include "iexecuteplugin.h" +#include "executeplugin.h" #include "debug.h" using namespace KDevelop; NativeAppJob::NativeAppJob(QObject* parent, KDevelop::ILaunchConfiguration* cfg) : KDevelop::OutputExecuteJob( parent ) - , m_cfgname(cfg->name()) + , m_name(cfg->name()) { + { + auto cfgGroup = cfg->config(); + if (cfgGroup.readEntry(ExecutePlugin::isExecutableEntry, false)) { + m_name = cfgGroup.readEntry(ExecutePlugin::executableEntry, cfg->name()).section('/', -1); + } + } setCapabilities(Killable); IExecutePlugin* iface = KDevelop::ICore::self()->pluginController()->pluginForExtension(QStringLiteral("org.kdevelop.IExecutePlugin"), QStringLiteral("kdevexecute"))->extension(); Q_ASSERT(iface); const KDevelop::EnvironmentProfileList environmentProfiles(KSharedConfig::openConfig()); QString envProfileName = iface->environmentProfileName(cfg); QString err; QUrl executable = iface->executable( cfg, err ); if( !err.isEmpty() ) { setError( -1 ); setErrorText( err ); return; } if (envProfileName.isEmpty()) { qCWarning(PLUGIN_EXECUTE) << "Launch Configuration:" << cfg->name() << i18n("No environment profile specified, looks like a broken " "configuration, please check run configuration '%1'. " "Using default environment profile.", cfg->name() ); envProfileName = environmentProfiles.defaultProfileName(); } setEnvironmentProfile(envProfileName); QStringList arguments = iface->arguments( cfg, err ); if( !err.isEmpty() ) { setError( -2 ); setErrorText( err ); } if( error() != 0 ) { qCWarning(PLUGIN_EXECUTE) << "Launch Configuration:" << cfg->name() << "oops, problem" << errorText(); return; } setStandardToolView(KDevelop::IOutputView::RunView); setBehaviours(KDevelop::IOutputView::AllowUserClose | KDevelop::IOutputView::AutoScroll); setFilteringStrategy(OutputModel::NativeAppErrorFilter); setProperties(DisplayStdout | DisplayStderr); // Now setup the process parameters QUrl wc = iface->workingDirectory( cfg ); if( !wc.isValid() || wc.isEmpty() ) { wc = QUrl::fromLocalFile( QFileInfo( executable.toLocalFile() ).absolutePath() ); } setWorkingDirectory( wc ); qCDebug(PLUGIN_EXECUTE) << "setting app:" << executable << arguments; if (iface->useTerminal(cfg)) { QString terminalCommand = iface->terminal(cfg); terminalCommand.replace(QLatin1String("%exe"), KShell::quoteArg( executable.toLocalFile()) ); terminalCommand.replace(QLatin1String("%workdir"), KShell::quoteArg( wc.toLocalFile()) ); QStringList args = KShell::splitArgs(terminalCommand); args.append( arguments ); *this << args; } else { *this << executable.toLocalFile(); *this << arguments; } - setJobName(cfg->name()); + setJobName(m_name); } NativeAppJob* findNativeJob(KJob* j) { NativeAppJob* job = qobject_cast(j); if (!job) { const QList jobs = j->findChildren(); if (!jobs.isEmpty()) job = jobs.first(); } return job; } void NativeAppJob::start() { // we kill any execution of the configuration - foreach(KJob* j, ICore::self()->runController()->currentJobs()) { - NativeAppJob* job = findNativeJob(j); - if (job && job != this && job->m_cfgname == m_cfgname) { - QMessageBox::StandardButton button = QMessageBox::question(nullptr, i18n("Job already running"), i18n("'%1' is already being executed. Should we kill the previous instance?", m_cfgname)); - if (button != QMessageBox::No && ICore::self()->runController()->currentJobs().contains(j)) - j->kill(); + auto currentJobs = ICore::self()->runController()->currentJobs(); + for (auto it = currentJobs.begin(); it != currentJobs.end();) { + NativeAppJob* job = findNativeJob(*it); + if (job && job != this && job->m_name == m_name) { + QMessageBox::StandardButton button = QMessageBox::question(nullptr, i18n("Job already running"), i18n("'%1' is already being executed. Should we kill the previous instance?", m_name)); + if (button != QMessageBox::No && ICore::self()->runController()->currentJobs().contains(*it)) { + (*it)->kill(); + } + currentJobs = ICore::self()->runController()->currentJobs(); + it = currentJobs.begin(); + } else { + ++it; } } OutputExecuteJob::start(); } diff --git a/plugins/execute/nativeappjob.h b/plugins/execute/nativeappjob.h index 62ba718f7..d7ca78989 100644 --- a/plugins/execute/nativeappjob.h +++ b/plugins/execute/nativeappjob.h @@ -1,43 +1,43 @@ /* This file is part of KDevelop Copyright 2009 Andreas Pakulat This library 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 library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KDEVPLATFORM_PLUGIN_NATIVEAPPJOB_H #define KDEVPLATFORM_PLUGIN_NATIVEAPPJOB_H #include namespace KDevelop { class ILaunchConfiguration; } class NativeAppJob : public KDevelop::OutputExecuteJob { Q_OBJECT public: NativeAppJob( QObject* parent, KDevelop::ILaunchConfiguration* cfg ); void start() override; private: - QString m_cfgname; + QString m_name; }; #endif diff --git a/util/executecompositejob.cpp b/util/executecompositejob.cpp index f224a6c53..4cdb06c5b 100644 --- a/util/executecompositejob.cpp +++ b/util/executecompositejob.cpp @@ -1,147 +1,149 @@ /* This file is part of KDevelop Copyright 2007-2008 Hamish Rodda This library 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 library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "executecompositejob.h" #include "debug.h" namespace KDevelop { class ExecuteCompositeJobPrivate { public: void startNextJob(KJob* job); bool m_killing = false; bool m_abortOnError = true; int m_jobIndex = -1; int m_jobCount = 0; }; ExecuteCompositeJob::ExecuteCompositeJob(QObject* parent, const QList& jobs) : KCompositeJob(parent), d(new ExecuteCompositeJobPrivate) { setCapabilities(Killable); qCDebug(UTIL) << "execute composite" << jobs; foreach(KJob* job, jobs) { if (!job) { qCWarning(UTIL) << "Added null job!"; continue; } addSubjob(job); if (objectName().isEmpty()) setObjectName(job->objectName()); } } ExecuteCompositeJob::~ExecuteCompositeJob() { delete d; } void ExecuteCompositeJobPrivate::startNextJob(KJob* job) { ++m_jobIndex; qCDebug(UTIL) << "starting:" << job; job->start(); } void ExecuteCompositeJob::start() { if(hasSubjobs()) { d->startNextJob(subjobs().first()); } else { emitResult(); } } bool ExecuteCompositeJob::addSubjob(KJob* job) { const bool success = KCompositeJob::addSubjob(job); if (!success) return false; ++d->m_jobCount; connect(job, SIGNAL(percent(KJob*,ulong)), this, SLOT(slotPercent(KJob*,ulong))); return true; } void ExecuteCompositeJob::slotPercent(KJob* job, unsigned long percent) { Q_UNUSED(job); Q_ASSERT(d->m_jobCount > 0); Q_ASSERT(d->m_jobIndex >= 0 && d->m_jobIndex < d->m_jobCount); const float ratio = (float)d->m_jobIndex / d->m_jobCount; const unsigned long totalPercent = ratio * 100 + ((float)percent / d->m_jobCount); emitPercent(totalPercent, 100); } void ExecuteCompositeJob::slotResult(KJob* job) { disconnect(job, SIGNAL(percent(KJob*,ulong)), this, SLOT(slotPercent(KJob*,ulong))); // jobIndex + 1 because this job just finished const float ratio = d->m_jobIndex != -1 ? (d->m_jobIndex + 1.0) / d->m_jobCount : 1.0; emitPercent(ratio * 100, 100); qCDebug(UTIL) << "finished: "<< job << job->error() << "percent:" << ratio * 100; + bool emitDone = false; if (d->m_abortOnError && job->error()) { qCDebug(UTIL) << "JOB ERROR:" << job->error() << job->errorString(); - KCompositeJob::slotResult(job); + KCompositeJob::slotResult(job); // calls emitResult() + emitDone = true; } else removeSubjob(job); if (hasSubjobs() && !error() && !d->m_killing) { qCDebug(UTIL) << "remaining: " << subjobs().count() << subjobs(); d->startNextJob(subjobs().first()); - } else { + } else if (!emitDone) { setError(job->error()); setErrorText(job->errorString()); emitResult(); } } bool ExecuteCompositeJob::doKill() { qCDebug(UTIL) << "Killing subjobs:" << subjobs().size(); d->m_killing = true; while(hasSubjobs()) { KJob* j = subjobs().first(); if (!j || j->kill()) { removeSubjob(j); } else { return false; } } return true; } void ExecuteCompositeJob::setAbortOnError(bool abort) { d->m_abortOnError = abort; } }