File indexing completed on 2024-04-28 04:42:42

0001 /*
0002  * SPDX-FileCopyrightText: 2020-2021 Han Young <hanyoung@protonmail.com>
0003  * SPDX-FileCopyrightText: 2020 Devin Lin <espidev@gmail.com>
0004  * SPDX-FileCopyrightText: 2022 Volker Krause <vkrause@kde.org>
0005  * SPDX-License-Identifier: LGPL-2.0-or-later
0006  */
0007 
0008 #include "locationqueryreply.h"
0009 #include "kweathercore_p.h"
0010 #include "locationquery.h"
0011 #include "locationqueryresult.h"
0012 #include "reply_p.h"
0013 
0014 #include <QCoreApplication>
0015 #include <QGeoPositionInfo>
0016 #include <QGeoPositionInfoSource>
0017 #include <QJsonArray>
0018 #include <QJsonDocument>
0019 #include <QJsonObject>
0020 #include <QNetworkAccessManager>
0021 #include <QNetworkReply>
0022 #include <QUrlQuery>
0023 
0024 #if QT_CONFIG(permissions)
0025 #include <QPermissions>
0026 #endif
0027 
0028 using namespace KWeatherCore;
0029 
0030 class KWeatherCore::LocationQueryReplyPrivate : public ReplyPrivate
0031 {
0032 public:
0033     void requestPosition(LocationQueryReply *q, QGeoPositionInfoSource *source, QNetworkAccessManager *nam);
0034     std::vector<LocationQueryResult> m_result;
0035 };
0036 
0037 static std::optional<QString> findSubdivision(const QJsonObject &json)
0038 {
0039     const auto adminCodeIter = json.constFind(QLatin1String("adminCodes1"));
0040     if (adminCodeIter == json.constEnd()) {
0041         return std::nullopt;
0042     } else {
0043         return (*adminCodeIter).toObject().value(QLatin1String("ISO3166_2")).toString();
0044     }
0045 }
0046 
0047 LocationQueryReply::LocationQueryReply(const QString &name, int number, QNetworkAccessManager *nam, QObject *parent)
0048     : Reply(new LocationQueryReplyPrivate, parent)
0049 {
0050     QUrl url(QStringLiteral("http://api.geonames.org/searchJSON"));
0051     QUrlQuery urlQuery;
0052 
0053     urlQuery.addQueryItem(QStringLiteral("q"), name);
0054     urlQuery.addQueryItem(QStringLiteral("maxRows"), QString::number(number));
0055     urlQuery.addQueryItem(QStringLiteral("username"), QStringLiteral("kweatherdev"));
0056     url.setQuery(urlQuery);
0057 
0058     auto reply = nam->get(QNetworkRequest(url));
0059     QObject::connect(reply, &QNetworkReply::finished, this, [reply, this]() {
0060         Q_D(LocationQueryReply);
0061         reply->deleteLater();
0062         if (reply->error() != QNetworkReply::NoError) {
0063             d->setError(Reply::NetworkError, reply->errorString());
0064             Q_EMIT finished();
0065             return;
0066         }
0067 
0068         const QJsonDocument document = QJsonDocument::fromJson(reply->readAll());
0069         const QJsonObject root = document.object();
0070 
0071         auto counts = root[QLatin1String("totalResultsCount")].toInt();
0072         // if no result
0073         if (!counts) {
0074             d->setError(Reply::NotFound);
0075             Q_EMIT finished();
0076             return;
0077         }
0078 
0079         // if our api calls reached daily limit
0080         if (root[QLatin1String("status")].toObject()[QLatin1String("value")].toInt() == 18) {
0081             d->setError(Reply::RateLimitExceeded);
0082             qWarning("API calls reached daily limit");
0083             Q_EMIT finished();
0084             return;
0085         }
0086 
0087         const auto geonames = root.value(QLatin1String("geonames")).toArray();
0088         // add query results
0089         for (const auto &resRef : std::as_const(geonames)) {
0090             const auto res = resRef.toObject();
0091             const auto result = LocationQueryResult(res.value(QLatin1String("lat")).toString().toFloat(),
0092                                                     res.value(QLatin1String("lng")).toString().toFloat(),
0093                                                     res.value(QLatin1String("toponymName")).toString(),
0094                                                     res.value(QLatin1String("name")).toString(),
0095                                                     res.value(QLatin1String("countryCode")).toString(),
0096                                                     res.value(QLatin1String("countryName")).toString(),
0097                                                     QString::number(res.value(QLatin1String("geonameId")).toInt()),
0098                                                     findSubdivision(res));
0099             d->m_result.push_back(result);
0100         }
0101 
0102         Q_EMIT finished();
0103     });
0104 }
0105 
0106 LocationQueryReply::LocationQueryReply(QGeoPositionInfoSource *source, QNetworkAccessManager *nam, QObject *parent)
0107     : Reply(new LocationQueryReplyPrivate, parent)
0108 {
0109     Q_D(LocationQueryReply);
0110     if (!source) {
0111         d->setError(LocationQueryReply::NoService);
0112         QMetaObject::invokeMethod(this, &LocationQueryReply::finished, Qt::QueuedConnection);
0113         return;
0114     }
0115 
0116 #if QT_CONFIG(permissions)
0117     QLocationPermission permission;
0118     permission.setAccuracy(QLocationPermission::Precise);
0119     permission.setAvailability(QLocationPermission::WhenInUse);
0120     switch (QCoreApplication::instance()->checkPermission(permission)) {
0121     case Qt::PermissionStatus::Undetermined:
0122         QCoreApplication::instance()->requestPermission(permission, this, [this, nam, source](const auto &permission) {
0123             Q_D(LocationQueryReply);
0124             if (permission.status() == Qt::PermissionStatus::Granted) {
0125                 d->requestPosition(this, source, nam);
0126             } else {
0127                 d->setError(LocationQueryReply::NoService);
0128                 Q_EMIT finished();
0129             }
0130         });
0131         return;
0132     case Qt::PermissionStatus::Denied:
0133         d->setError(LocationQueryReply::NoService);
0134         QMetaObject::invokeMethod(this, &LocationQueryReply::finished, Qt::QueuedConnection);
0135         return;
0136     case Qt::PermissionStatus::Granted:
0137         d->requestPosition(this, source, nam);
0138         break;
0139     }
0140 #else
0141     d->requestPosition(this, source, nam);
0142 #endif
0143 }
0144 
0145 LocationQueryReply::~LocationQueryReply() = default;
0146 
0147 void LocationQueryReplyPrivate::requestPosition(LocationQueryReply *q, QGeoPositionInfoSource *source, QNetworkAccessManager *nam)
0148 {
0149     QObject::connect(source, &QGeoPositionInfoSource::positionUpdated, q, [this, q, nam](const QGeoPositionInfo &pos) {
0150         const auto lat = pos.coordinate().latitude();
0151         const auto lon = pos.coordinate().longitude();
0152         QUrl url(QStringLiteral("http://api.geonames.org/findNearbyJSON"));
0153         QUrlQuery urlQuery;
0154 
0155         urlQuery.addQueryItem(QStringLiteral("lat"), KWeatherCorePrivate::toFixedString(lat));
0156         urlQuery.addQueryItem(QStringLiteral("lng"), KWeatherCorePrivate::toFixedString(lon));
0157         urlQuery.addQueryItem(QStringLiteral("username"), QStringLiteral("kweatherdev"));
0158         url.setQuery(urlQuery);
0159 
0160         auto req = QNetworkRequest(url);
0161 
0162         qWarning() << "lat: " << lat << "lon: " << lon;
0163         auto reply = nam->get(req);
0164 
0165         QObject::connect(reply, &QNetworkReply::finished, q, [this, q, lat, lon, reply] {
0166             reply->deleteLater();
0167             const QJsonDocument document = QJsonDocument::fromJson(reply->readAll());
0168             const QJsonObject root = document.object();
0169             const auto array = root[QLatin1String("geonames")].toArray();
0170             if (array.size()) {
0171                 m_result.push_back(LocationQueryResult(lat,
0172                                                        lon,
0173                                                        array.at(0)[QLatin1String("toponymName")].toString(),
0174                                                        array.at(0)[QLatin1String("name")].toString(),
0175                                                        array.at(0)[QLatin1String("countryCode")].toString(),
0176                                                        array.at(0)[QLatin1String("countryName")].toString(),
0177                                                        QString::number(root[QLatin1String("geonameId")].toInt())));
0178             } else {
0179                 setError(Reply::NotFound);
0180             }
0181 
0182             Q_EMIT q->finished();
0183         });
0184     });
0185 
0186     source->requestUpdate();
0187 }
0188 
0189 const std::vector<LocationQueryResult> &LocationQueryReply::result() const
0190 {
0191     Q_D(const LocationQueryReply);
0192     return d->m_result;
0193 }