File indexing completed on 2024-05-05 05:30:13

0001 /*
0002     SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0003     SPDX-FileCopyrightText: 2021-2022 Harald Sitter <sitter@kde.org>
0004 */
0005 
0006 #include "CommandOutputContext.h"
0007 
0008 #include <utility>
0009 
0010 #include <QDebug>
0011 #include <QProcess>
0012 #include <QStandardPaths>
0013 
0014 #include <KLocalizedString>
0015 #include <KOSRelease>
0016 
0017 CommandOutputContext::CommandOutputContext(const QStringList &findExecutables, const QString &executable, const QStringList &arguments, QObject *parent)
0018     : QObject(parent)
0019     , m_executableName(executable)
0020     , m_executablePath(QStandardPaths::findExecutable(m_executableName))
0021     , m_arguments(arguments)
0022     , m_bugReportUrl(KOSRelease().bugReportUrl())
0023 {
0024     // Various utilities are installed in sbin, but work without elevated privileges
0025     if (m_executablePath.isEmpty()) {
0026         m_executablePath =
0027             QStandardPaths::findExecutable(m_executableName, {QStringLiteral("/usr/local/sbin"), QStringLiteral("/usr/sbin"), QStringLiteral("/sbin")});
0028     }
0029 
0030     m_foundExecutablePaths[executable] = m_executablePath;
0031     for (const QString &findExecutable : findExecutables) {
0032         m_foundExecutablePaths[findExecutable] = QStandardPaths::findExecutable(findExecutable);
0033     }
0034 
0035     metaObject()->invokeMethod(this, &CommandOutputContext::load);
0036 }
0037 
0038 CommandOutputContext::CommandOutputContext(const QString &executable, const QStringList &arguments, QObject *parent)
0039     : CommandOutputContext({/* executable is by default always searched for */}, executable, arguments, parent)
0040 {
0041 }
0042 
0043 QString CommandOutputContext::executableName() const
0044 {
0045     return m_executableName;
0046 }
0047 
0048 QStringList CommandOutputContext::arguments() const
0049 {
0050     return m_arguments;
0051 }
0052 
0053 QString CommandOutputContext::filter() const
0054 {
0055     return m_filter;
0056 }
0057 
0058 void CommandOutputContext::setFilter(const QString &filter)
0059 {
0060     m_filter = filter;
0061     if (m_filter.isEmpty()) {
0062         m_text = m_originalLines.join('\n');
0063     } else {
0064         m_text.clear();
0065         for (const QString &line : std::as_const(m_originalLines)) {
0066             if (line.contains(filter, Qt::CaseInsensitive)) {
0067                 m_text += line + '\n';
0068             }
0069         }
0070     }
0071     Q_EMIT textChanged();
0072     Q_EMIT filterChanged();
0073 }
0074 
0075 void CommandOutputContext::reset()
0076 {
0077     m_ready = false;
0078     m_error.clear();
0079     m_explanation.clear();
0080     m_text.clear();
0081     m_filter.clear();
0082     Q_EMIT readyChanged();
0083     Q_EMIT errorChanged();
0084     Q_EMIT explanationChanged();
0085     Q_EMIT textChanged();
0086     Q_EMIT filterChanged();
0087 
0088     // Not exposed as properties
0089     m_originalLines.clear();
0090 }
0091 
0092 void CommandOutputContext::load()
0093 {
0094     reset();
0095 
0096     for (auto it = m_foundExecutablePaths.cbegin(); it != m_foundExecutablePaths.cend(); ++it) {
0097         if (it.value().isEmpty()) {
0098             setError(xi18nc("@info", "The <command>%1</command> tool is required to display this page, but could not be found", it.key()),
0099                      xi18nc("@info",
0100                             "You can search for it and install it using your package manager.<nl/>"
0101                             "Then please report this packaging issue to your distribution."));
0102             return;
0103         }
0104     }
0105 
0106     auto proc = new QProcess(this);
0107     proc->setProcessChannelMode(QProcess::MergedChannels);
0108     connect(proc, &QProcess::finished, this, [this, proc](int /* exitCode */, QProcess::ExitStatus exitStatus) {
0109         proc->deleteLater();
0110 
0111         switch (exitStatus) {
0112         case QProcess::CrashExit:
0113             return setError(xi18nc("@info", "The <command>%1</command> tool crashed while generating page content", m_executableName),
0114                             xi18nc("@Info", "Try again later. If keeps happening, please report the crash to your distribution."));
0115         case QProcess::NormalExit:
0116             break;
0117         }
0118 
0119         m_text = QString::fromLocal8Bit(proc->readAllStandardOutput());
0120         if (m_trimAllowed) {
0121             m_text = m_text.trimmed();
0122         }
0123         m_originalLines = m_text.split('\n');
0124         if (!m_filter.isEmpty()) {
0125             // re-apply filter on new text
0126             setFilter(m_filter);
0127         }
0128 
0129         Q_EMIT textChanged();
0130         setReady();
0131     });
0132     proc->start(m_executablePath, m_arguments);
0133 }
0134 
0135 void CommandOutputContext::setError(const QString &message, const QString &explanation = QString())
0136 {
0137     m_error = message;
0138 
0139     if (!explanation.isEmpty()) {
0140         m_explanation = explanation;
0141     }
0142 
0143     Q_EMIT errorChanged();
0144     Q_EMIT explanationChanged();
0145 
0146     setReady();
0147 }
0148 
0149 void CommandOutputContext::setReady()
0150 {
0151     m_ready = true;
0152     Q_EMIT readyChanged();
0153 }
0154 
0155 void CommandOutputContext::setTrimAllowed(bool allow)
0156 {
0157     m_trimAllowed = allow;
0158 }
0159 
0160 #include "moc_CommandOutputContext.cpp"