File indexing completed on 2025-03-09 04:54:35

0001 /*
0002     This file is part of KMail, the KDE mail client.
0003     SPDX-FileCopyrightText: 2017 Sandro Knauß <sknauss@kde.org>
0004 
0005     SPDX-License-Identifier: GPL-2.0-or-later
0006 */
0007 
0008 #include "messagepartrendererfactory.h"
0009 #include "messagepartrendererbase.h"
0010 #include "messagepartrendererfactory_p.h"
0011 #include "messagepartrenderplugin.h"
0012 #include "messageviewer_debug.h"
0013 #include "viewer/urlhandlermanager.h"
0014 
0015 #include "plugins/attachmentmessagepartrenderer.h"
0016 #include "plugins/messagepartrenderer.h"
0017 #include "plugins/textmessagepartrenderer.h"
0018 
0019 #include <KPluginMetaData>
0020 #include <QJsonArray>
0021 #include <QMimeDatabase>
0022 #include <QPluginLoader>
0023 #include <algorithm>
0024 
0025 using namespace MessageViewer;
0026 
0027 MessagePartRendererFactoryPrivate::~MessagePartRendererFactoryPrivate()
0028 {
0029     QHashIterator<QByteArray, std::vector<RendererInfo>> i(m_renderers);
0030     while (i.hasNext()) {
0031         i.next();
0032         auto renderInfo = i.value();
0033         renderInfo.erase(renderInfo.begin(), renderInfo.end());
0034     }
0035 }
0036 
0037 void MessagePartRendererFactoryPrivate::setup()
0038 {
0039     if (m_renderers.isEmpty()) {
0040         initialize_builtin_renderers();
0041         loadPlugins();
0042     }
0043     Q_ASSERT(!m_renderers.isEmpty());
0044 }
0045 
0046 void MessagePartRendererFactoryPrivate::loadPlugins()
0047 {
0048     if (m_pluginSubdir.isEmpty()) {
0049         return;
0050     }
0051     const QList<KPluginMetaData> plugins = KPluginMetaData::findPlugins(m_pluginSubdir);
0052     for (const auto &md : plugins) {
0053         const auto pluginData = md.rawData().value(QLatin1StringView("renderer")).toArray();
0054         if (pluginData.isEmpty()) {
0055             qCWarning(MESSAGEVIEWER_LOG) << "Plugin" << md.fileName() << "has no meta data.";
0056             continue;
0057         }
0058         QPluginLoader loader(md.fileName());
0059         auto plugin = qobject_cast<MessagePartRenderPlugin *>(loader.instance());
0060         if (!plugin) {
0061             qCWarning(MESSAGEVIEWER_LOG) << md.fileName() << "is not a MessagePartRendererPlugin";
0062             continue;
0063         }
0064 
0065         MessagePartRendererBase *renderer = nullptr;
0066         for (int i = 0; (renderer = plugin->renderer(i)) && i < pluginData.size(); ++i) {
0067             const auto metaData = pluginData.at(i).toObject();
0068             const auto type = metaData.value(QLatin1StringView("type")).toString().toUtf8();
0069             if (type.isEmpty()) {
0070                 qCWarning(MESSAGEVIEWER_LOG) << md.fileName() << "returned empty type specification for index" << i;
0071                 break;
0072             }
0073             const auto mimetype = metaData.value(QLatin1StringView("mimetype")).toString().toLower();
0074             // priority should always be higher than the built-in ones, otherwise what's the point?
0075             const auto priority = metaData.value(QLatin1StringView("priority")).toInt() + 100;
0076             qCDebug(MESSAGEVIEWER_LOG) << "renderer plugin for " << type << mimetype << priority;
0077             insert(type, renderer, mimetype, priority);
0078         }
0079 
0080         const Interface::BodyPartURLHandler *handler = nullptr;
0081         for (int i = 0; (handler = plugin->urlHandler(i)); ++i) {
0082             const auto metaData = pluginData.at(i).toObject();
0083             const auto mimeType = metaData.value(QLatin1StringView("mimetype")).toString().toLower();
0084             URLHandlerManager::instance()->registerHandler(handler, mimeType);
0085         }
0086     }
0087 }
0088 
0089 void MessagePartRendererFactoryPrivate::initialize_builtin_renderers()
0090 {
0091     insert("MimeTreeParser::MessagePart", new MessagePartRenderer());
0092     insert("MimeTreeParser::TextMessagePart", new TextMessagePartRenderer());
0093     insert("MimeTreeParser::AttachmentMessagePart", new AttachmentMessagePartRenderer());
0094 }
0095 
0096 void MessagePartRendererFactoryPrivate::insert(const QByteArray &type, MessagePartRendererBase *renderer, const QString &mimeType, int priority)
0097 {
0098     if (type.isEmpty() || !renderer) {
0099         return;
0100     }
0101 
0102     QMimeDatabase db;
0103     const auto mt = db.mimeTypeForName(mimeType);
0104 
0105     RendererInfo info;
0106     info.renderer.reset(renderer);
0107     info.mimeType = mt.isValid() ? mt.name() : mimeType;
0108     info.priority = priority;
0109 
0110     auto &v = m_renderers[type];
0111     v.push_back(info);
0112 }
0113 
0114 MessagePartRendererFactory::MessagePartRendererFactory()
0115     : d(new MessagePartRendererFactoryPrivate)
0116 {
0117 }
0118 
0119 MessagePartRendererFactory::~MessagePartRendererFactory() = default;
0120 
0121 void MessagePartRendererFactory::setPluginPath(const QString &subdir)
0122 {
0123     d->m_pluginSubdir = subdir;
0124 }
0125 
0126 MessagePartRendererFactory *MessagePartRendererFactory::instance()
0127 {
0128     static MessagePartRendererFactory s_instance;
0129     return &s_instance;
0130 }
0131 
0132 QList<MessagePartRendererBase *> MessagePartRendererFactory::renderersForPart(const QMetaObject *mo, const MimeTreeParser::MessagePartPtr &mp) const
0133 {
0134     d->setup();
0135 
0136     const auto mtName = mp->content() ? QString::fromUtf8(mp->content()->contentType()->mimeType().toLower()) : QString();
0137     QMimeDatabase db;
0138     const auto mt = db.mimeTypeForName(mtName);
0139     auto ancestors = mt.allAncestors();
0140     if (mt.isValid() || !mtName.isEmpty()) {
0141         ancestors.prepend(mt.isValid() ? mt.name() : mtName);
0142     }
0143 
0144     auto candidates = d->m_renderers.value(mo->className());
0145 
0146     // remove candidates with a mimetype set that don't match the mimetype of the part
0147     candidates.erase(std::remove_if(candidates.begin(),
0148                                     candidates.end(),
0149                                     [ancestors](const RendererInfo &info) {
0150                                         if (info.mimeType.isEmpty()) {
0151                                             return false;
0152                                         }
0153                                         return !ancestors.contains(info.mimeType);
0154                                     }),
0155                      candidates.end());
0156 
0157     // sort most specific mimetpypes first
0158     std::stable_sort(candidates.begin(), candidates.end(), [ancestors](const RendererInfo &lhs, const RendererInfo &rhs) {
0159         if (lhs.mimeType == rhs.mimeType) {
0160             return lhs.priority > rhs.priority;
0161         }
0162         if (lhs.mimeType.isEmpty()) {
0163             return false;
0164         }
0165         if (rhs.mimeType.isEmpty()) {
0166             return true;
0167         }
0168         return ancestors.indexOf(lhs.mimeType) < ancestors.indexOf(rhs.mimeType);
0169     });
0170 
0171     QList<MessagePartRendererBase *> r;
0172     r.reserve(candidates.size());
0173     for (const auto &candidate : candidates) {
0174         r.push_back(candidate.renderer.data());
0175     }
0176     return r;
0177 }