File indexing completed on 2024-05-05 04:39:43

0001 /*
0002     SPDX-FileCopyrightText: 2017 Aleix Pol Gonzalez <aleixpol@kde.org>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-only
0005 */
0006 
0007 #include "dockerplugin.h"
0008 #include "dockerruntime.h"
0009 #include "dockerpreferences.h"
0010 #include "dockerpreferencessettings.h"
0011 #include <interfaces/icore.h>
0012 #include <interfaces/iruntimecontroller.h>
0013 #include <interfaces/iuicontroller.h>
0014 #include <interfaces/context.h>
0015 #include <interfaces/contextmenuextension.h>
0016 #include <outputview/outputexecutejob.h>
0017 #include <project/projectmodel.h>
0018 #include <QStandardPaths>
0019 #include <QAction>
0020 #include <QRegularExpression>
0021 #include <QTemporaryDir>
0022 #include <QFileDialog>
0023 #include <QInputDialog>
0024 #include <QProcess>
0025 #include <KPluginFactory>
0026 #include <KActionCollection>
0027 #include <KLocalizedString>
0028 #include <KParts/MainWindow>
0029 #include <KJob>
0030 
0031 K_PLUGIN_FACTORY_WITH_JSON(KDevDockerFactory, "kdevdocker.json", registerPlugin<DockerPlugin>();)
0032 
0033 using namespace KDevelop;
0034 
0035 DockerPlugin::DockerPlugin(QObject *parent, const QVariantList & /*args*/)
0036     : KDevelop::IPlugin( QStringLiteral("kdevdocker"), parent )
0037     , m_settings(new DockerPreferencesSettings)
0038 {
0039     runtimeChanged(ICore::self()->runtimeController()->currentRuntime());
0040 
0041     setXMLFile( QStringLiteral("kdevdockerplugin.rc") );
0042     connect(ICore::self()->runtimeController(), &IRuntimeController::currentRuntimeChanged, this, &DockerPlugin::runtimeChanged);
0043 
0044     auto* process = new QProcess(this);
0045     connect(process, QOverload<int,QProcess::ExitStatus>::of(&QProcess::finished),
0046             this, &DockerPlugin::imagesListFinished);
0047     process->start(QStringLiteral("docker"), {QStringLiteral("images"), QStringLiteral("--filter"), QStringLiteral("dangling=false"), QStringLiteral("--format"), QStringLiteral("{{.Repository}}:{{.Tag}}\t{{.ID}}")}, QIODevice::ReadOnly);
0048 
0049     DockerRuntime::s_settings = m_settings.data();
0050 }
0051 
0052 DockerPlugin::~DockerPlugin()
0053 {
0054     DockerRuntime::s_settings = nullptr;
0055 }
0056 
0057 void DockerPlugin::imagesListFinished(int code)
0058 {
0059     if (code != 0)
0060         return;
0061 
0062     auto* process = qobject_cast<QProcess*>(sender());
0063     Q_ASSERT(process);
0064     QTextStream stream(process);
0065     while(!stream.atEnd()) {
0066         const QString line = stream.readLine();
0067         const QStringList parts = line.split(QLatin1Char('\t'));
0068 
0069         const QString tag = parts[0] == QLatin1String("<none>") ? parts[1] : parts[0];
0070         ICore::self()->runtimeController()->addRuntimes(new DockerRuntime(tag));
0071     }
0072 
0073     process->deleteLater();
0074     Q_EMIT imagesListed();
0075 }
0076 
0077 void DockerPlugin::runtimeChanged(KDevelop::IRuntime* newRuntime)
0078 {
0079     const bool isDocker = qobject_cast<DockerRuntime*>(newRuntime);
0080 
0081     const auto& actions = actionCollection()->actions();
0082     for (auto action: actions) {
0083         action->setEnabled(isDocker);
0084     }
0085 }
0086 
0087 KDevelop::ContextMenuExtension DockerPlugin::contextMenuExtension(KDevelop::Context* context, QWidget* parent)
0088 {
0089     QList<QUrl> urls;
0090 
0091     if ( context->type() == KDevelop::Context::FileContext ) {
0092         auto* filectx = static_cast<KDevelop::FileContext*>(context);
0093         urls = filectx->urls();
0094     } else if ( context->type() == KDevelop::Context::ProjectItemContext ) {
0095         auto* projctx = static_cast<KDevelop::ProjectItemContext*>(context);
0096         const auto items = projctx->items();
0097         for (KDevelop::ProjectBaseItem* item : items) {
0098             if ( item->file() ) {
0099                 urls << item->path().toUrl();
0100             }
0101         }
0102     }
0103 
0104     for(auto it = urls.begin(); it != urls.end(); ) {
0105         if (it->isLocalFile() && it->fileName() == QLatin1String("Dockerfile")) {
0106             ++it;
0107         } else {
0108             it = urls.erase(it);
0109         }
0110     }
0111 
0112     if ( !urls.isEmpty() ) {
0113         KDevelop::ContextMenuExtension ext;
0114         for (const QUrl& url : qAsConst(urls)) {
0115             const KDevelop::Path file(url);
0116 
0117             auto action = new QAction(QIcon::fromTheme(QStringLiteral("text-dockerfile")), i18n("docker build '%1'", file.path()), parent);
0118             connect(action, &QAction::triggered, this, [this, file]() {
0119                 const auto dir = file.parent();
0120                 const QString name = QInputDialog::getText(
0121                     ICore::self()->uiController()->activeMainWindow(), i18nc("@title:window", "Choose Tag Name"),
0122                     i18nc("@label:textbox", "Tag name for '%1':", file.path()),
0123                     QLineEdit::Normal, dir.lastPathSegment()
0124                 );
0125 
0126                 auto process = new OutputExecuteJob;
0127                 process->setExecuteOnHost(true);
0128                 process->setProperties(OutputExecuteJob::DisplayStdout | OutputExecuteJob::DisplayStderr);
0129                 // TODO: call process->setStandardToolView(IOutputView::?); to prevent creating a new tool view for each
0130                 // job in OutputJob::startOutput(). Such nonstandard and unshared tool views are also not configurable.
0131                 *process << QStringList{QStringLiteral("docker"), QStringLiteral("build"), QStringLiteral("--tag"), name, dir.toLocalFile()};
0132                 connect(process, &KJob::finished, this, [name] (KJob* job) {
0133                     if (job->error() != 0)
0134                         return;
0135 
0136                     ICore::self()->runtimeController()->addRuntimes(new DockerRuntime(name));
0137                 });
0138                 process->start();
0139             });
0140             ext.addAction(KDevelop::ContextMenuExtension::RunGroup, action);
0141         }
0142 
0143         return ext;
0144     }
0145 
0146     return KDevelop::IPlugin::contextMenuExtension(context, parent);
0147 }
0148 
0149 int DockerPlugin::configPages() const
0150 {
0151     return 1;
0152 }
0153 
0154 KDevelop::ConfigPage* DockerPlugin::configPage(int number, QWidget* parent)
0155 {
0156     if (number == 0) {
0157         return new DockerPreferences(this, m_settings.data(), parent);
0158     }
0159     return nullptr;
0160 }
0161 
0162 #include "dockerplugin.moc"
0163 #include "moc_dockerplugin.cpp"