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