Commit 5476365a authored by Volker Krause's avatar Volker Krause

Add basic JSON-LD reservation data management

parent 94f97055
......@@ -21,6 +21,7 @@ ecm_setup_version(PROJECT VARIABLE_PREFIX ITINERARY VERSION_HEADER itinerary_ver
# build-time dependencies
find_package(Qt5 REQUIRED COMPONENTS Test Quick)
find_package(KPkPass REQUIRED)
find_package(KItinerary REQUIRED)
find_package(QmlLint)
set_package_properties(QmlLint PROPERTIES URL "https://qt.io" PURPOSE "Validate QML code.")
find_package(SharedMimeInfo 1.0 REQUIRED)
......
add_definitions(-DSOURCE_DIR="${CMAKE_CURRENT_SOURCE_DIR}")
ecm_add_test(pkpassmanagertest.cpp LINK_LIBRARIES Qt5::Test itinerary)
ecm_add_test(reservationmanagertest.cpp LINK_LIBRARIES Qt5::Test itinerary)
ecm_add_test(timelinemodeltest.cpp LINK_LIBRARIES Qt5::Test itinerary)
[
{
"@context": "http://schema.org",
"@type": "FlightReservation",
"airplaneSeat": "17C",
"reservationFor": {
"@type": "Flight",
"airline": {
"@type": "Airline",
"iataCode": "4U",
"name": "Germanwings"
},
"arrivalAirport": {
"@type": "Airport",
"geo": {
"@type": "GeoCoordinates",
"latitude": 52.55970001220703,
"longitude": 13.287799835205078
},
"iataCode": "TXL",
"name": "Berlin-Tegel"
},
"boardingTime": {
"@type": "QDateTime",
"@value": "2017-06-18T18:40:00+01:00",
"timezone": "Europe/London"
},
"departureAirport": {
"@type": "Airport",
"geo": {
"@type": "GeoCoordinates",
"latitude": 51.477500915527344,
"longitude": -0.4613890051841736
},
"iataCode": "LHR",
"name": "London Heathrow"
},
"departureGate": " - ",
"flightNumber": "8465"
},
"reservationNumber": "XXX007",
"reservedTicket": {
"@type": "Ticket",
"ticketToken": "aztecCode:M1KRAUSE/VOLKER EXXX007 LHRTXL4U 8465 169Y017C0040 147>1181 7168B4U 0000000000000291040PASSPORTID2 LH 123412341234012 "
},
"ticketToken": "aztecCode:M1KRAUSE/VOLKER EXXX007 LHRTXL4U 8465 169Y017C0040 147>1181 7168B4U 0000000000000291040PASSPORTID2 LH 123412341234012 ",
"underName": {
"@type": "Person",
"name": "KRAUSE/VOLKER"
}
}
]
[
{
"@context": "http://schema.org",
"@type": "FlightReservation",
"airplaneSeat": "17C",
"reservationFor": {
"@type": "Flight",
"airline": {
"@type": "Airline",
"iataCode": "4U",
"name": "Germanwings"
},
"arrivalAirport": {
"@type": "Airport",
"geo": {
"@type": "GeoCoordinates",
"latitude": 52.55970001220703,
"longitude": 13.287799835205078
},
"iataCode": "TXL",
"name": "Berlin-Tegel"
},
"departureTime": {
"@type": "QDateTime",
"@value": "2017-06-18T19:10:00+01:00",
"timezone": "Europe/London"
},
"departureAirport": {
"@type": "Airport",
"geo": {
"@type": "GeoCoordinates",
"latitude": 51.477500915527344,
"longitude": -0.4613890051841736
},
"iataCode": "LHR",
"name": "London Heathrow"
},
"departureGate": "42",
"flightNumber": "8465"
},
"reservationNumber": "XXX007"
}
]
/*
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 <http://www.gnu.org/licenses/>.
*/
#include <reservationmanager.h>
#include <QtTest/qtest.h>
#include <QSignalSpy>
#include <QStandardPaths>
class ReservationManagerTest : public QObject
{
Q_OBJECT
private:
void clearReservations(ReservationManager *mgr)
{
for (const auto id : mgr->reservations()) {
mgr->removeReservation(id);
}
}
private slots:
void initTestCase()
{
QStandardPaths::setTestModeEnabled(true);
}
void testOperations()
{
ReservationManager mgr;
clearReservations(&mgr);
QSignalSpy addSpy(&mgr, &ReservationManager::reservationAdded);
QVERIFY(addSpy.isValid());
QSignalSpy updateSpy(&mgr, &ReservationManager::reservationUpdated);
QVERIFY(updateSpy.isValid());
QSignalSpy rmSpy(&mgr, &ReservationManager::reservationRemoved);
QVERIFY(rmSpy.isValid());
QVERIFY(mgr.reservations().isEmpty());
mgr.importReservation(QLatin1String(SOURCE_DIR "/data/4U8465-v1.json"));
auto res = mgr.reservations();
QCOMPARE(res.size(), 1);
const auto resId = res.at(0);
QVERIFY(!resId.isEmpty());
QCOMPARE(addSpy.size(), 1);
QCOMPARE(addSpy.at(0).at(0).toString(), resId);
QVERIFY(updateSpy.isEmpty());
QVERIFY(!mgr.reservation(resId).isNull());
mgr.importReservation(QLatin1String(SOURCE_DIR "/data/4U8465-v2.json"));
QCOMPARE(addSpy.size(), 1);
QCOMPARE(updateSpy.size(), 1);
QCOMPARE(mgr.reservations().size(), 1);
QCOMPARE(updateSpy.at(0).at(0).toString(), resId);
QVERIFY(mgr.reservation(resId).isValid());
mgr.removeReservation(resId);
QCOMPARE(addSpy.size(), 1);
QCOMPARE(updateSpy.size(), 1);
QCOMPARE(rmSpy.size(), 1);
QCOMPARE(rmSpy.at(0).at(0).toString(), resId);
QVERIFY(mgr.reservations().isEmpty());
QVERIFY(mgr.reservation(resId).isNull());
}
};
QTEST_GUILESS_MAIN(ReservationManagerTest)
#include "reservationmanagertest.moc"
set(itinerary_srcs
pkpassmanager.cpp
reservationmanager.cpp
timelinemodel.cpp
)
ecm_qt_declare_logging_category(itinerary_srcs
......@@ -9,6 +10,7 @@ ecm_qt_declare_logging_category(itinerary_srcs
)
add_library(itinerary STATIC ${itinerary_srcs})
target_link_libraries(itinerary PUBLIC
KItinerary
KPkPass
Qt5::Network
)
......
/*
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 <http://www.gnu.org/licenses/>.
*/
#include "reservationmanager.h"
#include "logging.h"
#include <KItinerary/Flight>
#include <KItinerary/JsonLdDocument>
#include <KItinerary/Reservation>
#include <QDir>
#include <QDirIterator>
#include <QFile>
#include <QJsonArray>
#include <QJsonDocument>
#include <QStandardPaths>
#include <QUuid>
#include <QVector>
using namespace KItinerary;
ReservationManager::ReservationManager(QObject* parent)
: QObject(parent)
{
}
ReservationManager::~ReservationManager() = default;
QVector<QString> ReservationManager::reservations() const
{
const auto basePath = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + QStringLiteral("/reservations");
QDir::root().mkpath(basePath);
QVector<QString> resIds;
for (QDirIterator it(basePath, QDir::NoDotAndDotDot | QDir::Files); it.hasNext();) {
it.next();
resIds.push_back(it.fileInfo().baseName());
}
return resIds;
}
QVariant ReservationManager::reservation(const QString& id) const
{
const auto it = m_reservations.constFind(id);
if (it != m_reservations.constEnd()) {
return it.value();
}
const QString resPath = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + QStringLiteral("/reservations/") + id + QLatin1String(".jsonld");
QFile f(resPath);
if (!f.open(QFile::ReadOnly)) {
qCWarning(Log) << "Failed to open JSON-LD reservation data file:" << resPath << f.errorString();
return {};
}
const auto doc = QJsonDocument::fromJson(f.readAll());
if (!doc.isArray() && doc.array().size() != 1) {
qCWarning(Log) << "Invalid JSON-LD reservation data file:" << resPath;
return {};
}
const auto resData = JsonLdDocument::fromJson(doc.array());
if (resData.size() != 1) {
qCWarning(Log) << "Unable to parse JSON-LD reservation data file:" << resPath;
return {};
}
m_reservations.insert(id, resData.at(0));
return resData.at(0);
}
void ReservationManager::importReservation(const QString& filename)
{
const auto basePath = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + QStringLiteral("/reservations/");
QDir::root().mkpath(basePath);
QFile f(filename);
if (!f.open(QFile::ReadOnly)) {
qCWarning(Log) << "Unable to open file:" << f.errorString();
return;
}
const auto doc = QJsonDocument::fromJson(f.readAll());
if (!doc.isArray()) {
qCWarning(Log) << "Invalid JSON format.";
return;
}
const auto resData = JsonLdDocument::fromJson(doc.array());
for (auto res : resData) {
QString resId;
bool oldResFound = false;
// check if we know this one already, and update if that's the case
for (const auto &oldResId : reservations()) {
const auto oldRes = reservation(oldResId);
if (isSameReservation(oldRes, res)) {
res = JsonLdDocument::apply(oldRes, res);
resId = oldResId;
oldResFound = true;
break;
}
}
if (resId.isEmpty()) {
resId = QUuid::createUuid().toString();
}
const QString path = basePath + resId + QLatin1String(".jsonld");
QFile f(path);
if (!f.open(QFile::WriteOnly)) {
qCWarning(Log) << "Unable to create file:" << f.errorString();
continue;
}
f.write(QJsonDocument(JsonLdDocument::toJson({res})).toJson());
m_reservations.insert(resId, res);
if (oldResFound) {
emit reservationUpdated(resId);
} else {
emit reservationAdded(resId);
}
}
}
void ReservationManager::removeReservation(const QString& id)
{
const auto basePath = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + QStringLiteral("/reservations/");
QFile::remove(basePath + QLatin1Char('/') + id + QLatin1String(".jsonld"));
m_reservations.remove(id);
emit reservationRemoved(id);
}
bool ReservationManager::isSameReservation(const QVariant& lhs, const QVariant& rhs) const
{
if (lhs.isNull() || rhs.isNull()) {
return false;
}
if (lhs.userType() != rhs.userType()) {
return false;
}
// flight: booking ref, flight number and departure day match
if (lhs.userType() == qMetaTypeId<FlightReservation>()) {
const auto lhsRes = lhs.value<FlightReservation>();
const auto rhsRes = rhs.value<FlightReservation>();
if (lhsRes.reservationNumber() != rhsRes.reservationNumber() || lhsRes.reservationNumber().isEmpty()) {
return false;
}
const auto lhsFlight = lhsRes.reservationFor().value<Flight>();
const auto rhsFlight = rhsRes.reservationFor().value<Flight>();
if (lhsFlight.flightNumber() != rhsFlight.flightNumber() || lhsFlight.flightNumber().isEmpty()) {
return false;
}
// TODO check departure day
}
// TODO train/bus: booking ref, train number and depature day match
// TODO hotel: booking ref, checkin day match
// TODO for all: underName either matches or is not set
return true;
}
/*
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 <http://www.gnu.org/licenses/>.
*/
#ifndef RESERVATIONMANAGER_H
#define RESERVATIONMANAGER_H
#include <QHash>
#include <QObject>
/** Manages JSON-LD reservation data. */
class ReservationManager : public QObject
{
Q_OBJECT
public:
ReservationManager(QObject *parent = nullptr);
~ReservationManager();
QVector<QString> reservations() const;
QVariant reservation(const QString &id) const;
void importReservation(const QString &filename);
void removeReservation(const QString &id);
signals:
void reservationAdded(const QString &id);
void reservationUpdated(const QString &id);
void reservationRemoved(const QString &id);
private:
bool isSameReservation(const QVariant &lhs, const QVariant &rhs) const;
mutable QHash<QString, QVariant> m_reservations;
};
#endif // RESERVATIONMANAGER_H
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