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 }