KPublicTransport

manager.cpp
1 /*
2  SPDX-FileCopyrightText: 2018 Volker Krause <[email protected]>
3 
4  SPDX-License-Identifier: LGPL-2.0-or-later
5 */
6 
7 #include "manager.h"
8 #include "assetrepository_p.h"
9 #include "journeyreply.h"
10 #include "journeyrequest.h"
11 #include "requestcontext_p.h"
12 #include "locationreply.h"
13 #include "locationrequest.h"
14 #include "logging.h"
15 #include "stopoverreply.h"
16 #include "stopoverrequest.h"
17 #include "vehiclelayoutrequest.h"
18 #include "vehiclelayoutreply.h"
19 #include "datatypes/attributionutil_p.h"
20 #include "datatypes/backend.h"
21 #include "datatypes/backend_p.h"
22 #include "datatypes/disruption.h"
23 #include "datatypes/json_p.h"
24 #include "datatypes/platform.h"
25 #include "datatypes/vehicle.h"
26 #include "geo/geojson_p.h"
27 
28 #include <KPublicTransport/Journey>
29 #include <KPublicTransport/Location>
30 #include <KPublicTransport/Stopover>
31 
32 #include "backends/accessibilitycloudbackend.h"
33 #include "backends/cache.h"
34 #include "backends/deutschebahnbackend.h"
35 #include "backends/efabackend.h"
36 #include "backends/hafasmgatebackend.h"
37 #include "backends/hafasquerybackend.h"
38 #include "backends/ivvassbackend.h"
39 #include "backends/navitiabackend.h"
40 #include "backends/oebbbackend.h"
41 #include "backends/openjourneyplannerbackend.h"
42 #include "backends/opentripplannergraphqlbackend.h"
43 #include "backends/opentripplannerrestbackend.h"
44 #include "gbfs/gbfsbackend.h"
45 
46 #include <QDirIterator>
47 #include <QJsonArray>
48 #include <QJsonDocument>
49 #include <QJsonObject>
50 #include <QMetaProperty>
51 #include <QNetworkAccessManager>
52 #include <QStandardPaths>
53 #include <QTimer>
54 #include <QTimeZone>
55 
56 #include <functional>
57 
58 using namespace KPublicTransport;
59 
60 static inline void initResources() {
61  Q_INIT_RESOURCE(networks);
62  Q_INIT_RESOURCE(network_certs);
63  Q_INIT_RESOURCE(otp);
64 }
65 
66 namespace KPublicTransport {
67 class ManagerPrivate {
68 public:
69  QNetworkAccessManager* nam();
70  void loadNetworks();
71  std::unique_ptr<AbstractBackend> loadNetwork(const QJsonObject &obj);
72  template <typename Backend, typename Backend2, typename ...Backends>
73  static std::unique_ptr<AbstractBackend> loadNetwork(const QJsonObject &backendType, const QJsonObject &obj);
74  template <typename Backend> std::unique_ptr<AbstractBackend>
75  static loadNetwork(const QJsonObject &backendType, const QJsonObject &obj);
76  template <typename T>
77  static std::unique_ptr<AbstractBackend> loadNetwork(const QJsonObject &obj);
78 
79  template <typename RequestT> bool shouldSkipBackend(const Backend &backend, const RequestT &req) const;
80 
81  void resolveLocation(LocationRequest &&locReq, const AbstractBackend *backend, const std::function<void(const Location &loc)> &callback);
82  bool queryJourney(const AbstractBackend *backend, const JourneyRequest &req, JourneyReply *reply);
83  bool queryStopover(const AbstractBackend *backend, const StopoverRequest &req, StopoverReply *reply);
84 
85  template <typename RepT, typename ReqT> RepT* makeReply(const ReqT &request);
86 
87  void readCachedAttributions();
88 
89  Manager *q = nullptr;
90  QNetworkAccessManager *m_nam = nullptr;
91  std::vector<Backend> m_backends;
92  std::vector<Attribution> m_attributions;
93 
94  // we store both explicitly to have a third state, backends with the enabled state being the "default" (whatever that might eventually be)
95  QStringList m_enabledBackends;
96  QStringList m_disabledBackends;
97 
98  bool m_allowInsecure = false;
99  bool m_hasReadCachedAttributions = false;
100  bool m_backendsEnabledByDefault = true;
101 
102 private:
103  bool shouldSkipBackend(const Backend &backend) const;
104 };
105 }
106 
107 QNetworkAccessManager* ManagerPrivate::nam()
108 {
109  if (!m_nam) {
110  m_nam = new QNetworkAccessManager(q);
111  m_nam->setRedirectPolicy(QNetworkRequest::NoLessSafeRedirectPolicy);
112  m_nam->setStrictTransportSecurityEnabled(true);
113  m_nam->enableStrictTransportSecurityStore(true, QStandardPaths::writableLocation(QStandardPaths::GenericCacheLocation) + QLatin1String("/org.kde.kpublictransport/hsts/"));
114  }
115  return m_nam;
116 }
117 
118 
119 void ManagerPrivate::loadNetworks()
120 {
121  QDirIterator it(QStringLiteral(":/org.kde.kpublictransport/networks"));
122  while (it.hasNext()) {
123  QFile f(it.next());
124  if (!f.open(QFile::ReadOnly)) {
125  qCWarning(Log) << "Failed to open public transport network configuration:" << f.errorString();
126  continue;
127  }
128 
130  const auto doc = QJsonDocument::fromJson(f.readAll(), &error);
131  if (error.error != QJsonParseError::NoError) {
132  qCWarning(Log) << "Failed to parse public transport network configuration:" << error.errorString() << it.fileName();
133  continue;
134  }
135 
136  auto net = loadNetwork(doc.object());
137  if (net) {
138  net->setBackendId(it.fileInfo().baseName());
139  net->init();
140  if (!net->attribution().isEmpty()) {
141  m_attributions.push_back(net->attribution());
142  }
143 
144  auto b = BackendPrivate::fromJson(doc.object());
145  BackendPrivate::setImpl(b, std::move(net));
146  m_backends.push_back(std::move(b));
147  } else {
148  qCWarning(Log) << "Failed to load public transport network configuration config:" << it.fileName();
149  }
150  }
151 
152  std::stable_sort(m_backends.begin(), m_backends.end(), [](const auto &lhs, const auto &rhs) {
153  return lhs.identifier() < rhs.identifier();
154  });
155 
156  AttributionUtil::sort(m_attributions);
157  qCDebug(Log) << m_backends.size() << "public transport network configurations loaded";
158 }
159 
160 std::unique_ptr<AbstractBackend> ManagerPrivate::loadNetwork(const QJsonObject &obj)
161 {
162  const auto type = obj.value(QLatin1String("type")).toObject();
163  // backends need to be topologically sorted according to their preference/priority here
164  return loadNetwork<
165  NavitiaBackend,
166  OpenTripPlannerGraphQLBackend,
167  OpenTripPlannerRestBackend,
168  DeutscheBahnBackend,
169  OebbBackend,
170  HafasMgateBackend,
171  HafasQueryBackend,
172  EfaBackend,
173  IvvAssBackend,
174  OpenJourneyPlannerBackend,
175  GBFSBackend,
176  AccessibilityCloudBackend
177  >(type, obj);
178 }
179 
180 template <typename Backend, typename Backend2, typename ...Backends>
181 std::unique_ptr<AbstractBackend> ManagerPrivate::loadNetwork(const QJsonObject &backendType, const QJsonObject &obj)
182 {
183  if (backendType.value(QLatin1String(Backend::type())).toBool()) {
184  return loadNetwork<Backend>(obj);
185  }
186  return loadNetwork<Backend2, Backends...>(backendType, obj);
187 }
188 
189 template <typename Backend>
190 std::unique_ptr<AbstractBackend> ManagerPrivate::loadNetwork(const QJsonObject &backendType, const QJsonObject &obj)
191 {
192  if (backendType.value(QLatin1String(Backend::type())).toBool()) {
193  return ManagerPrivate::loadNetwork<Backend>(obj);
194  }
195  qCWarning(Log) << "Unknown backend type:" << backendType;
196  return {};
197 }
198 
199 static void applyBackendOptions(AbstractBackend *backend, const QMetaObject *mo, const QJsonObject &obj)
200 {
201  const auto opts = obj.value(QLatin1String("options")).toObject();
202  for (auto it = opts.begin(); it != opts.end(); ++it) {
203  const auto idx = mo->indexOfProperty(it.key().toUtf8().constData());
204  if (idx < 0) {
205  qCWarning(Log) << "Unknown backend setting:" << it.key();
206  continue;
207  }
208  const auto mp = mo->property(idx);
209  if (it.value().isObject()) {
210  mp.writeOnGadget(backend, it.value().toObject());
211  } else if (it.value().isArray()) {
212  const auto a = it.value().toArray();
213  if (mp.userType() == QVariant::StringList) {
214  QStringList l;
215  l.reserve(a.size());
216  std::transform(a.begin(), a.end(), std::back_inserter(l), [](const auto &v) { return v.toString(); });
217  mp.writeOnGadget(backend, l);
218  } else {
219  mp.writeOnGadget(backend, it.value().toArray());
220  }
221  } else {
222  mp.writeOnGadget(backend, it.value().toVariant());
223  }
224  }
225 
226  const auto attrObj = obj.value(QLatin1String("attribution")).toObject();
227  const auto attr = Attribution::fromJson(attrObj);
228  backend->setAttribution(attr);
229 
230  const auto tzId = obj.value(QLatin1String("timezone")).toString();
231  if (!tzId.isEmpty()) {
232  QTimeZone tz(tzId.toUtf8());
233  if (tz.isValid()) {
234  backend->setTimeZone(tz);
235  } else {
236  qCWarning(Log) << "Invalid timezone:" << tzId;
237  }
238  }
239 
240  const auto langArray = obj.value(QLatin1String("supportedLanguages")).toArray();
241  QStringList langs;
242  langs.reserve(langArray.size());
243  std::transform(langArray.begin(), langArray.end(), std::back_inserter(langs), std::mem_fn(qOverload<>(&QJsonValue::toString)));
244  backend->setSupportedLanguages(langs);
245 }
246 
247 template<typename T> std::unique_ptr<AbstractBackend> ManagerPrivate::loadNetwork(const QJsonObject &obj)
248 {
249  std::unique_ptr<AbstractBackend> backend(new T);
250  applyBackendOptions(backend.get(), &T::staticMetaObject, obj);
251  return backend;
252 }
253 
254 bool ManagerPrivate::shouldSkipBackend(const Backend &backend) const
255 {
256  if (!backend.isSecure() && !m_allowInsecure) {
257  qCDebug(Log) << "Skipping insecure backend:" << backend.identifier();
258  return true;
259  }
260  return !q->isBackendEnabled(backend.identifier());
261 }
262 
263 template <typename RequestT>
264 bool ManagerPrivate::shouldSkipBackend(const Backend &backend, const RequestT &req) const
265 {
266  if (!req.backendIds().isEmpty() && !req.backendIds().contains(backend.identifier())) {
267  //qCDebug(Log) << "Skipping backend" << backend.identifier() << "due to explicit request";
268  return true;
269  }
270  return shouldSkipBackend(backend);
271 }
272 
273 // IMPORTANT callback must not be called directly, but only via queued invocation,
274 // our callers rely on that to not mess up sync/async response handling
275 void ManagerPrivate::resolveLocation(LocationRequest &&locReq, const AbstractBackend *backend, const std::function<void(const Location&)> &callback)
276 {
277  // check if this location query is cached already
278  const auto cacheEntry = Cache::lookupLocation(backend->backendId(), locReq.cacheKey());
279  switch (cacheEntry.type) {
280  case CacheHitType::Negative:
281  QTimer::singleShot(0, q, [callback]() { callback({}); });
282  return;
283  case CacheHitType::Positive:
284  if (!cacheEntry.data.empty()) {
285  const auto loc = cacheEntry.data[0];
286  QTimer::singleShot(0, q, [callback, loc]() { callback(loc); });
287  return;
288  }
289  break;
290  case CacheHitType::Miss:
291  break;
292  }
293 
294  // actually do the location query
295  locReq.setMaximumResults(1);
296  auto locReply = new LocationReply(locReq, q);
297  if (backend->queryLocation(locReq, locReply, nam())) {
298  locReply->setPendingOps(1);
299  } else {
300  locReply->setPendingOps(0);
301  }
302  QObject::connect(locReply, &Reply::finished, q, [callback, locReply]() {
303  locReply->deleteLater();
304  if (locReply->result().empty()) {
305  callback({});
306  } else {
307  callback(locReply->result()[0]);
308  }
309  });
310 }
311 
312 static Location::Types locationTypesForJourneyRequest(const JourneyRequest &req)
313 {
315  if (req.modes() & JourneySection::PublicTransport) {
316  t |= Location::Stop;
317  }
318  if (req.modes() & JourneySection::RentedVehicle) {
320  }
321  return t;
322 }
323 
324 bool ManagerPrivate::queryJourney(const AbstractBackend* backend, const JourneyRequest &req, JourneyReply *reply)
325 {
326  auto cache = Cache::lookupJourney(backend->backendId(), req.cacheKey());
327  switch (cache.type) {
328  case CacheHitType::Negative:
329  qCDebug(Log) << "Negative cache hit for backend" << backend->backendId();
330  return false;
331  case CacheHitType::Positive:
332  qCDebug(Log) << "Positive cache hit for backend" << backend->backendId();
333  reply->addAttributions(std::move(cache.attributions));
334  reply->addResult(backend, std::move(cache.data));
335  return false;
336  case CacheHitType::Miss:
337  qCDebug(Log) << "Cache miss for backend" << backend->backendId();
338  break;
339  }
340 
341  // resolve locations if needed
342  if (backend->needsLocationQuery(req.from(), AbstractBackend::QueryType::Journey)) {
343  LocationRequest fromReq(req.from());
344  fromReq.setTypes(locationTypesForJourneyRequest(req));
345  resolveLocation(std::move(fromReq), backend, [reply, backend, req, this](const Location &loc) {
346  auto jnyRequest = req;
347  const auto fromLoc = Location::merge(jnyRequest.from(), loc);
348  jnyRequest.setFrom(fromLoc);
349 
350  if (backend->needsLocationQuery(jnyRequest.to(), AbstractBackend::QueryType::Journey)) {
351  LocationRequest toReq(jnyRequest.to());
352  toReq.setTypes(locationTypesForJourneyRequest(req));
353  resolveLocation(std::move(toReq), backend, [jnyRequest, reply, backend, this](const Location &loc) {
354  auto jnyReq = jnyRequest;
355  const auto toLoc = Location::merge(jnyRequest.to(), loc);
356  jnyReq.setTo(toLoc);
357  if (!backend->queryJourney(jnyReq, reply, nam())) {
358  reply->addError(Reply::NotFoundError, {});
359  }
360  });
361 
362  return;
363  }
364 
365  if (!backend->queryJourney(jnyRequest, reply, nam())) {
366  reply->addError(Reply::NotFoundError, {});
367  }
368  });
369 
370  return true;
371  }
372 
373  if (backend->needsLocationQuery(req.to(), AbstractBackend::QueryType::Journey)) {
374  LocationRequest toReq(req.to());
375  toReq.setTypes(locationTypesForJourneyRequest(req));
376  resolveLocation(std::move(toReq), backend, [req, toReq, reply, backend, this](const Location &loc) {
377  const auto toLoc = Location::merge(req.to(), loc);
378  auto jnyRequest = req;
379  jnyRequest.setTo(toLoc);
380  if (!backend->queryJourney(jnyRequest, reply, nam())) {
381  reply->addError(Reply::NotFoundError, {});
382  }
383  });
384  return true;
385  }
386 
387  return backend->queryJourney(req, reply, nam());
388 }
389 
390 bool ManagerPrivate::queryStopover(const AbstractBackend *backend, const StopoverRequest &req, StopoverReply *reply)
391 {
392  auto cache = Cache::lookupDeparture(backend->backendId(), req.cacheKey());
393  switch (cache.type) {
394  case CacheHitType::Negative:
395  qCDebug(Log) << "Negative cache hit for backend" << backend->backendId();
396  return false;
397  case CacheHitType::Positive:
398  qCDebug(Log) << "Positive cache hit for backend" << backend->backendId();
399  reply->addAttributions(std::move(cache.attributions));
400  reply->addResult(backend, std::move(cache.data));
401  return false;
402  case CacheHitType::Miss:
403  qCDebug(Log) << "Cache miss for backend" << backend->backendId();
404  break;
405  }
406 
407  // check if we first need to resolve the location first
408  if (backend->needsLocationQuery(req.stop(), AbstractBackend::QueryType::Departure)) {
409  qCDebug(Log) << "Backend needs location query first:" << backend->backendId();
410  LocationRequest locReq(req.stop());
411  locReq.setTypes(Location::Stop); // Stopover can never refer to other location types
412  resolveLocation(std::move(locReq), backend, [reply, req, backend, this](const Location &loc) {
413  const auto depLoc = Location::merge(req.stop(), loc);
414  auto depRequest = req;
415  depRequest.setStop(depLoc);
416  if (!backend->queryStopover(depRequest, reply, nam())) {
417  reply->addError(Reply::NotFoundError, {});
418  }
419  });
420  return true;
421  }
422 
423  return backend->queryStopover(req, reply, nam());
424 }
425 
426 void ManagerPrivate::readCachedAttributions()
427 {
428  if (m_hasReadCachedAttributions) {
429  return;
430  }
431 
432  Cache::allCachedAttributions(m_attributions);
433  m_hasReadCachedAttributions = true;
434 }
435 
436 template<typename RepT, typename ReqT>
437 RepT* ManagerPrivate::makeReply(const ReqT &request)
438 {
439  auto reply = new RepT(request, q);
440  QObject::connect(reply, &Reply::finished, q, [this, reply]() {
441  AttributionUtil::merge(m_attributions, reply->attributions());
442  });
443  return reply;
444 }
445 
446 
447 
448 Manager::Manager(QObject *parent)
449  : QObject(parent)
450  , d(new ManagerPrivate)
451 {
452  initResources();
453  qRegisterMetaType<Disruption::Effect>();
454  d->q = this;
455  d->loadNetworks();
456 
457  if (!AssetRepository::instance()) {
458  auto assetRepo = new AssetRepository(this);
459  assetRepo->setNetworkAccessManager(d->nam());
460  }
461 
462  Cache::expire();
463 }
464 
465 Manager::~Manager() = default;
466 
468 {
469  if (d->m_nam == nam) {
470  return;
471  }
472 
473  if (d->m_nam->parent() == this) {
474  delete d->m_nam;
475  }
476 
477  d->m_nam = nam;
478 
479  if (AssetRepository::instance()) {
480  AssetRepository::instance()->setNetworkAccessManager(nam);
481  }
482 }
483 
485 {
486  return d->m_allowInsecure;
487 }
488 
490 {
491  if (d->m_allowInsecure == insecure) {
492  return;
493  }
494  d->m_allowInsecure = insecure;
495  Q_EMIT configurationChanged();
496 }
497 
499 {
500  auto reply = d->makeReply<JourneyReply>(req);
501  int pendingOps = 0;
502 
503  // validate input
504  req.validate();
505  if (!req.isValid()) {
506  reply->addError(Reply::InvalidRequest, {});
507  reply->setPendingOps(pendingOps);
508  return reply;
509  }
510 
511  // first time/direct query
512  if (req.contexts().empty()) {
513  QSet<QString> triedBackends;
514  bool foundNonGlobalCoverage = false;
515  for (const auto coverageType : { CoverageArea::Realtime, CoverageArea::Regular, CoverageArea::Any }) {
516  for (const auto &backend: d->m_backends) {
517  if (triedBackends.contains(backend.identifier()) || d->shouldSkipBackend(backend, req)) {
518  continue;
519  }
520  const auto coverage = backend.coverageArea(coverageType);
521  if (coverage.isEmpty() || (!coverage.coversLocation(req.from()) && !coverage.coversLocation(req.to()))) {
522  continue;
523  }
524  triedBackends.insert(backend.identifier());
525  foundNonGlobalCoverage |= !coverage.isGlobal();
526 
527  if (d->queryJourney(BackendPrivate::impl(backend), req, reply)) {
528  ++pendingOps;
529  }
530  }
531 
532  if (pendingOps && foundNonGlobalCoverage) {
533  break;
534  }
535  }
536 
537  // subsequent earlier/later query
538  } else {
539  for (const auto &context : req.contexts()) {
540  // backend supports this itself
541  if ((context.type == RequestContext::Next && context.backend->hasCapability(AbstractBackend::CanQueryNextJourney))
542  ||(context.type == RequestContext::Previous && context.backend->hasCapability(AbstractBackend::CanQueryPreviousJourney)))
543  {
544  if (d->queryJourney(context.backend, req, reply)) {
545  ++pendingOps;
546  continue;
547  }
548  }
549 
550  // backend doesn't support this, let's try to emulate
551  if (context.type == RequestContext::Next && req.dateTimeMode() == JourneyRequest::Departure) {
552  auto r = req;
553  r.setDepartureTime(context.dateTime);
554  if (d->queryJourney(context.backend, r, reply)) {
555  ++pendingOps;
556  continue;
557  }
558  } else if (context.type == RequestContext::Previous && req.dateTimeMode() == JourneyRequest::Departure) {
559  auto r = req;
560  r.setArrivalTime(context.dateTime);
561  if (d->queryJourney(context.backend, r, reply)) {
562  ++pendingOps;
563  continue;
564  }
565  }
566  }
567  }
568 
569  if (req.downloadAssets()) {
570  reply->addAttributions(AssetRepository::instance()->attributions());
571  }
572  reply->setPendingOps(pendingOps);
573  return reply;
574 }
575 
577 {
578  auto reply = d->makeReply<StopoverReply>(req);
579  int pendingOps = 0;
580 
581  // validate input
582  if (!req.isValid()) {
583  reply->addError(Reply::InvalidRequest, {});
584  reply->setPendingOps(pendingOps);
585  return reply;
586  }
587 
588  // first time/direct query
589  if (req.contexts().empty()) {
590  QSet<QString> triedBackends;
591  bool foundNonGlobalCoverage = false;
592  for (const auto coverageType : { CoverageArea::Realtime, CoverageArea::Regular, CoverageArea::Any }) {
593  for (const auto &backend: d->m_backends) {
594  if (triedBackends.contains(backend.identifier()) || d->shouldSkipBackend(backend, req)) {
595  continue;
596  }
597  if (req.mode() == StopoverRequest::QueryArrival && (BackendPrivate::impl(backend)->capabilities() & AbstractBackend::CanQueryArrivals) == 0) {
598  qCDebug(Log) << "Skipping backend due to not supporting arrival queries:" << backend.identifier();
599  continue;
600  }
601  const auto coverage = backend.coverageArea(coverageType);
602  if (coverage.isEmpty() || !coverage.coversLocation(req.stop())) {
603  continue;
604  }
605  triedBackends.insert(backend.identifier());
606  foundNonGlobalCoverage |= !coverage.isGlobal();
607 
608  if (d->queryStopover(BackendPrivate::impl(backend), req, reply)) {
609  ++pendingOps;
610  }
611  }
612 
613  if (pendingOps && foundNonGlobalCoverage) {
614  break;
615  }
616  }
617 
618  // subsequent earlier/later query
619  } else {
620  for (const auto &context : req.contexts()) {
621  // backend supports this itself
622  if ((context.type == RequestContext::Next && context.backend->hasCapability(AbstractBackend::CanQueryNextDeparture))
623  ||(context.type == RequestContext::Previous && context.backend->hasCapability(AbstractBackend::CanQueryPreviousDeparture)))
624  {
625  if (d->queryStopover(context.backend, req, reply)) {
626  ++pendingOps;
627  continue;
628  }
629  }
630 
631  // backend doesn't support this, let's try to emulate
632  if (context.type == RequestContext::Next && req.mode() == StopoverRequest::QueryDeparture) {
633  auto r = req;
634  r.setDateTime(context.dateTime);
635  if (d->queryStopover(context.backend, r, reply)) {
636  ++pendingOps;
637  continue;
638  }
639  }
640  }
641  }
642 
643  if (req.downloadAssets()) {
644  reply->addAttributions(AssetRepository::instance()->attributions());
645  }
646  reply->setPendingOps(pendingOps);
647  return reply;
648 }
649 
650 StopoverReply* Manager::queryDeparture(const StopoverRequest &req) const
651 {
652  return queryStopover(req);
653 }
654 
656 {
657  auto reply = d->makeReply<LocationReply>(req);
658  int pendingOps = 0;
659 
660  // validate input
661  if (!req.isValid()) {
662  reply->addError(Reply::InvalidRequest, {});
663  reply->setPendingOps(pendingOps);
664  return reply;
665  }
666 
667  QSet<QString> triedBackends;
668  bool foundNonGlobalCoverage = false;
669  for (const auto coverageType : { CoverageArea::Realtime, CoverageArea::Regular, CoverageArea::Any }) {
670  for (const auto &backend : d->m_backends) {
671  if (triedBackends.contains(backend.identifier()) || d->shouldSkipBackend(backend, req)) {
672  continue;
673  }
674  const auto coverage = backend.coverageArea(coverageType);
675  if (coverage.isEmpty() || !coverage.coversLocation(req.location())) {
676  continue;
677  }
678  triedBackends.insert(backend.identifier());
679  foundNonGlobalCoverage |= !coverage.isGlobal();
680 
681  reply->addAttribution(BackendPrivate::impl(backend)->attribution());
682 
683  auto cache = Cache::lookupLocation(backend.identifier(), req.cacheKey());
684  switch (cache.type) {
685  case CacheHitType::Negative:
686  qCDebug(Log) << "Negative cache hit for backend" << backend.identifier();
687  break;
688  case CacheHitType::Positive:
689  qCDebug(Log) << "Positive cache hit for backend" << backend.identifier();
690  reply->addAttributions(std::move(cache.attributions));
691  reply->addResult(std::move(cache.data));
692  break;
693  case CacheHitType::Miss:
694  qCDebug(Log) << "Cache miss for backend" << backend.identifier();
695  if (BackendPrivate::impl(backend)->queryLocation(req, reply, d->nam())) {
696  ++pendingOps;
697  }
698  break;
699  }
700  }
701  if (pendingOps && foundNonGlobalCoverage) {
702  break;
703  }
704  }
705  reply->setPendingOps(pendingOps);
706  return reply;
707 }
708 
710 {
711  auto reply = d->makeReply<VehicleLayoutReply>(req);
712  int pendingOps = 0;
713 
714  // validate input
715  if (!req.isValid()) {
716  reply->addError(Reply::InvalidRequest, {});
717  reply->setPendingOps(pendingOps);
718  return reply;
719  }
720 
721  for (const auto &backend : d->m_backends) {
722  if (d->shouldSkipBackend(backend, req)) {
723  continue;
724  }
725  const auto coverage = backend.coverageArea(CoverageArea::Realtime);
726  if (coverage.isEmpty() || !coverage.coversLocation(req.stopover().stopPoint())) {
727  continue;
728  }
729  reply->addAttribution(BackendPrivate::impl(backend)->attribution());
730 
731  auto cache = Cache::lookupVehicleLayout(backend.identifier(), req.cacheKey());
732  switch (cache.type) {
733  case CacheHitType::Negative:
734  qCDebug(Log) << "Negative cache hit for backend" << backend.identifier();
735  break;
736  case CacheHitType::Positive:
737  qCDebug(Log) << "Positive cache hit for backend" << backend.identifier();
738  if (cache.data.size() == 1) {
739  reply->addAttributions(std::move(cache.attributions));
740  reply->addResult(cache.data[0]);
741  break;
742  }
743  Q_FALLTHROUGH();
744  case CacheHitType::Miss:
745  qCDebug(Log) << "Cache miss for backend" << backend.identifier();
746  if (BackendPrivate::impl(backend)->queryVehicleLayout(req, reply, d->nam())) {
747  ++pendingOps;
748  }
749  break;
750  }
751 
752  }
753  reply->setPendingOps(pendingOps);
754  return reply;
755 }
756 
757 const std::vector<Attribution>& Manager::attributions() const
758 {
759  d->readCachedAttributions();
760  return d->m_attributions;
761 }
762 
763 QVariantList Manager::attributionsVariant() const
764 {
765  d->readCachedAttributions();
766  QVariantList l;
767  l.reserve(d->m_attributions.size());
768  std::transform(d->m_attributions.begin(), d->m_attributions.end(), std::back_inserter(l), [](const auto &attr) { return QVariant::fromValue(attr); });
769  return l;
770 }
771 
772 const std::vector<Backend>& Manager::backends() const
773 {
774  return d->m_backends;
775 }
776 
777 bool Manager::isBackendEnabled(const QString &backendId) const
778 {
779  if (std::binary_search(d->m_disabledBackends.cbegin(), d->m_disabledBackends.cend(), backendId)) {
780  return false;
781  }
782  if (std::binary_search(d->m_enabledBackends.cbegin(), d->m_enabledBackends.cend(), backendId)) {
783  return true;
784  }
785 
786  return d->m_backendsEnabledByDefault;
787 }
788 
789 static void sortedInsert(QStringList &l, const QString &value)
790 {
791  const auto it = std::lower_bound(l.begin(), l.end(), value);
792  if (it == l.end() || (*it) != value) {
793  l.insert(it, value);
794  }
795 }
796 
797 static void sortedRemove(QStringList &l, const QString &value)
798 {
799  const auto it = std::lower_bound(l.begin(), l.end(), value);
800  if (it != l.end() && (*it) == value) {
801  l.erase(it);
802  }
803 }
804 
805 void Manager::setBackendEnabled(const QString &backendId, bool enabled)
806 {
807  if (enabled) {
808  sortedInsert(d->m_enabledBackends, backendId);
809  sortedRemove(d->m_disabledBackends, backendId);
810  } else {
811  sortedRemove(d->m_enabledBackends, backendId);
812  sortedInsert(d->m_disabledBackends, backendId);
813  }
814  Q_EMIT configurationChanged();
815 }
816 
818 {
819  return d->m_enabledBackends;
820 }
821 
823 {
824  QSignalBlocker blocker(this); // no change signals during settings restore
825  for (const auto &backendId : backendIds) {
826  setBackendEnabled(backendId, true);
827  }
828 }
829 
831 {
832  return d->m_disabledBackends;
833 }
834 
836 {
837  QSignalBlocker blocker(this); // no change signals during settings restore
838  for (const auto &backendId : backendIds) {
839  setBackendEnabled(backendId, false);
840  }
841 }
842 
844 {
845  return d->m_backendsEnabledByDefault;
846 }
847 
849 {
850  d->m_backendsEnabledByDefault = byDefault;
851 
852  Q_EMIT configurationChanged();
853 }
QJsonDocument fromJson(const QByteArray &json, QJsonParseError *error)
Information about a backend service queried for location/departure/journey data.
Definition: backend.h:21
Departure or arrival query reply.
Definition: stopoverreply.h:22
a public transport stop (train station, bus stop, etc)
Definition: location.h:36
KPublicTransport::Stopover stopover
The stopover vehicle and platform layout information are requested for.
Query operations and data types for accessing realtime public transport information from online servi...
Definition: attribution.cpp:16
bool isValid() const
Returns true if this is a valid request, that is it has enough parameters set to perform a query...
bool allowInsecureBackends() const
Returns whether access to insecure backends is allowed.
void setNetworkAccessManager(QNetworkAccessManager *nam)
Set the network access manager to use for network operations.
Definition: manager.cpp:467
const std::vector< Attribution > & attributions() const
Returns the attributions for the provided data.
Definition: reply.cpp:84
QString writableLocation(QStandardPaths::StandardLocation type)
QString cacheKey() const
Unique string representation used for caching results.
bool downloadAssets
Download graphic assets such as line logos for the data requested here.
dateTime() represents the desired departure time.
QString errorString() const const
void reserve(int alloc)
a location that isn&#39;t of any specific type
Definition: location.h:35
QStringList backendIds() const
Identifiers of the backends that should be queried.
bool isValid() const
Returns true if this is a valid request, that is, it has enough parameters set to perform a query...
KPublicTransport::Location stopPoint
The stop point of this departure.
Definition: stopover.h:64
QStringList disabledBackends() const
Returns the identifiers of explicitly disabled backends.
a pick-up/drop-off point for dock-based rental bike/scooter systems
Definition: location.h:37
free floating or dock-based rental bike service, electric scooters, car sharing services, ie. any vehicle you drive yourself but that isn&#39;t your own
Definition: journey.h:45
bool isSecure
Supports secrure network access.
Definition: backend.h:35
QList::iterator erase(QList::iterator pos)
void finished()
Emitted whenever the corresponding search has been completed.
void setDisabledBackends(const QStringList &backendIds)
Sets the explicitly disabled backends.
Definition: manager.cpp:835
QStringList enabledBackends() const
Returns the identifiers of explicitly enabled backends.
QSet::iterator insert(const T &value)
VehicleLayoutReply * queryVehicleLayout(const VehicleLayoutRequest &req) const
Query vehicle and platform layout information.
Definition: manager.cpp:709
QString cacheKey() const
Unique string representation used for caching results.
static Attribution fromJson(const QJsonObject &obj)
Deserialize an Attribution object from JSON.
Definition: attribution.cpp:48
KPublicTransport::Location from
The starting point of the journey search.
void setArrivalTime(const QDateTime &dt)
Sets the desired arrival time.
QString identifier
Internal identifier of this backend.
Definition: backend.h:27
QString toString() const const
LocationReply * queryLocation(const LocationRequest &req) const
Query location information based on coordinates or (parts of) the name.
Definition: manager.cpp:655
bool isValid() const
Returns true if this is a valid request, ie.
QJsonObject toObject() const const
QJsonArray toArray() const const
Journey query response.
Definition: journeyreply.h:21
void error(QWidget *parent, const QString &text, const QString &caption=QString(), Options options=Notify)
static Location merge(const Location &lhs, const Location &rhs)
Merge two departure instances.
Definition: location.cpp:368
bool toBool(bool defaultValue) const const
The requested journey/departure/place could not be found.
Definition: reply.h:34
bool isValid() const
Returns true if this is a valid request, that is it has enough parameters set to perform a query...
JourneyReply * queryJourney(const JourneyRequest &req) const
Query a journey.
Definition: manager.cpp:498
bool isBackendEnabled(const QString &backendId) const
Returns whether the use of the backend with a given identifier is enabled.
Definition: manager.cpp:777
QString cacheKey() const
Unique string representation used for caching results.
Describes an arrival or departure search.
Location query reply.
Definition: locationreply.h:22
void push_back(QChar ch)
QList::iterator end()
Describes a query for vehicle layout information.
StopoverReply * queryStopover(const StopoverRequest &req) const
Query arrivals or departures from a specific station.
Definition: manager.cpp:576
KPublicTransport::Location stop
The location at which to search for departures/arrivals.
QVariant fromValue(const T &value)
bool contains(const T &value) const const
int indexOfProperty(const char *name) const const
bool backendsEnabledByDefault() const
Returns wheter backends are enabled by default.
void setBackendsEnabledByDefault(bool byDefault)
Set wheter backends are enabled by default.
Definition: manager.cpp:848
Incomplete or otherwise invalid request.
Definition: reply.h:35
void setDepartureTime(const QDateTime &dt)
Set the desired departure time.
Reply to a vehicle layout query.
KPublicTransport::JourneySection::Modes modes
Modes of transportation that should be considered for this query.
void insert(int i, const T &value)
void setBackendEnabled(const QString &backendId, bool enabled)
Sets whether the backend with the given identifier should be used.
Definition: manager.cpp:805
QString cacheKey() const
Unique string representation used for caching results.
void setAllowInsecureBackends(bool insecure)
Allow usage of insecure backends, that is services not using transport encryption.
Definition: manager.cpp:489
Mode mode
Controls whether to search for arrivals or departures.
bool writeOnGadget(void *gadget, const QVariant &value) const const
Describes a location search.
QJsonValue value(const QString &key) const const
bool downloadAssets
Enable downloading of graphic assets such as line logos for the data requested here.
KPublicTransport::Location location
Location object containing the search parameters.
Describes a journey search.
void setEnabledBackends(const QStringList &backendIds)
Sets the explicitly enabled backends.
Definition: manager.cpp:822
QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
QString errorString() const const
KPublicTransport::Location to
The journey destination.
DateTimeMode dateTimeMode
Controls whether to search for journeys starting or ending at the given time.
QList::iterator begin()
const std::vector< Backend > & backends() const
Returns information about all available backends.
Definition: manager.cpp:772
Entry point for starting public transport queries.
Definition: manager.h:41
QMetaProperty property(int index) const const
const std::vector< Attribution > & attributions() const
Returns all static attribution information, as well as all dynamic ones found in the cache or accumul...
This file is part of the KDE documentation.
Documentation copyright © 1996-2021 The KDE developers.
Generated on Sun Oct 24 2021 23:05:21 by doxygen 1.8.11 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.