File indexing completed on 2024-05-05 05:50:42

0001 /*
0002     SPDX-FileCopyrightText: 2016 Elvis Angelaccio <elvis.angelaccio@kde.org>
0003 
0004     SPDX-License-Identifier: BSD-2-Clause
0005 */
0006 
0007 #include "pluginmanager.h"
0008 #include "ark_debug.h"
0009 #include "settings.h"
0010 
0011 #include <KSharedConfig>
0012 
0013 #include <QFileInfo>
0014 #include <QMimeDatabase>
0015 #include <QPluginLoader>
0016 #include <QProcess>
0017 #include <QRegularExpression>
0018 #include <QSet>
0019 
0020 #include <algorithm>
0021 
0022 namespace Kerfuffle
0023 {
0024 PluginManager::PluginManager(QObject *parent)
0025     : QObject(parent)
0026 {
0027     loadPlugins();
0028 }
0029 
0030 QVector<Plugin *> PluginManager::installedPlugins() const
0031 {
0032     return m_plugins;
0033 }
0034 
0035 QVector<Plugin *> PluginManager::availablePlugins() const
0036 {
0037     QVector<Plugin *> availablePlugins;
0038     for (Plugin *plugin : std::as_const(m_plugins)) {
0039         if (plugin->isValid()) {
0040             availablePlugins << plugin;
0041         }
0042     }
0043 
0044     return availablePlugins;
0045 }
0046 
0047 QVector<Plugin *> PluginManager::availableWritePlugins() const
0048 {
0049     QVector<Plugin *> availableWritePlugins;
0050     const auto plugins = availablePlugins();
0051     for (Plugin *plugin : plugins) {
0052         if (plugin->isReadWrite()) {
0053             availableWritePlugins << plugin;
0054         }
0055     }
0056 
0057     return availableWritePlugins;
0058 }
0059 
0060 QVector<Plugin *> PluginManager::enabledPlugins() const
0061 {
0062     QVector<Plugin *> enabledPlugins;
0063     for (Plugin *plugin : std::as_const(m_plugins)) {
0064         if (plugin->isEnabled()) {
0065             enabledPlugins << plugin;
0066         }
0067     }
0068 
0069     return enabledPlugins;
0070 }
0071 
0072 QVector<Plugin *> PluginManager::preferredPluginsFor(const QMimeType &mimeType)
0073 {
0074     const auto mimeName = mimeType.name();
0075     if (m_preferredPluginsCache.contains(mimeName)) {
0076         return m_preferredPluginsCache.value(mimeName);
0077     }
0078 
0079     const auto plugins = preferredPluginsFor(mimeType, false);
0080     m_preferredPluginsCache.insert(mimeName, plugins);
0081     return plugins;
0082 }
0083 
0084 QVector<Plugin *> PluginManager::preferredWritePluginsFor(const QMimeType &mimeType) const
0085 {
0086     return preferredPluginsFor(mimeType, true);
0087 }
0088 
0089 Plugin *PluginManager::preferredPluginFor(const QMimeType &mimeType)
0090 {
0091     const QVector<Plugin *> preferredPlugins = preferredPluginsFor(mimeType);
0092     return preferredPlugins.isEmpty() ? new Plugin() : preferredPlugins.first();
0093 }
0094 
0095 Plugin *PluginManager::preferredWritePluginFor(const QMimeType &mimeType) const
0096 {
0097     const QVector<Plugin *> preferredWritePlugins = preferredWritePluginsFor(mimeType);
0098     return preferredWritePlugins.isEmpty() ? new Plugin() : preferredWritePlugins.first();
0099 }
0100 
0101 QStringList PluginManager::supportedMimeTypes(MimeSortingMode mode) const
0102 {
0103     QSet<QString> supported;
0104     QMimeDatabase db;
0105     const auto plugins = availablePlugins();
0106     for (Plugin *plugin : plugins) {
0107         const auto mimeTypes = plugin->metaData().mimeTypes();
0108         for (const auto &mimeType : mimeTypes) {
0109             if (db.mimeTypeForName(mimeType).isValid()) {
0110                 supported.insert(mimeType);
0111             }
0112         }
0113     }
0114 
0115     // Remove entry for lrzipped tar if lrzip executable not found in path.
0116     if (QStandardPaths::findExecutable(QStringLiteral("lrzip")).isEmpty()) {
0117         supported.remove(QStringLiteral("application/x-lrzip-compressed-tar"));
0118     }
0119 
0120     // Remove entry for lz4-compressed tar if lz4 executable not found in path.
0121     if (QStandardPaths::findExecutable(QStringLiteral("lz4")).isEmpty()) {
0122         supported.remove(QStringLiteral("application/x-lz4-compressed-tar"));
0123     }
0124 
0125     static bool s_libarchiveHasLzo = libarchiveHasLzo();
0126     // Remove entry for lzo-compressed tar if libarchive not linked against lzo and lzop executable not found in path.
0127     if (!s_libarchiveHasLzo && QStandardPaths::findExecutable(QStringLiteral("lzop")).isEmpty()) {
0128         supported.remove(QStringLiteral("application/x-tzo"));
0129     }
0130 
0131     if (mode == SortByComment) {
0132         return sortByComment(supported);
0133     }
0134 
0135     return supported.values();
0136 }
0137 
0138 QStringList PluginManager::supportedWriteMimeTypes(MimeSortingMode mode) const
0139 {
0140     QSet<QString> supported;
0141     QMimeDatabase db;
0142     const auto plugins = availableWritePlugins();
0143     for (Plugin *plugin : plugins) {
0144         const auto mimeTypes = plugin->metaData().mimeTypes();
0145         for (const auto &mimeType : mimeTypes) {
0146             if (db.mimeTypeForName(mimeType).isValid()) {
0147                 supported.insert(mimeType);
0148             }
0149         }
0150     }
0151 
0152     // Remove entry for lrzipped tar if lrzip executable not found in path.
0153     if (QStandardPaths::findExecutable(QStringLiteral("lrzip")).isEmpty()) {
0154         supported.remove(QStringLiteral("application/x-lrzip-compressed-tar"));
0155     }
0156 
0157     // Remove entry for lz4-compressed tar if lz4 executable not found in path.
0158     if (QStandardPaths::findExecutable(QStringLiteral("lz4")).isEmpty()) {
0159         supported.remove(QStringLiteral("application/x-lz4-compressed-tar"));
0160     }
0161 
0162     // Remove entry for lzo-compressed tar if libarchive not linked against lzo and lzop executable not found in path.
0163     if (!libarchiveHasLzo() && QStandardPaths::findExecutable(QStringLiteral("lzop")).isEmpty()) {
0164         supported.remove(QStringLiteral("application/x-tzo"));
0165     }
0166 
0167     // shared-mime-info 2.3 explicitly separated application/x-bzip2-compressed-tar from application/x-bzip-compressed-tar
0168     // since bzip2 is not compatible with the old (and deprecated) bzip format.
0169     // See https://gitlab.freedesktop.org/xdg/shared-mime-info/-/merge_requests/239
0170     // With shared-mime-info 2.3 (or newer) we can't have both mimetypes at the same time, since libarchive does not support
0171     // the old deprecated bzip format. Also we can't know which version of shared-mime-info the system is actually using.
0172     // For these reasons, just take the mimetype from QMimeDatabase to keep the compatibility with any shared-mime-info version.
0173     if (supported.contains(QLatin1String("application/x-bzip-compressed-tar")) && supported.contains(QLatin1String("application/x-bzip2-compressed-tar"))) {
0174         supported.remove(QLatin1String("application/x-bzip-compressed-tar"));
0175         supported.remove(QLatin1String("application/x-bzip2-compressed-tar"));
0176         supported.insert(QMimeDatabase().mimeTypeForFile(QStringLiteral("dummy.tar.bz2"), QMimeDatabase::MatchExtension).name());
0177     }
0178 
0179     if (mode == SortByComment) {
0180         return sortByComment(supported);
0181     }
0182 
0183     return supported.values();
0184 }
0185 
0186 QVector<Plugin *> PluginManager::filterBy(const QVector<Plugin *> &plugins, const QMimeType &mimeType) const
0187 {
0188     const bool supportedMime = supportedMimeTypes().contains(mimeType.name());
0189     QVector<Plugin *> filteredPlugins;
0190     for (Plugin *plugin : plugins) {
0191         if (!supportedMime) {
0192             // Check whether the mimetype inherits from a supported mimetype.
0193             const QStringList mimeTypes = plugin->metaData().mimeTypes();
0194             for (const QString &mime : mimeTypes) {
0195                 if (mimeType.inherits(mime)) {
0196                     filteredPlugins << plugin;
0197                 }
0198             }
0199         } else if (plugin->metaData().mimeTypes().contains(mimeType.name())) {
0200             filteredPlugins << plugin;
0201         }
0202     }
0203 
0204     return filteredPlugins;
0205 }
0206 
0207 void PluginManager::loadPlugins()
0208 {
0209     const QVector<KPluginMetaData> plugins = KPluginMetaData::findPlugins(QStringLiteral("kerfuffle"));
0210     for (const KPluginMetaData &metaData : plugins) {
0211         Plugin *plugin = new Plugin(this, metaData);
0212         plugin->setEnabled(!ArkSettings::disabledPlugins().contains(metaData.pluginId()));
0213         m_plugins << plugin;
0214     }
0215 }
0216 
0217 QVector<Plugin *> PluginManager::preferredPluginsFor(const QMimeType &mimeType, bool readWrite) const
0218 {
0219     QVector<Plugin *> preferredPlugins = filterBy((readWrite ? availableWritePlugins() : availablePlugins()), mimeType);
0220 
0221     std::sort(preferredPlugins.begin(), preferredPlugins.end(), [](Plugin *p1, Plugin *p2) {
0222         return p1->priority() > p2->priority();
0223     });
0224 
0225     return preferredPlugins;
0226 }
0227 
0228 QStringList PluginManager::sortByComment(const QSet<QString> &mimeTypes)
0229 {
0230     QMap<QString, QString> map;
0231 
0232     // Initialize the QMap to sort by comment.
0233     for (const QString &mimeType : mimeTypes) {
0234         QMimeType mime(QMimeDatabase().mimeTypeForName(mimeType));
0235         map[mime.comment().toLower()] = mime.name();
0236     }
0237 
0238     // Convert to sorted QStringList.
0239     QStringList sortedMimeTypes;
0240     for (const QString &value : std::as_const(map)) {
0241         sortedMimeTypes << value;
0242     }
0243 
0244     return sortedMimeTypes;
0245 }
0246 
0247 bool PluginManager::libarchiveHasLzo()
0248 {
0249     // Step 1: look for the libarchive plugin, which is built against libarchive.
0250     const QString pluginPath = QPluginLoader(QStringLiteral("kerfuffle/kerfuffle_libarchive")).fileName();
0251 
0252     // Step 2: process the libarchive plugin dependencies to figure out the absolute libarchive path.
0253     QProcess dependencyTool;
0254     QStringList args;
0255 #ifdef DEPENDENCY_TOOL_ARGS
0256     args << QStringLiteral(DEPENDENCY_TOOL_ARGS);
0257 #endif
0258     dependencyTool.setProgram(QStringLiteral(DEPENDENCY_TOOL));
0259     dependencyTool.setArguments(args + QStringList(pluginPath));
0260     dependencyTool.start();
0261     dependencyTool.waitForFinished();
0262     const QString output = QString::fromUtf8(dependencyTool.readAllStandardOutput());
0263     QRegularExpression regex(QStringLiteral("/.*/libarchive.so|/.*/libarchive.*.dylib"));
0264     if (!regex.match(output).hasMatch()) {
0265         return false;
0266     }
0267 
0268     // Step 3: check whether libarchive links against liblzo.
0269     const QStringList libarchivePath(regex.match(output).captured(0));
0270     dependencyTool.setArguments(args + libarchivePath);
0271     dependencyTool.start();
0272     dependencyTool.waitForFinished();
0273     return dependencyTool.readAllStandardOutput().contains(QByteArrayLiteral("lzo"));
0274 }
0275 
0276 }
0277 
0278 #include "moc_pluginmanager.cpp"