Commit 3b22c0d2 authored by Volker Krause's avatar Volker Krause
Browse files

Add infrastructure for caching location queries

parent 53e40a9e
......@@ -13,3 +13,4 @@ target_include_directories(weathertest PRIVATE ${CMAKE_BINARY_DIR})
ecm_add_test(navitiaparsertest.cpp LINK_LIBRARIES Qt5::Test KPublicTransport)
ecm_add_test(hafasparsertest.cpp LINK_LIBRARIES Qt5::Test KPublicTransport)
ecm_add_test(publictransportmanagertest.cpp LINK_LIBRARIES Qt5::Test KPublicTransport)
ecm_add_test(cachetest.cpp LINK_LIBRARIES Qt5::Test KPublicTransport)
/*
Copyright (C) 2018 Volker Krause <vkrause@kde.org>
This program is free software; you can redistribute it and/or modify it
under the terms of the GNU Library General Public License as published by
the Free Software Foundation; either version 2 of the License, or (at your
option) any later version.
This program is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include <KPublicTransport/Cache>
#include <KPublicTransport/Location>
#include <KPublicTransport/LocationRequest>
#include <QDir>
#include <QStandardPaths>
#include <QTest>
#include <QTimeZone>
using namespace KPublicTransport;
class CacheTest : public QObject
{
Q_OBJECT
private slots:
void initTestCase()
{
qputenv("TZ", "UTC");
QStandardPaths::setTestModeEnabled(true);
QDir(QStandardPaths::writableLocation(QStandardPaths::GenericCacheLocation)).removeRecursively();
}
void testLocationCache()
{
LocationRequest req;
req.setCoordinate(52.5, 13.5);
QVERIFY(!req.cacheKey().isEmpty());
auto entry = Cache::lookupLocation(QLatin1String("unittest"), req.cacheKey());
QCOMPARE(entry.type, CacheHitType::Miss);
Cache::addNegativeLocationCacheEntry(QLatin1String("unittest"), req.cacheKey());
entry = Cache::lookupLocation(QLatin1String("unittest"), req.cacheKey());
QCOMPARE(entry.type, CacheHitType::Negative);
Location loc;
loc.setName(QLatin1String("Randa"));
loc.setCoordinate(7.6, 46.1);
Cache::addLocationCacheEntry(QLatin1String("unittest"), req.cacheKey(), {loc});
entry = Cache::lookupLocation(QLatin1String("unittest"), req.cacheKey());
QCOMPARE(entry.type, CacheHitType::Positive);
QCOMPARE(entry.data.size(), 1);
QCOMPARE(entry.data[0].name(), loc.name());
QCOMPARE(entry.data[0].latitude(), 7.6f);
QCOMPARE(entry.data[0].longitude(), 46.1f);
}
};
QTEST_GUILESS_MAIN(CacheTest)
#include "cachetest.moc"
......@@ -9,6 +9,7 @@ set(kpublictransport_srcs
reply.cpp
backends/abstractbackend.cpp
backends/cache.cpp
backends/hafasmgatebackend.cpp
backends/hafasmgateparser.cpp
backends/navitiabackend.cpp
......@@ -49,6 +50,7 @@ ecm_generate_headers(KPublicTransport_FORWARDING_HEADERS
# # ### for testing only
ecm_generate_headers(KPublicTransport_Backends_FORWARDING_HEADERS
HEADER_NAMES
Cache
HafasMgateParser
NavitiaParser
PREFIX KPublicTransport
......
/*
Copyright (C) 2018 Volker Krause <vkrause@kde.org>
This program is free software; you can redistribute it and/or modify it
under the terms of the GNU Library General Public License as published by
the Free Software Foundation; either version 2 of the License, or (at your
option) any later version.
This program is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include "cache.h"
#include <KPublicTransport/Location>
#include <QDebug>
#include <QDir>
#include <QFile>
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
#include <QStandardPaths>
using namespace KPublicTransport;
static QString cachePath(const QString &backendId, const QString &contentType)
{
return QStandardPaths::writableLocation(QStandardPaths::GenericCacheLocation) +
QLatin1String("/org.kde.kpublictransport/") + backendId + QLatin1Char('/') +
contentType + QLatin1Char('/');
}
void Cache::addLocationCacheEntry(const QString &backendId, const QString &cacheKey, const std::vector<Location> &data)
{
const auto dir = cachePath(backendId, QStringLiteral("location"));
QDir().mkpath(dir);
QFile f(dir + cacheKey + QLatin1String(".json"));
f.open(QFile::WriteOnly | QFile::Truncate);
f.write(QJsonDocument(Location::toJson(data)).toJson());
}
void Cache::addNegativeLocationCacheEntry(const QString &backendId, const QString &cacheKey)
{
const auto dir = cachePath(backendId, QStringLiteral("location"));
QDir().mkpath(dir);
QFile f(dir + cacheKey + QLatin1String(".json"));
f.open(QFile::WriteOnly | QFile::Truncate);
// empty file is used as indicator for a negative hit
}
CacheEntry<Location> Cache::lookupLocation(const QString &backendId, const QString &cacheKey)
{
const auto dir = cachePath(backendId, QStringLiteral("location"));
QFile f (dir + cacheKey + QLatin1String(".json"));
if (!f.open(QFile::ReadOnly)) {
return {{}, CacheHitType::Miss };
}
if (f.size() == 0) {
return {{}, CacheHitType::Negative };
}
CacheEntry<Location> entry;
entry.type = CacheHitType::Positive;
entry.data = Location::fromJson(QJsonDocument::fromJson(f.readAll()).array());
return entry;
}
/*
Copyright (C) 2018 Volker Krause <vkrause@kde.org>
This program is free software; you can redistribute it and/or modify it
under the terms of the GNU Library General Public License as published by
the Free Software Foundation; either version 2 of the License, or (at your
option) any later version.
This program is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#ifndef KPUBLICTRANSPORT_CACHE_H
#define KPUBLICTRANSPORT_CACHE_H
#include <vector>
class QString;
namespace KPublicTransport {
class Location;
enum class CacheHitType {
Miss,
Positive,
Negative
};
template <typename T> struct CacheEntry
{
CacheEntry() = default;
CacheEntry(CacheEntry&&) = default;
CacheEntry(const CacheEntry&) = delete;
~CacheEntry() = default;
CacheEntry& operator=(CacheEntry&&) = default;
CacheEntry& operator=(const CacheEntry&) = delete;
std::vector<T> data;
CacheHitType type = CacheHitType::Miss;
};
/** Query result caching functions. */
namespace Cache
{
/** Add data to the cache. */
void addLocationCacheEntry(const QString &backendId, const QString &cacheKey, const std::vector<Location> &data);
/** Add negative cache entry for location queries, ie. remember a result could not be found. */
void addNegativeLocationCacheEntry(const QString &backendId, const QString &cacheKey);
/** Perform cache lookuip for location results. */
CacheEntry<Location> lookupLocation(const QString &backendId, const QString &cacheKey);
}
}
#endif // KPUBLICTRANSPORT_CACHE_H
......@@ -20,6 +20,8 @@
#include "datatypes_p.h"
#include <QHash>
#include <QJsonArray>
#include <QJsonObject>
#include <QTimeZone>
#include <cmath>
......@@ -151,4 +153,43 @@ int Location::distance(float lat1, float lon1, float lat2, float lon2)
return 2.0 * earthRadius * atan2(sqrt(a), sqrt(1.0 - a));
}
QJsonObject Location::toJson(const Location &loc)
{
QJsonObject obj;
obj.insert(QLatin1String("name"), loc.name());
obj.insert(QLatin1String("latitude"), loc.latitude());
obj.insert(QLatin1String("longitude"), loc.longitude());
return obj;
}
QJsonArray Location::toJson(const std::vector<Location> &locs)
{
QJsonArray a;
//a.reserve(locs.size());
std::transform(locs.begin(), locs.end(), std::back_inserter(a), qOverload<const Location&>(&Location::toJson));
return a;
}
static Location fromJsonObject(const QJsonObject &obj)
{
Location loc;
loc.setName(obj.value(QLatin1String("name")).toString());
loc.setCoordinate(obj.value(QLatin1String("latitude")).toDouble(), obj.value(QLatin1String("longitude")).toDouble());
return loc;
}
std::vector<Location> Location::fromJson(const QJsonValue &v)
{
std::vector<Location> res;
if (v.isArray()) {
const auto a = v.toArray();
res.reserve(a.size());
std::transform(a.begin(), a.end(), std::back_inserter(res), [](const auto &v) { return fromJsonObject(v.toObject()); });
} else if (v.isObject()) {
res.push_back(fromJsonObject(v.toObject()));
}
return res;
}
#include "moc_location.cpp"
......@@ -20,6 +20,9 @@
#include "datatypes.h"
class QJsonArray;
class QJsonObject;
class QJsonValue;
class QTimeZone;
template <typename K, typename T> class QHash;
......@@ -67,6 +70,14 @@ public:
/** Compute the distance between two geo coordinates, in meters. */
static int distance(float lat1, float lon1, float lat2, float lon2);
/** Serializes one Location object to JSON. */
static QJsonObject toJson(const Location &loc);
/** Serializes an array of Location objects to JSON. */
static QJsonArray toJson(const std::vector<Location> &locs);
/** Dezerializes one or more Location objects from JSON. */
static std::vector<Location> fromJson(const QJsonValue &v);
};
}
......
......@@ -73,3 +73,17 @@ void LocationRequest::setName(const QString &name)
{
d->name = name;
}
QString LocationRequest::cacheKey() const
{
QString normalizedName;
normalizedName.reserve(d->name.size());
for (const auto c : qAsConst(d->name)) {
if (c.isLetter() || c.isDigit()) {
normalizedName.push_back(c);
}
}
return QString::number((int)(latitude() * 1000000)) + QLatin1Char('x') + QString::number((int)(longitude() * 1000000))
+ QLatin1Char('_') + normalizedName;
}
......@@ -46,6 +46,9 @@ public:
void setName(const QString &name);
// TODO select full name or name fragment mode for auto-completion
// unique string representation used for caching results
QString cacheKey() const;
private:
QExplicitlySharedDataPointer<LocationRequestPrivate> d;
};
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment