File indexing completed on 2024-12-29 04:49:58
0001 /* 0002 SPDX-FileCopyrightText: 2017-2021 Volker Krause <vkrause@kde.org> 0003 0004 SPDX-License-Identifier: LGPL-2.0-or-later 0005 */ 0006 0007 #include "config-kitinerary.h" 0008 #include "extractorrepository.h" 0009 0010 #include "logging.h" 0011 #include "extractors/activitypubextractor.h" 0012 #include "extractors/genericboardingpassextractor.h" 0013 0014 #include <KItinerary/ExtractorDocumentNode> 0015 #include <KItinerary/ExtractorDocumentProcessor> 0016 #include <KItinerary/ExtractorFilter> 0017 #include <KItinerary/ScriptExtractor> 0018 0019 #include <QDirIterator> 0020 #include <QJsonArray> 0021 #include <QJsonDocument> 0022 #include <QJsonObject> 0023 #include <QMetaProperty> 0024 #include <QStandardPaths> 0025 0026 using namespace KItinerary; 0027 0028 static void initResources() // must be outside of a namespace 0029 { 0030 Q_INIT_RESOURCE(extractors); 0031 Q_INIT_RESOURCE(vdv_certs); 0032 Q_INIT_RESOURCE(rsp6_keys); 0033 } 0034 0035 namespace KItinerary { 0036 class ExtractorRepositoryPrivate { 0037 public: 0038 ExtractorRepositoryPrivate(); 0039 void loadAll(); 0040 void initBuiltInExtractors(); 0041 void loadScriptExtractors(); 0042 void addExtractor(std::unique_ptr<AbstractExtractor> &&e); 0043 0044 std::vector<std::unique_ptr<AbstractExtractor>> m_extractors; 0045 QStringList m_extraSearchPaths; 0046 }; 0047 } 0048 0049 ExtractorRepositoryPrivate::ExtractorRepositoryPrivate() 0050 { 0051 initResources(); 0052 loadAll(); 0053 } 0054 0055 void ExtractorRepositoryPrivate::loadAll() 0056 { 0057 initBuiltInExtractors(); 0058 loadScriptExtractors(); 0059 } 0060 0061 void ExtractorRepositoryPrivate::initBuiltInExtractors() 0062 { 0063 addExtractor(std::make_unique<ActivityPubExtractor>()); 0064 addExtractor(std::make_unique<GenericBoardingPassExtractor>()); 0065 } 0066 0067 ExtractorRepository::ExtractorRepository() 0068 { 0069 static ExtractorRepositoryPrivate repo; 0070 d = &repo; 0071 } 0072 0073 ExtractorRepository::~ExtractorRepository() = default; 0074 ExtractorRepository::ExtractorRepository(KItinerary::ExtractorRepository &&) noexcept = default; 0075 0076 void ExtractorRepository::reload() 0077 { 0078 d->m_extractors.clear(); 0079 d->loadAll(); 0080 } 0081 0082 const std::vector<std::unique_ptr<AbstractExtractor>>& ExtractorRepository::extractors() const 0083 { 0084 return d->m_extractors; 0085 } 0086 0087 void ExtractorRepository::extractorsForNode(const ExtractorDocumentNode &node, std::vector<const AbstractExtractor*> &extractors) const 0088 { 0089 if (node.isNull()) { 0090 return; 0091 } 0092 0093 for (const auto &extractor : d->m_extractors) { 0094 if (extractor->canHandle(node)) { 0095 // while we only would add each extractor at most once, some of them might already be in the list, so de-duplicate 0096 const auto it = std::lower_bound(extractors.begin(), extractors.end(), extractor.get(), [](auto lhs, auto rhs) { 0097 return lhs < rhs; 0098 }); 0099 if (it == extractors.end() || (*it) != extractor.get()) { 0100 extractors.insert(it, extractor.get()); 0101 } 0102 } 0103 } 0104 } 0105 0106 const AbstractExtractor* ExtractorRepository::extractorByName(QStringView name) const 0107 { 0108 auto it = std::lower_bound(d->m_extractors.begin(), d->m_extractors.end(), name, [](const auto &lhs, auto rhs) { 0109 return lhs->name() < rhs; 0110 }); 0111 if (it != d->m_extractors.end() && (*it)->name() == name) { 0112 return (*it).get(); 0113 } 0114 return {}; 0115 } 0116 0117 void ExtractorRepositoryPrivate::loadScriptExtractors() 0118 { 0119 auto searchDirs = m_extraSearchPaths; 0120 const auto qsp = QStandardPaths::standardLocations(QStandardPaths::GenericDataLocation); 0121 for (const auto &p : qsp) { 0122 searchDirs.push_back(p + QLatin1StringView("/kitinerary/extractors")); 0123 } 0124 searchDirs += QStringLiteral(":/org.kde.pim/kitinerary/extractors"); 0125 0126 for (const auto &dir : std::as_const(searchDirs)) { 0127 QDirIterator it(dir, QDir::Files); 0128 while (it.hasNext()) { 0129 const auto fileName = it.next(); 0130 if (!fileName.endsWith(QLatin1StringView(".json"))) { 0131 continue; 0132 } 0133 0134 QFile file(fileName); 0135 if (!file.open(QFile::ReadOnly)) { 0136 continue; 0137 } 0138 0139 QJsonParseError error; 0140 const auto doc = QJsonDocument::fromJson(file.readAll(), &error); 0141 if (doc.isNull()) { 0142 qCWarning(Log) << "Extractor loading error:" << fileName << error.errorString(); 0143 continue; 0144 } 0145 0146 QFileInfo fi(fileName); 0147 const auto name = fi.fileName().left(fi.fileName().size() - 5); 0148 0149 if (doc.isObject()) { 0150 const auto obj = doc.object(); 0151 auto ext = std::make_unique<ScriptExtractor>(); 0152 if (ext->load(obj, fi.canonicalFilePath())) { 0153 addExtractor(std::move(ext)); 0154 } else { 0155 qCWarning(Log) << "failed to load extractor:" << fi.canonicalFilePath(); 0156 } 0157 } else if (doc.isArray()) { 0158 const auto extractorArray = doc.array(); 0159 int i = 0; 0160 for (const auto &v : extractorArray) { 0161 auto ext = std::make_unique<ScriptExtractor>(); 0162 if (ext->load(v.toObject(), fi.canonicalFilePath(), extractorArray.size() == 1 ? -1 : i)) { 0163 addExtractor(std::move(ext)); 0164 } else { 0165 qCWarning(Log) << "failed to load extractor:" << fi.canonicalFilePath(); 0166 } 0167 ++i; 0168 } 0169 } else { 0170 qCWarning(Log) << "Invalid extractor meta-data:" << fileName; 0171 continue; 0172 } 0173 } 0174 } 0175 } 0176 0177 void ExtractorRepositoryPrivate::addExtractor(std::unique_ptr<AbstractExtractor> &&e) 0178 { 0179 auto it = std::lower_bound(m_extractors.begin(), m_extractors.end(), e, [](const auto &lhs, const auto &rhs) { 0180 return lhs->name() < rhs->name(); 0181 }); 0182 if (it == m_extractors.end() || (*it)->name() != e->name()) { 0183 m_extractors.insert(it, std::move(e)); 0184 } 0185 } 0186 0187 QStringList ExtractorRepository::additionalSearchPaths() const 0188 { 0189 return d->m_extraSearchPaths; 0190 } 0191 0192 void ExtractorRepository::setAdditionalSearchPaths(const QStringList& searchPaths) 0193 { 0194 d->m_extraSearchPaths = searchPaths; 0195 } 0196 0197 QJsonValue ExtractorRepository::extractorToJson(const ScriptExtractor *extractor) const 0198 { 0199 QJsonArray a; 0200 bool added = false; 0201 for (const auto &ext : d->m_extractors) { 0202 auto e = dynamic_cast<ScriptExtractor*>(ext.get()); 0203 if (!e || e->fileName() != extractor->fileName()) { 0204 continue; 0205 } 0206 if (extractor->name() == e->name()) { 0207 a.push_back(extractor->toJson()); 0208 added = true; 0209 } else { 0210 a.push_back(e->toJson()); 0211 } 0212 } 0213 if (!added) { 0214 a.push_back(extractor->toJson()); 0215 } 0216 0217 if (a.size() == 1) { 0218 return a.at(0); 0219 } 0220 return a; 0221 }