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 }