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 }