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 "gbfsstore.h"
0008 #include "logging.h"
0009 
0010 #include <QDateTime>
0011 #include <QDebug>
0012 #include <QDir>
0013 #include <QDirIterator>
0014 #include <QFile>
0015 #include <QFileInfo>
0016 #include <QJsonDocument>
0017 #include <QJsonObject>
0018 #include <QStandardPaths>
0019 #include <QUrl>
0020 
0021 #include <cassert>
0022 #include <chrono>
0023 
0024 using namespace KPublicTransport;
0025 
0026 struct {
0027     const std::chrono::seconds ttl;
0028 } static constexpr const file_ttl_map[] = {
0029     { std::chrono::hours(24 * 5) },
0030     { std::chrono::hours(24 * 5) },
0031     { std::chrono::hours(24 * 5) },
0032     { std::chrono::minutes(5) },
0033     { std::chrono::minutes(5) },
0034     { std::chrono::hours(24 * 5) },
0035     { std::chrono::hours(24 * 5) },
0036     { std::chrono::hours(24 * 5) },
0037     { std::chrono::hours(24 * 5) },
0038     { std::chrono::hours(24 * 5) },
0039     { std::chrono::hours(24 * 5) },
0040     { std::chrono::hours(24 * 5) },
0041     { std::chrono::hours(24 * 5) },
0042 };
0043 
0044 static_assert((sizeof(file_ttl_map) / sizeof(file_ttl_map[0])) == GBFS::Unknown, "");
0045 
0046 GBFSStore::GBFSStore() = default;
0047 
0048 GBFSStore::GBFSStore(const QString &systemId)
0049     : m_systemId(systemId)
0050 {
0051 }
0052 
0053 GBFSStore::~GBFSStore() = default;
0054 
0055 bool GBFSStore::isValid() const
0056 {
0057     return !m_systemId.isEmpty();
0058 }
0059 
0060 bool GBFSStore::hasData(GBFS::FileType type) const
0061 {
0062     const auto name = fileName(type);
0063     return QFile::exists(name);
0064 }
0065 
0066 bool GBFSStore::hasCurrentData(GBFS::FileType type) const
0067 {
0068     const auto name = fileName(type);
0069     QFileInfo fi(name);
0070     return fi.exists() && fi.fileTime(QFile::FileModificationTime) >= QDateTime::currentDateTime();
0071 }
0072 
0073 void GBFSStore::storeData(GBFS::FileType type, const QJsonDocument &doc)
0074 {
0075     const auto name = fileName(type);
0076     QFile f(name);
0077     if (!f.open(QFile::WriteOnly)) {
0078         qWarning() << f.errorString() << f.fileName();
0079         return;
0080     }
0081     f.write(doc.toJson(QJsonDocument::Compact));
0082     f.close();
0083 
0084     // mtime changes need to be done without content changes to take effect
0085     const auto ttl = std::max(std::chrono::seconds(doc.object().value(QLatin1String("ttl")).toInt()), file_ttl_map[type].ttl);
0086     f.open(QFile::WriteOnly | QFile::Append);
0087     f.setFileTime(QDateTime::currentDateTimeUtc().addSecs(ttl.count()), QFile::FileModificationTime);
0088     f.close();
0089 }
0090 
0091 QJsonDocument GBFSStore::loadData(GBFS::FileType type) const
0092 {
0093     const auto name = fileName(type);
0094     QFile f(name);
0095     if (!f.open(QFile::ReadOnly)) {
0096         qWarning() << f.errorString() << f.fileName();
0097         return {};
0098     }
0099     return QJsonDocument::fromJson(f.readAll());
0100 }
0101 
0102 static void expireRecursive(const QString &path)
0103 {
0104     const auto now = QDateTime::currentDateTime();
0105     QDirIterator it(path, QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot | QDir::NoSymLinks);
0106     while (it.hasNext()) {
0107         it.next();
0108 
0109         if (it.fileInfo().isDir()) {
0110             expireRecursive(it.filePath());
0111             if (QDir(it.filePath()).isEmpty()) {
0112                 qCDebug(Log) << "removing empty cache directory" << it.fileName();
0113                 QDir(path).rmdir(it.filePath());
0114             }
0115         } else if (it.fileInfo().lastModified() < now) {
0116             qCDebug(Log) << "removing expired cache entry" << it.filePath();
0117             QDir(path).remove(it.filePath());
0118         }
0119     }
0120 }
0121 
0122 void GBFSStore::expire()
0123 {
0124     expireRecursive(QStandardPaths::writableLocation(QStandardPaths::GenericCacheLocation) + QLatin1String("/org.kde.kpublictransport/gbfs/feeds/"));
0125 }
0126 
0127 QString GBFSStore::fileName(GBFS::FileType type) const
0128 {
0129     assert(!m_systemId.isEmpty());
0130     QString path = QStandardPaths::writableLocation(QStandardPaths::GenericCacheLocation) + QLatin1String("/org.kde.kpublictransport/gbfs/feeds/") + m_systemId;
0131     QDir().mkpath(path);
0132     path += QLatin1Char('/') + QString::fromUtf8(GBFS::keyNameForType(type)) + QLatin1String(".json");
0133     return path;
0134 }