KPublicTransport

scriptedrestonboardbackend.cpp
1/*
2 SPDX-FileCopyrightText: 2022 Volker Krause <vkrause@kde.org>
3 SPDX-License-Identifier: LGPL-2.0-or-later
4*/
5
6#include "scriptedrestonboardbackend_p.h"
7#include "logging.h"
8#include "positiondata_p.h"
9
10#include "../lib/datatypes/journeyutil_p.h"
11
12#include <KPublicTransport/Journey>
13#include <KPublicTransport/Stopover>
14
15#include <QFile>
16#include <QJSEngine>
17#include <QJsonValue>
18#include <QJsonObject>
19#include <QNetworkRequest>
20#include <QScopeGuard>
21#include <QTimer>
22#include <QTimeZone>
23
24using namespace KPublicTransport;
25
26ScriptedRestOnboardBackend::ScriptedRestOnboardBackend(QObject *parent)
27 : RestOnboardBackend(parent)
28{
29}
30
31ScriptedRestOnboardBackend::~ScriptedRestOnboardBackend()
32{
33 if (m_watchdogTimer) {
34 m_watchdogTimer->deleteLater();
35 }
36 m_watchdogThread.quit();
37 m_watchdogThread.wait();
38}
39
40bool ScriptedRestOnboardBackend::supportsPosition() const
41{
42 return m_positionEndpoint.isValid();
43}
44
45bool ScriptedRestOnboardBackend::supportsJourney() const
46{
47 return m_journeyEndpoint.isValid();
48}
49
50QNetworkRequest ScriptedRestOnboardBackend::createPositionRequest() const
51{
52 return QNetworkRequest(m_positionEndpoint);
53}
54
55QNetworkRequest ScriptedRestOnboardBackend::createJourneyRequest() const
56{
57 return QNetworkRequest(m_journeyEndpoint);
58}
59
60static double strictToNumber(const QJSValue &val)
61{
62 if (val.isNumber()) {
63 return val.toNumber();
64 }
65 if (val.isString()) {
66 bool result = false;
67 const auto n = val.toString().toDouble(&result);
68 return result ? n : NAN;
69 }
70 return NAN;
71}
72
73PositionData ScriptedRestOnboardBackend::parsePositionData(const QJsonValue &response) const
74{
75 setupEngine();
76
77 // watchdog setup
78 QMetaObject::invokeMethod(m_watchdogTimer, qOverload<>(&QTimer::start));
79 const auto watchdogStop = qScopeGuard([this]() {
80 QMetaObject::invokeMethod(m_watchdogTimer, qOverload<>(&QTimer::stop));
81 });
82 m_engine->setInterrupted(false);
83
84 auto func = m_engine->globalObject().property(m_positionFunction);
85 if (!func.isCallable()) {
86 qCWarning(Log) << "Script entry point not found!" << m_positionFunction;
87 return {};
88 }
89
90 const auto arg = m_engine->toScriptValue(response);
91 const auto result = func.call(QJSValueList{arg});
92 if (result.isError()) {
93 printScriptError(result);
94 return {};
95 }
96
97 // convert JS result
98 PositionData pos;
99 pos.timestamp = QDateTime::fromString(result.property(QStringLiteral("timestamp")).toString(), Qt::ISODate);
100 pos.latitude = strictToNumber(result.property(QStringLiteral("latitude")));
101 pos.longitude = strictToNumber(result.property(QStringLiteral("longitude")));
102 pos.speed = strictToNumber(result.property(QStringLiteral("speed")));
103 pos.heading = strictToNumber(result.property(QStringLiteral("heading")));
104 pos.altitude = strictToNumber(result.property(QStringLiteral("altitude")));
105 return pos;
106}
107
108Journey ScriptedRestOnboardBackend::parseJourneyData(const QJsonValue &response) const
109{
110 setupEngine();
111
112 // watchdog setup
113 QMetaObject::invokeMethod(m_watchdogTimer, qOverload<>(&QTimer::start));
114 const auto watchdogStop = qScopeGuard([this]() {
115 QMetaObject::invokeMethod(m_watchdogTimer, qOverload<>(&QTimer::stop));
116 });
117 m_engine->setInterrupted(false);
118
119 auto func = m_engine->globalObject().property(m_journeyFunction);
120 if (!func.isCallable()) {
121 qCWarning(Log) << "Script entry point not found!" << m_journeyFunction;
122 return {};
123 }
124
125 const auto arg = m_engine->toScriptValue(response);
126 const auto result = func.call(QJSValueList{arg});
127 if (result.isError()) {
128 printScriptError(result);
129 return {};
130 }
131
132 // convert JS result
133 auto jny = Journey::fromJson(QJsonValue::fromVariant(result.toVariant()).toObject());
134 auto sections = jny.takeSections();
135
136 for (auto &section : sections) {
137 auto stops = section.takeIntermediateStops();
138 // many backends will have the entire trip as intermediate stops, redistribute
139 // that for our format
140 if (section.from().isEmpty() && !stops.empty()) {
141 const auto s = stops.front();
142 section.setDeparture(s);
143 stops.erase(stops.begin());
144 }
145
146 if (section.to().isEmpty() && !stops.empty()) {
147 const auto s = stops.back();
148 section.setArrival(s);
149 stops.pop_back();
150 }
151
152 section.setIntermediateStops(std::move(stops));
153 }
154
155 jny.setSections(std::move(sections));
156 JourneyUtil::propagateTimeZones(jny);
157 return jny;
158}
159
160void ScriptedRestOnboardBackend::setupEngine() const
161{
162 if (m_engine) {
163 return;
164 }
165 m_engine.reset(new QJSEngine);
166 m_engine->installExtensions(QJSEngine::ConsoleExtension);
167
168 m_watchdogThread.start();
169 m_watchdogTimer = new QTimer;
170 m_watchdogTimer->setInterval(std::chrono::milliseconds(500));
171 m_watchdogTimer->setSingleShot(true);
172 m_watchdogTimer->moveToThread(&m_watchdogThread);
173 QObject::connect(m_watchdogTimer, &QTimer::timeout, this, [this]() { m_engine->setInterrupted(true); }, Qt::DirectConnection);
174
175 // load script
176 QFile f(QLatin1String(":/org.kde.kpublictransport.onboard/") + m_scriptName);
177 if (!f.open(QFile::ReadOnly)) {
178 qCWarning(Log) << "Failed to open extractor script" << f.fileName() << f.errorString();
179 return;
180 }
181
182 const auto result = m_engine->evaluate(QString::fromUtf8(f.readAll()), f.fileName());
183 if (result.isError()) {
184 printScriptError(result);
185 return;
186 }
187}
188
189void ScriptedRestOnboardBackend::printScriptError(const QJSValue &error) const
190{
191 qCWarning(Log) << "JS ERROR: " << m_scriptName << error.property(QLatin1String("lineNumber")).toInt() << ": " << error.toString();
192}
A journey plan.
Definition journey.h:272
static Journey fromJson(const QJsonObject &obj)
Deserialize an object from JSON.
Definition journey.cpp:877
char * toString(const EngineQuery &query)
void error(QWidget *parent, const QString &text, const QString &title, const KGuiItem &buttonOk, Options options=Notify)
Query operations and data types for accessing realtime public transport information from online servi...
QDateTime fromString(QStringView string, QStringView format, QCalendar cal)
QJsonValue fromVariant(const QVariant &variant)
QJsonObject toObject() const const
bool isNumber() const const
bool isString() const const
double toNumber() const const
QString toString() const const
bool invokeMethod(QObject *context, Functor &&function, FunctorReturnType *ret)
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
QString fromUtf8(QByteArrayView str)
double toDouble(bool *ok) const const
DirectConnection
void setInterval(int msec)
void start()
void stop()
void timeout()
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Mon Nov 18 2024 12:07:52 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.