File indexing completed on 2024-06-16 05:00:14

0001 /*
0002     bodypartformatterfactory.cpp
0003 
0004     This file is part of KMail, the KDE mail client.
0005     SPDX-FileCopyrightText: 2004 Marc Mutz <mutz@kde.org>
0006     SPDX-FileCopyrightText: 2004 Ingo Kloecker <kloecker@kde.org>
0007 
0008     SPDX-License-Identifier: GPL-2.0-or-later
0009 */
0010 
0011 #include "bodypartformatterfactory.h"
0012 #include "bodypartformatterfactory_p.h"
0013 #include "interfaces/bodypartformatter.h"
0014 #include "mimetreeparser_debug.h"
0015 
0016 #include <KPluginMetaData>
0017 #include <QJsonArray>
0018 #include <QMimeDatabase>
0019 
0020 #include <QPluginLoader>
0021 #include <cassert>
0022 
0023 using namespace MimeTreeParser;
0024 
0025 BodyPartFormatterFactoryPrivate::BodyPartFormatterFactoryPrivate(BodyPartFormatterFactory *factory)
0026     : q(factory)
0027 {
0028 }
0029 
0030 BodyPartFormatterFactoryPrivate::~BodyPartFormatterFactoryPrivate()
0031 {
0032     QHashIterator<QString, std::vector<FormatterInfo>> i(registry);
0033     while (i.hasNext()) {
0034         i.next();
0035         auto formatterInfo = i.value();
0036         formatterInfo.erase(formatterInfo.begin(), formatterInfo.end());
0037     }
0038 }
0039 
0040 void BodyPartFormatterFactoryPrivate::setup()
0041 {
0042     if (registry.empty()) {
0043         messageviewer_create_builtin_bodypart_formatters();
0044         q->loadPlugins();
0045     }
0046     assert(!registry.empty());
0047 }
0048 
0049 void BodyPartFormatterFactoryPrivate::insert(const QString &mimeType, const Interface::BodyPartFormatter *formatter, int priority)
0050 {
0051     if (mimeType.isEmpty() || !formatter) {
0052         return;
0053     }
0054 
0055     QMimeDatabase db;
0056     const auto mt = db.mimeTypeForName(mimeType);
0057     FormatterInfo info;
0058     info.formatter = formatter;
0059     info.priority = priority;
0060 
0061     auto &v = registry[mt.isValid() ? mt.name() : mimeType];
0062     v.push_back(info);
0063     std::stable_sort(v.begin(), v.end(), [](FormatterInfo lhs, FormatterInfo rhs) {
0064         return lhs.priority > rhs.priority;
0065     });
0066 }
0067 
0068 void BodyPartFormatterFactoryPrivate::appendFormattersForType(const QString &mimeType, QList<const Interface::BodyPartFormatter *> &formatters)
0069 {
0070     const auto it = registry.constFind(mimeType);
0071     if (it == registry.constEnd()) {
0072         return;
0073     }
0074     for (const auto &f : it.value()) {
0075         formatters.push_back(f.formatter);
0076     }
0077 }
0078 
0079 BodyPartFormatterFactory::BodyPartFormatterFactory()
0080     : d(new BodyPartFormatterFactoryPrivate(this))
0081 {
0082 }
0083 
0084 BodyPartFormatterFactory::~BodyPartFormatterFactory() = default;
0085 
0086 BodyPartFormatterFactory *BodyPartFormatterFactory::instance()
0087 {
0088     static BodyPartFormatterFactory s_instance;
0089     return &s_instance;
0090 }
0091 
0092 void BodyPartFormatterFactory::insert(const QString &mimeType, const Interface::BodyPartFormatter *formatter, int priority)
0093 {
0094     d->insert(mimeType.toLower(), formatter, priority);
0095 }
0096 
0097 QList<const Interface::BodyPartFormatter *> BodyPartFormatterFactory::formattersForType(const QString &mimeType) const
0098 {
0099     QList<const Interface::BodyPartFormatter *> r;
0100     d->setup();
0101 
0102     QMimeDatabase db;
0103     std::vector<QString> processedTypes;
0104     processedTypes.push_back(mimeType.toLower());
0105 
0106     // add all formatters we have along the mimetype hierarchy
0107     for (std::size_t i = 0; i < processedTypes.size(); ++i) {
0108         const auto mt = db.mimeTypeForName(processedTypes[i]);
0109         if (mt.isValid()) {
0110             processedTypes[i] = mt.name(); // resolve alias if necessary
0111         }
0112         if (processedTypes[i] == QLatin1StringView("application/octet-stream")) { // we'll deal with that later
0113             continue;
0114         }
0115         d->appendFormattersForType(processedTypes[i], r);
0116 
0117         const auto parentTypes = mt.parentMimeTypes();
0118         for (const auto &parentType : parentTypes) {
0119             if (std::find(processedTypes.begin(), processedTypes.end(), parentType) != processedTypes.end()) {
0120                 continue;
0121             }
0122             processedTypes.push_back(parentType);
0123         }
0124     }
0125 
0126     // make sure we always have a suitable fallback formatter
0127     if (mimeType.startsWith(QLatin1StringView("multipart/"))) {
0128         if (mimeType != QLatin1StringView("multipart/mixed")) {
0129             d->appendFormattersForType(QStringLiteral("multipart/mixed"), r);
0130         }
0131     } else {
0132         d->appendFormattersForType(QStringLiteral("application/octet-stream"), r);
0133     }
0134     assert(!r.empty());
0135     return r;
0136 }
0137 
0138 void BodyPartFormatterFactory::loadPlugins()
0139 {
0140     const QList<KPluginMetaData> plugins = KPluginMetaData::findPlugins(QStringLiteral("pim6/messageviewer/bodypartformatter"));
0141     for (const auto &md : plugins) {
0142         const auto formatterData = md.rawData().value(QLatin1StringView("formatter")).toArray();
0143         if (formatterData.isEmpty()) {
0144             continue;
0145         }
0146         QPluginLoader loader(md.fileName());
0147         auto plugin = qobject_cast<MimeTreeParser::Interface::BodyPartFormatterPlugin *>(loader.instance());
0148         if (!plugin) {
0149             continue;
0150         }
0151 
0152         const MimeTreeParser::Interface::BodyPartFormatter *bfp = nullptr;
0153         for (int i = 0; (bfp = plugin->bodyPartFormatter(i)) && i < formatterData.size(); ++i) {
0154             const auto metaData = formatterData.at(i).toObject();
0155             const auto mimetype = metaData.value(QLatin1StringView("mimetype")).toString();
0156             if (mimetype.isEmpty()) {
0157                 qCWarning(MIMETREEPARSER_LOG) << "BodyPartFormatterFactory: plugin" << md.fileName() << "returned empty mimetype specification for index" << i;
0158                 break;
0159             }
0160             // priority should always be higher than the built-in ones, otherwise what's the point?
0161             const auto priority = metaData.value(QLatin1StringView("priority")).toInt() + 100;
0162             qCDebug(MIMETREEPARSER_LOG) << "plugin for " << mimetype << priority;
0163             insert(mimetype, bfp, priority);
0164         }
0165     }
0166 }