1 /*
2    SPDX-FileCopyrightText: 2017-2021 Volker Krause <vkrause@kde.org>
3 
4    SPDX-License-Identifier: LGPL-2.0-or-later
5 */
6 
7 #include "config-kitinerary.h"
8 #include "extractorrepository.h"
9 
10 #include "logging.h"
11 #include "extractors/genericboardingpassextractor.h"
12 
13 #include <KItinerary/ExtractorDocumentNode>
14 #include <KItinerary/ExtractorDocumentProcessor>
15 #include <KItinerary/ExtractorFilter>
16 #include <KItinerary/ScriptExtractor>
17 
18 #include <QDirIterator>
19 #include <QJsonArray>
20 #include <QJsonDocument>
21 #include <QJsonObject>
22 #include <QMetaProperty>
23 #include <QStandardPaths>
24 
25 using namespace KItinerary;
26 
initResources()27 static void initResources() // must be outside of a namespace
28 {
29     Q_INIT_RESOURCE(extractors);
30     Q_INIT_RESOURCE(vdv_certs);
31 }
32 
33 namespace KItinerary {
34 class ExtractorRepositoryPrivate {
35 public:
36     ExtractorRepositoryPrivate();
37     void loadAll();
38     void initBuiltInExtractors();
39     void loadScriptExtractors();
40     void addExtractor(std::unique_ptr<AbstractExtractor> &&e);
41 
42     std::vector<std::unique_ptr<AbstractExtractor>> m_extractors;
43     QStringList m_extraSearchPaths;
44 };
45 }
46 
ExtractorRepositoryPrivate()47 ExtractorRepositoryPrivate::ExtractorRepositoryPrivate()
48 {
49     initResources();
50     loadAll();
51 }
52 
loadAll()53 void ExtractorRepositoryPrivate::loadAll()
54 {
55     initBuiltInExtractors();
56     loadScriptExtractors();
57 }
58 
initBuiltInExtractors()59 void ExtractorRepositoryPrivate::initBuiltInExtractors()
60 {
61     addExtractor(std::make_unique<GenericBoardingPassExtractor>());
62 }
63 
ExtractorRepository()64 ExtractorRepository::ExtractorRepository()
65 {
66     static ExtractorRepositoryPrivate repo;
67     d = &repo;
68 }
69 
70 ExtractorRepository::~ExtractorRepository() = default;
71 ExtractorRepository::ExtractorRepository(KItinerary::ExtractorRepository &&) noexcept = default;
72 
reload()73 void ExtractorRepository::reload()
74 {
75     d->m_extractors.clear();
76     d->loadAll();
77 }
78 
extractors() const79 const std::vector<std::unique_ptr<AbstractExtractor>>& ExtractorRepository::extractors() const
80 {
81     return d->m_extractors;
82 }
83 
extractorsForNode(const ExtractorDocumentNode & node,std::vector<const AbstractExtractor * > & extractors) const84 void ExtractorRepository::extractorsForNode(const ExtractorDocumentNode &node, std::vector<const AbstractExtractor*> &extractors) const
85 {
86     if (node.isNull()) {
87         return;
88     }
89 
90     for (const auto &extractor : d->m_extractors) {
91         if (extractor->canHandle(node)) {
92             // while we only would add each extractor at most once, some of them might already be in the list, so de-duplicate
93             const auto it = std::lower_bound(extractors.begin(), extractors.end(), extractor.get(), [](auto lhs, auto rhs) {
94                 return lhs < rhs;
95             });
96             if (it == extractors.end() || (*it) != extractor.get()) {
97                 extractors.insert(it, extractor.get());
98             }
99         }
100     }
101 }
102 
extractorByName(QStringView name) const103 const AbstractExtractor* ExtractorRepository::extractorByName(QStringView name) const
104 {
105     auto it = std::lower_bound(d->m_extractors.begin(), d->m_extractors.end(), name, [](const auto &lhs, auto rhs) {
106         return lhs->name() < rhs;
107     });
108     if (it != d->m_extractors.end() && (*it)->name() == name) {
109         return (*it).get();
110     }
111     return {};
112 }
113 
loadScriptExtractors()114 void ExtractorRepositoryPrivate::loadScriptExtractors()
115 {
116     auto searchDirs = m_extraSearchPaths;
117     const auto qsp = QStandardPaths::standardLocations(QStandardPaths::GenericDataLocation);
118     for (const auto &p : qsp) {
119         searchDirs.push_back(p + QLatin1String("/kitinerary/extractors"));
120     }
121     searchDirs += QStringLiteral(":/org.kde.pim/kitinerary/extractors");
122 
123     for (const auto &dir : std::as_const(searchDirs)) {
124         QDirIterator it(dir, QDir::Files);
125         while (it.hasNext()) {
126             const auto fileName = it.next();
127             if (!fileName.endsWith(QLatin1String(".json"))) {
128                 continue;
129             }
130 
131             QFile file(fileName);
132             if (!file.open(QFile::ReadOnly)) {
133                 continue;
134             }
135 
136             QJsonParseError error;
137             const auto doc = QJsonDocument::fromJson(file.readAll(), &error);
138             if (doc.isNull()) {
139                 qCWarning(Log) << "Extractor loading error:" << fileName << error.errorString();
140                 continue;
141             }
142 
143             QFileInfo fi(fileName);
144             const auto name = fi.fileName().left(fi.fileName().size() - 5);
145 
146             if (doc.isObject()) {
147                 const auto obj = doc.object();
148                 auto ext = std::make_unique<ScriptExtractor>();
149                 if (ext->load(obj, fi.canonicalFilePath())) {
150                     addExtractor(std::move(ext));
151                 } else {
152                     qCWarning(Log) << "failed to load extractor:" << fi.canonicalFilePath();
153                 }
154             } else if (doc.isArray()) {
155                 const auto extractorArray = doc.array();
156                 int i = 0;
157                 for (const auto &v : extractorArray) {
158                     auto ext = std::make_unique<ScriptExtractor>();
159                     if (ext->load(v.toObject(), fi.canonicalFilePath(), extractorArray.size() == 1 ? -1 : i)) {
160                         addExtractor(std::move(ext));
161                     } else {
162                         qCWarning(Log) << "failed to load extractor:" << fi.canonicalFilePath();
163                     }
164                     ++i;
165                 }
166             } else {
167                 qCWarning(Log) << "Invalid extractor meta-data:" << fileName;
168                 continue;
169             }
170         }
171     }
172 }
173 
addExtractor(std::unique_ptr<AbstractExtractor> && e)174 void ExtractorRepositoryPrivate::addExtractor(std::unique_ptr<AbstractExtractor> &&e)
175 {
176     auto it = std::lower_bound(m_extractors.begin(), m_extractors.end(), e, [](const auto &lhs, const auto &rhs) {
177         return lhs->name() < rhs->name();
178     });
179     if (it == m_extractors.end() || (*it)->name() != e->name()) {
180         m_extractors.insert(it, std::move(e));
181     }
182 }
183 
additionalSearchPaths() const184 QStringList ExtractorRepository::additionalSearchPaths() const
185 {
186     return d->m_extraSearchPaths;
187 }
188 
setAdditionalSearchPaths(const QStringList & searchPaths)189 void ExtractorRepository::setAdditionalSearchPaths(const QStringList& searchPaths)
190 {
191     d->m_extraSearchPaths = searchPaths;
192 }
193 
extractorToJson(const ScriptExtractor * extractor) const194 QJsonValue ExtractorRepository::extractorToJson(const ScriptExtractor *extractor) const
195 {
196     QJsonArray a;
197     bool added = false;
198     for (const auto &ext : d->m_extractors) {
199         auto e = dynamic_cast<ScriptExtractor*>(ext.get());
200         if (!e || e->fileName() != extractor->fileName()) {
201             continue;
202         }
203         if (extractor->name() == e->name()) {
204             a.push_back(extractor->toJson());
205             added = true;
206         } else {
207             a.push_back(e->toJson());
208         }
209     }
210     if (!added) {
211         a.push_back(extractor->toJson());
212     }
213 
214     if (a.size() == 1) {
215         return a.at(0);
216     }
217     return a;
218 }
219