File indexing completed on 2024-12-08 07:19:10

0001 /*
0002     SPDX-FileCopyrightText: 2020 Volker Krause <vkrause@kde.org>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 #include "assetrepository_p.h"
0008 #include "logging.h"
0009 
0010 #include <KPublicTransport/Attribution>
0011 
0012 #include <QDebug>
0013 #include <QDir>
0014 #include <QFileInfo>
0015 #include <QJsonArray>
0016 #include <QJsonDocument>
0017 #include <QNetworkAccessManager>
0018 #include <QNetworkReply>
0019 #include <QStandardPaths>
0020 #include <QUrl>
0021 
0022 using namespace KPublicTransport;
0023 
0024 AssetRepository* AssetRepository::s_instance = nullptr;
0025 
0026 AssetRepository::AssetRepository(QObject *parent)
0027     : QObject(parent)
0028 {
0029     if (!s_instance) {
0030         s_instance = this;
0031     }
0032 }
0033 
0034 AssetRepository::~AssetRepository()
0035 {
0036     if (s_instance == this) {
0037         s_instance = nullptr;
0038     }
0039 }
0040 
0041 AssetRepository* AssetRepository::instance()
0042 {
0043     return s_instance;
0044 }
0045 
0046 void AssetRepository::setNetworkAccessManagerProvider(std::function<QNetworkAccessManager*()> namProvider)
0047 {
0048     m_namProvider = namProvider;
0049 }
0050 
0051 static QString cachePath()
0052 {
0053     return QStandardPaths::writableLocation(QStandardPaths::GenericCacheLocation) + QLatin1String("/org.kde.kpublictransport/assets/");
0054 }
0055 
0056 QUrl AssetRepository::localFile(const QUrl& url)
0057 {
0058     if (!url.isValid() || url.fileName().isEmpty()) {
0059         return {};
0060     }
0061 
0062     QFileInfo fi(cachePath() + url.fileName());
0063     if (fi.exists() && fi.size() > 0) {
0064         return QUrl::fromLocalFile(fi.absoluteFilePath());
0065     }
0066     return {};
0067 }
0068 
0069 bool AssetRepository::download(const QUrl &url)
0070 {
0071     if (!url.isValid() || url.scheme() != QLatin1String("https") || url.fileName().isEmpty() || !m_namProvider || !m_namProvider()) {
0072         return false;
0073     }
0074 
0075     if (QFileInfo::exists(cachePath() + url.fileName())) { // already downloaded, or persistent error
0076         return false;
0077     }
0078 
0079     if (std::find(m_queue.begin(), m_queue.end(), url) != m_queue.end()) {
0080         return false;
0081     }
0082 
0083     m_queue.push_back(url);
0084     if (m_queue.size() == 1) {
0085         downloadNext();
0086     }
0087     return true;
0088 }
0089 
0090 bool AssetRepository::isQueueEmpty()
0091 {
0092     return m_queue.empty();
0093 }
0094 
0095 void AssetRepository::downloadNext()
0096 {
0097     if (m_queue.empty()) {
0098         Q_EMIT downloadFinished();
0099         return;
0100     }
0101 
0102     QNetworkRequest req(m_queue.front());
0103     auto reply = m_namProvider()->get(req);
0104     connect(reply, &QNetworkReply::finished, this, [this, reply]() {
0105         reply->deleteLater();
0106 
0107         switch (reply->error()) {
0108             case QNetworkReply::NoError:
0109             {
0110                 QDir().mkpath(cachePath());
0111                 QFile f(cachePath() + reply->request().url().fileName());
0112                 if (!f.open(QFile::WriteOnly)) {
0113                     qWarning() << "Failed to open file for storing asset" << f.errorString() << f.fileName();
0114                 } else {
0115                     f.write(reply->readAll());
0116                 }
0117                 break;
0118             }
0119             // persistent errors, empty file prevents forther
0120             case QNetworkReply::ContentNotFoundError:
0121             case QNetworkReply::ContentGoneError:
0122             case QNetworkReply::UnknownContentError:
0123             case QNetworkReply::TooManyRedirectsError:
0124             {
0125                 qWarning() << reply->errorString();
0126                 QDir().mkpath(cachePath());
0127                 QFile f(cachePath() + reply->request().url().fileName());
0128                 f.open(QFile::WriteOnly);
0129                 break;
0130             }
0131             // transient errors
0132             default:
0133                 qWarning() << reply->errorString();
0134                 break;
0135         }
0136 
0137         m_queue.pop_front();
0138         downloadNext();
0139     });
0140 }
0141 
0142 const std::vector<Attribution>& AssetRepository::attributions() const
0143 {
0144     if (m_attributions.empty()) {
0145         QFile f(QStringLiteral(":/org.kde.kpublictransport/assets/asset-attributions.json"));
0146         if (!f.open(QFile::ReadOnly)) {
0147             qCWarning(Log) << f.fileName() << f.errorString();
0148             return m_attributions;
0149         }
0150 
0151         m_attributions = Attribution::fromJson(QJsonDocument::fromJson(f.readAll()).array());
0152     }
0153 
0154     return m_attributions;
0155 }