File indexing completed on 2024-05-12 04:42:47

0001 /*
0002     SPDX-FileCopyrightText: 2020 Volker Krause <vkrause@kde.org>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 #include "gbfsservice.h"
0008 #include "datatypes/json_p.h"
0009 
0010 #include <QCryptographicHash>
0011 #include <QDebug>
0012 #include <QDirIterator>
0013 #include <QFile>
0014 #include <QJsonDocument>
0015 #include <QJsonObject>
0016 #include <QStandardPaths>
0017 
0018 using namespace KPublicTransport;
0019 
0020 void GBFSService::generateSystemId()
0021 {
0022     if (discoveryUrl.isEmpty()) {
0023         return;
0024     }
0025     systemId = QString::fromUtf8(QCryptographicHash::hash(discoveryUrl.toString().toUtf8(), QCryptographicHash::Sha1).toBase64(QByteArray::Base64UrlEncoding));
0026 }
0027 
0028 QJsonObject GBFSService::toJson(const GBFSService &service)
0029 {
0030     return Json::toJson(service);
0031 }
0032 
0033 GBFSService GBFSService::fromJson(const QJsonObject &obj)
0034 {
0035     return Json::fromJson<GBFSService>(obj);
0036 }
0037 
0038 
0039 std::vector<GBFSService> GBFSServiceRepository::m_services;
0040 
0041 static QString basePath()
0042 {
0043     return QStandardPaths::writableLocation(QStandardPaths::GenericCacheLocation) + QLatin1String("/org.kde.kpublictransport/gbfs/services/");
0044 }
0045 
0046 const std::vector<GBFSService>& GBFSServiceRepository::services()
0047 {
0048     if (m_services.empty()) {
0049         load();
0050     }
0051     return m_services;
0052 }
0053 
0054 void GBFSServiceRepository::store(const GBFSService &service)
0055 {
0056     if (service.systemId.isEmpty() || service.systemId.contains(QLatin1String("..")) || service.systemId.contains(QLatin1Char('/'))) {
0057         qWarning() << "invalid service id:" << service.systemId << service.discoveryUrl;
0058         return;
0059     }
0060 
0061     const auto base = basePath();
0062     QDir().mkpath(base);
0063     const QString fileName = base + service.systemId + QLatin1String(".json");
0064     QFile f(fileName);
0065     if (!f.open(QFile::WriteOnly)) {
0066         qWarning() << f.errorString() << f.fileName();
0067         return;
0068     }
0069 
0070     f.write(QJsonDocument(GBFSService::toJson(service)).toJson(QJsonDocument::Compact));
0071 }
0072 
0073 void GBFSServiceRepository::load()
0074 {
0075     QDirIterator it(basePath(), QDir::Files);
0076     while (it.hasNext()) {
0077         auto n = it.next();
0078         if (!n.endsWith(QLatin1String(".json"))) {
0079             continue;
0080         }
0081 
0082         QFile f(n);
0083         if (!f.open(QFile::ReadOnly)) {
0084             qWarning() << f.errorString() << f.fileName();
0085             continue;
0086         }
0087 
0088         auto service = GBFSService::fromJson(QJsonDocument::fromJson(f.readAll()).object());
0089         if (service.systemId.isEmpty()) {
0090             continue;
0091         }
0092         m_services.push_back(std::move(service));
0093     }
0094     std::sort(m_services.begin(), m_services.end(), [](const auto &lhs, const auto &rhs) { return lhs.systemId < rhs.systemId; });
0095 
0096     // load missing ones from the initial built-in list
0097     QFile f(QStringLiteral(":/org.kde.kpublictransport/gbfs/gbfs-feeds.json"));
0098     if (!f.open(QFile::ReadOnly)) {
0099         qWarning() << f.errorString();
0100         return;
0101     }
0102 
0103     const auto array = QJsonDocument::fromJson(f.readAll()).array();
0104     m_services.reserve(array.size());
0105     const auto endIdx = m_services.size();
0106     for (const auto &v : array) {
0107         const auto s = GBFSService::fromJson(v.toObject());
0108         const auto it = std::lower_bound(m_services.begin(), m_services.begin() + endIdx, s, [](const auto &lhs, const auto &rhs) { return lhs.systemId < rhs.systemId; });
0109         if (it != (m_services.begin() + endIdx) && (*it).systemId == s.systemId) {
0110             continue;
0111         }
0112         m_services.push_back(std::move(s));
0113     }
0114 }