diff --git a/autotests/mergeutiltest.cpp b/autotests/mergeutiltest.cpp index 7ad7cc5..3508c25 100644 --- a/autotests/mergeutiltest.cpp +++ b/autotests/mergeutiltest.cpp @@ -1,100 +1,121 @@ /* Copyright (C) 2018 Volker Krause 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 . */ #include #include #include #include #include #include #include #include using namespace KItinerary; class MergeUtilTest : public QObject { Q_OBJECT private Q_SLOTS: void testIsSameReservation() { QVERIFY(!MergeUtil::isSameReservation({}, {})); FlightReservation res1; QVERIFY(!MergeUtil::isSameReservation(QVariant::fromValue(res1), {})); QVERIFY(!MergeUtil::isSameReservation({}, QVariant::fromValue(res1))); res1.setReservationNumber(QLatin1String("XXX007")); Flight flight1; flight1.setFlightNumber(QLatin1String("1234")); res1.setReservationFor(QVariant::fromValue(flight1)); FlightReservation res2; res2.setReservationNumber(QLatin1String("YYY008")); Flight flight2; flight2.setFlightNumber(QLatin1String("1234")); res2.setReservationFor(QVariant::fromValue(flight2)); QVERIFY(!MergeUtil::isSameReservation(QVariant::fromValue(res1), QVariant::fromValue(res2))); res2.setReservationNumber(QLatin1String("XXX007")); QVERIFY(MergeUtil::isSameReservation(QVariant::fromValue(res1), QVariant::fromValue(res2))); } void testIsSameFlight() { Airline airline1; airline1.setIataCode(QLatin1String("KL")); Flight f1; f1.setAirline(airline1); f1.setFlightNumber(QLatin1String("8457")); f1.setDepartureTime(QDateTime(QDate(2018, 4, 2), QTime(17, 51, 0))); Flight f2; QVERIFY(!MergeUtil::isSameFlight(f1, f2)); f2.setFlightNumber(QLatin1String("8457")); QVERIFY(!MergeUtil::isSameFlight(f1, f2)); Airline airline2; airline2.setIataCode(QLatin1String("AF")); f2.setAirline(airline2); QVERIFY(!MergeUtil::isSameFlight(f1, f2)); airline2.setIataCode(QLatin1String("KL")); f2.setAirline(airline2); QVERIFY(!MergeUtil::isSameFlight(f1, f2)); f2.setDepartureDay(QDate(2018, 4, 2)); QVERIFY(MergeUtil::isSameFlight(f1, f2)); } void testIsSamePerson() { Person p1; p1.setName(QLatin1String("VOLKER KRAUSE")); QVERIFY(!MergeUtil::isSamePerson(p1, {})); QVERIFY(!MergeUtil::isSamePerson({}, p1)); QVERIFY(MergeUtil::isSamePerson(p1, p1)); Person p2; p2.setName(QLatin1String("Volker Krause")); QVERIFY(MergeUtil::isSamePerson(p1, p2)); } + + void testIsSameLodingReservation() + { + LodgingReservation res1; + LodgingBusiness hotel1; + hotel1.setName(QLatin1String("Haus Randa")); + res1.setReservationFor(QVariant::fromValue(hotel1)); + res1.setCheckinTime(QDateTime(QDate(2018, 4, 9), QTime(10, 0))); + res1.setReservationNumber(QLatin1String("1234")); + + LodgingReservation res2; + QVERIFY(!MergeUtil::isSameReservation(QVariant::fromValue(res1), QVariant::fromValue(res2))); + res2.setReservationNumber(QLatin1String("1234")); + QVERIFY(!MergeUtil::isSameReservation(QVariant::fromValue(res1), QVariant::fromValue(res2))); + res2.setCheckinTime(QDateTime(QDate(2018, 4, 9), QTime(15, 0))); + QVERIFY(!MergeUtil::isSameReservation(QVariant::fromValue(res1), QVariant::fromValue(res2))); + LodgingBusiness hotel2; + hotel2.setName(QLatin1String("Haus Randa")); + res2.setReservationFor(QVariant::fromValue(hotel2)); + QVERIFY(MergeUtil::isSameReservation(QVariant::fromValue(res1), QVariant::fromValue(res2))); + } }; QTEST_APPLESS_MAIN(MergeUtilTest) #include "mergeutiltest.moc" diff --git a/src/calendarhandler.cpp b/src/calendarhandler.cpp index 77dba54..ee5b6c3 100644 --- a/src/calendarhandler.cpp +++ b/src/calendarhandler.cpp @@ -1,262 +1,254 @@ /* Copyright (c) 2017 Volker Krause This library 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 library 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 Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "calendarhandler.h" #include "jsonlddocument.h" #include "logging.h" #include #include #include #include using namespace KCalCore; using namespace KItinerary; QDateTime CalendarHandler::startDateTime(const QVariant &reservation) { if (reservation.userType() == qMetaTypeId() || reservation.userType() == qMetaTypeId() || reservation.userType() == qMetaTypeId()) { const auto trip = JsonLdDocument::readProperty(reservation, "reservationFor"); return JsonLdDocument::readProperty(trip, "departureTime").toDateTime(); } else if (reservation.userType() == qMetaTypeId()) { - return JsonLdDocument::readProperty(reservation, "checkinDate").toDateTime(); + return reservation.value().checkinTime(); } return {}; } Event::Ptr CalendarHandler::findEvent(const Calendar::Ptr &calendar, const QVariant &reservation) { const auto bookingRef = JsonLdDocument::readProperty(reservation, "reservationNumber").toString(); if (bookingRef.isEmpty()) { return {}; } auto dt = startDateTime(reservation); if (reservation.userType() == qMetaTypeId()) { dt = QDateTime(dt.date(), QTime()); } const auto events = calendar->events(dt); for (const auto &event : events) { if (event->dtStart() == dt && event->uid().startsWith(bookingRef)) { return event; } } return {}; } void CalendarHandler::fillEvent(const QVariant &reservation, const KCalCore::Event::Ptr &event) { const int typeId = reservation.userType(); if (typeId == qMetaTypeId()) { fillFlightReservation(reservation, event); } else if (typeId == qMetaTypeId()) { - fillLodgingReservation(reservation, event); + fillLodgingReservation(reservation.value(), event); } else if (typeId == qMetaTypeId()) { fillTrainReservation(reservation, event); } else if (typeId == qMetaTypeId()) { fillBusReservation(reservation, event); } const auto bookingRef = JsonLdDocument::readProperty(reservation, "reservationNumber").toString(); if (!event->uid().startsWith(bookingRef)) { event->setUid(bookingRef + QLatin1Char('-') + event->uid()); } } void CalendarHandler::fillFlightReservation(const QVariant &reservation, const KCalCore::Event::Ptr &event) { const auto flight = JsonLdDocument::readProperty(reservation, "reservationFor"); const auto airline = JsonLdDocument::readProperty(flight, "airline"); const auto depPort = JsonLdDocument::readProperty(flight, "departureAirport"); const auto arrPort = JsonLdDocument::readProperty(flight, "arrivalAirport"); if (flight.isNull() || airline.isNull() || depPort.isNull() || arrPort.isNull()) { qCDebug(Log) << "got invalid flight reservation"; return; } const QString flightNumber = JsonLdDocument::readProperty(airline, "iataCode").toString() + QLatin1Char(' ') + JsonLdDocument::readProperty(flight, "flightNumber").toString(); event->setSummary(i18n("Flight %1 from %2 to %3", flightNumber, JsonLdDocument::readProperty(depPort, "iataCode").toString(), JsonLdDocument::readProperty(arrPort, "iataCode").toString() )); event->setLocation(JsonLdDocument::readProperty(depPort, "name").toString()); fillGeoPosition(depPort, event); event->setDtStart(JsonLdDocument::readProperty(flight, "departureTime").toDateTime()); event->setDtEnd(JsonLdDocument::readProperty(flight, "arrivalTime").toDateTime()); event->setAllDay(false); const auto boardingTime = JsonLdDocument::readProperty(flight, "boardingTime").toDateTime(); const auto departureGate = JsonLdDocument::readProperty(flight, "departureGate").toString(); if (boardingTime.isValid()) { Alarm::Ptr alarm(new Alarm(event.data())); alarm->setStartOffset(Duration(event->dtStart(), boardingTime)); if (departureGate.isEmpty()) { alarm->setDisplayAlarm(i18n("Boarding for flight %1", flightNumber)); } else { alarm->setDisplayAlarm(i18n("Boarding for flight %1 at gate %2", flightNumber, departureGate)); } alarm->setEnabled(true); event->addAlarm(alarm); } QStringList desc; if (boardingTime.isValid()) { desc.push_back(i18n("Boarding time: %1", QLocale().toString(boardingTime.time(), QLocale::ShortFormat))); } if (!departureGate.isEmpty()) { desc.push_back(i18n("Departure gate: %1", departureGate)); } auto s = JsonLdDocument::readProperty(reservation, "boardingGroup").toString(); if (!s.isEmpty()) { desc.push_back(i18n("Boarding group: %1", s)); } s = JsonLdDocument::readProperty(reservation, "airplaneSeat").toString(); if (!s.isEmpty()) { desc.push_back(i18n("Seat: %1", s)); } s = JsonLdDocument::readProperty(reservation, "reservationNumber").toString(); if (!s.isEmpty()) { desc.push_back(i18n("Booking reference: %1", s)); } event->setDescription(desc.join(QLatin1Char('\n'))); } void CalendarHandler::fillTripReservation(const QVariant &reservation, const KCalCore::Event::Ptr &event) { const auto trip = JsonLdDocument::readProperty(reservation, "reservationFor"); const auto depStation = JsonLdDocument::readProperty(trip, "departureStation"); const auto arrStation = JsonLdDocument::readProperty(trip, "arrivalStation"); event->setLocation(JsonLdDocument::readProperty(depStation, "name").toString()); fillGeoPosition(depStation, event); event->setDtStart(JsonLdDocument::readProperty(trip, "departureTime").toDateTime()); event->setDtEnd(JsonLdDocument::readProperty(trip, "arrivalTime").toDateTime()); event->setAllDay(false); QStringList desc; auto s = JsonLdDocument::readProperty(trip, "departurePlatform").toString(); if (!s.isEmpty()) { desc.push_back(i18n("Departure platform: %1", s)); } const auto ticket = JsonLdDocument::readProperty(reservation, "reservedTicket"); const auto seat = JsonLdDocument::readProperty(ticket, "ticketedSeat"); s = JsonLdDocument::readProperty(seat, "seatSection").toString(); if (!s.isEmpty()) { desc.push_back(i18n("Coach: %1", s)); } s = JsonLdDocument::readProperty(seat, "seatNumber").toString(); if (!s.isEmpty()) { desc.push_back(i18n("Seat: %1", s)); } s = JsonLdDocument::readProperty(trip, "arrivalPlatform").toString(); if (!s.isEmpty()) { desc.push_back(i18n("Arrival platform: %1", s)); } s = JsonLdDocument::readProperty(reservation, "reservationNumber").toString(); if (!s.isEmpty()) { desc.push_back(i18n("Booking reference: %1", s)); } event->setDescription(desc.join(QLatin1Char('\n'))); } void CalendarHandler::fillTrainReservation(const QVariant &reservation, const KCalCore::Event::Ptr &event) { const auto trip = JsonLdDocument::readProperty(reservation, "reservationFor"); const auto depStation = JsonLdDocument::readProperty(trip, "departureStation"); const auto arrStation = JsonLdDocument::readProperty(trip, "arrivalStation"); if (trip.isNull() || depStation.isNull() || arrStation.isNull()) { return; } event->setSummary(i18n("Train %1 from %2 to %3", JsonLdDocument::readProperty(trip, "trainNumber").toString(), JsonLdDocument::readProperty(depStation, "name").toString(), JsonLdDocument::readProperty(arrStation, "name").toString() )); fillTripReservation(reservation, event); } void CalendarHandler::fillBusReservation(const QVariant &reservation, const KCalCore::Event::Ptr &event) { const auto trip = JsonLdDocument::readProperty(reservation, "reservationFor"); const auto depStation = JsonLdDocument::readProperty(trip, "departureStation"); const auto arrStation = JsonLdDocument::readProperty(trip, "arrivalStation"); if (trip.isNull() || depStation.isNull() || arrStation.isNull()) { return; } event->setSummary(i18n("Bus %1 from %2 to %3", JsonLdDocument::readProperty(trip, "busNumber").toString(), JsonLdDocument::readProperty(depStation, "name").toString(), JsonLdDocument::readProperty(arrStation, "name").toString() )); fillTripReservation(reservation, event); } -void CalendarHandler::fillLodgingReservation(const QVariant &reservation, const KCalCore::Event::Ptr &event) +void CalendarHandler::fillLodgingReservation(const LodgingReservation &reservation, const KCalCore::Event::Ptr &event) { - const auto lodgingBusiness = JsonLdDocument::readProperty(reservation, "reservationFor"); - const auto address = JsonLdDocument::readProperty(lodgingBusiness, "address"); - if (lodgingBusiness.isNull() || address.isNull()) { + if (reservation.reservationFor().isNull()) { return; } + const auto lodgingBusiness = reservation.reservationFor().value(); + const auto address = lodgingBusiness.address(); - event->setSummary(i18n("Hotel reservation: %1", - JsonLdDocument::readProperty(lodgingBusiness, "name").toString() - )); + event->setSummary(i18n("Hotel reservation: %1", lodgingBusiness.name())); event->setLocation(i18nc(", , ", "%1, %2 %3, %4", - JsonLdDocument::readProperty(address, "streetAddress").toString(), - JsonLdDocument::readProperty(address, "postalCode").toString(), - JsonLdDocument::readProperty(address, "addressLocality").toString(), - JsonLdDocument::readProperty(address, "addressCountry").toString() - )); - fillGeoPosition(lodgingBusiness, event); + address.streetAddress(), address.postalCode(), + address.addressLocality(), address.addressCountry())); + fillGeoPosition(QVariant::fromValue(lodgingBusiness), event); - const auto checkinDt = JsonLdDocument::readProperty(reservation, "checkinDate").toDateTime(); - const auto checkoutDt = JsonLdDocument::readProperty(reservation, "checkoutDate").toDateTime(); - event->setDtStart(QDateTime(checkinDt.date(), QTime())); - event->setDtEnd(QDateTime(checkoutDt.date(), QTime())); + event->setDtStart(QDateTime(reservation.checkinTime().date(), QTime())); + event->setDtEnd(QDateTime(reservation.checkoutTime().date(), QTime())); event->setAllDay(true); event->setDescription(i18n("Check-in: %1\nCheck-out: %2\nBooking reference: %3", - QLocale().toString(checkinDt.time(), QLocale::ShortFormat), - QLocale().toString(checkoutDt.time(), QLocale::ShortFormat), - JsonLdDocument::readProperty(reservation, "reservationNumber").toString() - )); + QLocale().toString(reservation.checkinTime().time(), QLocale::ShortFormat), + QLocale().toString(reservation.checkoutTime().time(), QLocale::ShortFormat), + reservation.reservationNumber())); event->setTransparency(Event::Transparent); } void CalendarHandler::fillGeoPosition(const QVariant &place, const KCalCore::Event::Ptr &event) { const auto geo = JsonLdDocument::readProperty(place, "geo").value(); if (!geo.isValid()) { return; } event->setHasGeo(true); event->setGeoLatitude(geo.latitude()); event->setGeoLongitude(geo.longitude()); } diff --git a/src/calendarhandler.h b/src/calendarhandler.h index 3862bdc..5e90a09 100644 --- a/src/calendarhandler.h +++ b/src/calendarhandler.h @@ -1,58 +1,60 @@ /* Copyright (c) 2017 Volker Krause This library 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 library 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 Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef CALENDARHANDLER_H #define CALENDARHANDLER_H #include "kitinerary_export.h" #include #include class QVariant; namespace KItinerary { +class LodgingReservation; + /** Methods for converting between ical events and JSON-LD booking data. */ class KITINERARY_EXPORT CalendarHandler { public: /** Returns the start time associated with the given reservation. */ static QDateTime startDateTime(const QVariant &reservation); /** Attempts to find an event in @p calendar for @p reservation. */ static KCalCore::Event::Ptr findEvent(const KCalCore::Calendar::Ptr &calendar, const QVariant &reservation); /** Fills @p event with details of @p reservation. * Can be used on new events or to update existing ones. */ static void fillEvent(const QVariant &reservation, const KCalCore::Event::Ptr &event); private: static void fillFlightReservation(const QVariant &reservation, const KCalCore::Event::Ptr &event); static void fillTripReservation(const QVariant &reservation, const KCalCore::Event::Ptr &event); static void fillTrainReservation(const QVariant &reservation, const KCalCore::Event::Ptr &event); static void fillBusReservation(const QVariant &reservation, const KCalCore::Event::Ptr &event); - static void fillLodgingReservation(const QVariant &reservation, const KCalCore::Event::Ptr &event); + static void fillLodgingReservation(const LodgingReservation &reservation, const KCalCore::Event::Ptr &event); static void fillGeoPosition(const QVariant &place, const KCalCore::Event::Ptr &event); }; } #endif // CALENDARHANDLER_H diff --git a/src/datatypes/reservation.cpp b/src/datatypes/reservation.cpp index 71d1f5f..3ceaf5d 100644 --- a/src/datatypes/reservation.cpp +++ b/src/datatypes/reservation.cpp @@ -1,115 +1,115 @@ /* Copyright (C) 2018 Volker Krause 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 . */ #include "reservation.h" #include "organization.h" #include "datatypes_p.h" #include #include using namespace KItinerary; namespace KItinerary { class ReservationPrivate : public QSharedData { KITINERARY_PRIVATE_BASE_GADGET(Reservation) public: QString reservationNumber; QVariant reservationFor; QVariant reservedTicket; QVariant underName; QUrl url; QUrl cancelReservationUrl; QUrl modifyReservationUrl; QString pkpassPassTypeIdentifier; QString pkpassSerialNumber; Organization provider; }; KITINERARY_MAKE_BASE_CLASS(Reservation) KITINERARY_MAKE_PROPERTY(Reservation, QString, reservationNumber, setReservationNumber) KITINERARY_MAKE_PROPERTY(Reservation, QVariant, reservationFor, setReservationFor) KITINERARY_MAKE_PROPERTY(Reservation, QVariant, reservedTicket, setReservedTicket) KITINERARY_MAKE_PROPERTY(Reservation, QVariant, underName, setUnderName) KITINERARY_MAKE_PROPERTY(Reservation, QUrl, cancelReservationUrl, setCancelReservationUrl) KITINERARY_MAKE_PROPERTY(Reservation, QUrl, modifyReservationUrl, setModifyReservationUrl) KITINERARY_MAKE_PROPERTY(Reservation, QUrl, url, setUrl) KITINERARY_MAKE_PROPERTY(Reservation, QString, pkpassPassTypeIdentifier, setPkpassPassTypeIdentifier) KITINERARY_MAKE_PROPERTY(Reservation, QString, pkpassSerialNumber, setPkpassSerialNumber) KITINERARY_MAKE_PROPERTY(Reservation, Organization, provider, setProvider) class LodgingReservationPrivate : public ReservationPrivate { KITINERARY_PRIVATE_GADGET(LodgingReservation) public: - QDateTime checkinDate; - QDateTime checkoutDate; + QDateTime checkinTime; + QDateTime checkoutTime; }; KITINERARY_MAKE_SUB_CLASS(LodgingReservation, Reservation) -KITINERARY_MAKE_PROPERTY(LodgingReservation, QDateTime, checkinDate, setCheckinDate) -KITINERARY_MAKE_PROPERTY(LodgingReservation, QDateTime, checkoutDate, setCheckoutDate) +KITINERARY_MAKE_PROPERTY(LodgingReservation, QDateTime, checkinTime, setCheckinTime) +KITINERARY_MAKE_PROPERTY(LodgingReservation, QDateTime, checkoutTime, setCheckoutTime) QString LodgingReservation::checkinDateLocalized() const { K_D(const LodgingReservation); - return QLocale().toString(d->checkinDate.date(), QLocale::ShortFormat); + return QLocale().toString(d->checkinTime.date(), QLocale::ShortFormat); } QString LodgingReservation::checkoutDateLocalized() const { K_D(const LodgingReservation); - return QLocale().toString(d->checkoutDate.date(), QLocale::ShortFormat); + return QLocale().toString(d->checkoutTime.date(), QLocale::ShortFormat); } class FlightReservationPrivate : public ReservationPrivate { KITINERARY_PRIVATE_GADGET(FlightReservation) public: QString airplaneSeat; QString boardingGroup; QUrl ticketDownloadUrl; }; KITINERARY_MAKE_SUB_CLASS(FlightReservation, Reservation) KITINERARY_MAKE_PROPERTY(FlightReservation, QString, airplaneSeat, setAirplaneSeat) KITINERARY_MAKE_PROPERTY(FlightReservation, QString, boardingGroup, setBoardingGroup) KITINERARY_MAKE_PROPERTY(FlightReservation, QUrl, ticketDownloadUrl, setTicketDownloadUrl) class TrainReservationPrivate : public ReservationPrivate { KITINERARY_PRIVATE_GADGET(TrainReservation) }; KITINERARY_MAKE_SUB_CLASS(TrainReservation, Reservation) class BusReservationPrivate : public ReservationPrivate { KITINERARY_PRIVATE_GADGET(BusReservation) }; KITINERARY_MAKE_SUB_CLASS(BusReservation, Reservation) } template <> KItinerary::ReservationPrivate *QExplicitlySharedDataPointer::clone() { return d->clone(); } #include "moc_reservation.cpp" diff --git a/src/datatypes/reservation.h b/src/datatypes/reservation.h index c804c2d..820da35 100644 --- a/src/datatypes/reservation.h +++ b/src/datatypes/reservation.h @@ -1,119 +1,120 @@ /* Copyright (C) 2018 Volker Krause 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 . */ #ifndef KITINERARY_RESERVATION_H #define KITINERARY_RESERVATION_H #include "kitinerary_export.h" #include "datatypes.h" class QUrl; namespace KItinerary { class ReservationPrivate; class Organization; /** Abstract base class for reservations. * @see https://schema.org/Reservation */ class KITINERARY_EXPORT Reservation { KITINERARY_BASE_GADGET(Reservation) KITINERARY_PROPERTY(QString, reservationNumber, setReservationNumber) KITINERARY_PROPERTY(QVariant, reservationFor, setReservationFor) KITINERARY_PROPERTY(QVariant, reservedTicket, setReservedTicket) KITINERARY_PROPERTY(QVariant, underName, setUnderName) KITINERARY_PROPERTY(KItinerary::Organization, provider, setProvider) KITINERARY_PROPERTY(QUrl, url, setUrl) // Google extension KITINERARY_PROPERTY(QUrl, cancelReservationUrl, setCancelReservationUrl) KITINERARY_PROPERTY(QUrl, modifyReservationUrl, setModifyReservationUrl) // KDE extensions /** Pass type identifier of an associated Apple Wallet boarding pass. * @see KPkPass::Pass::passTypeIdentifier */ KITINERARY_PROPERTY(QString, pkpassPassTypeIdentifier, setPkpassPassTypeIdentifier) /** Serial number of an associated Apple Wallet boarding pass. * @see KPkPass::Pass::serialNumber */ KITINERARY_PROPERTY(QString, pkpassSerialNumber, setPkpassSerialNumber) protected: ///@cond internal QExplicitlySharedDataPointer d; ///@endcond }; /** A hotel reservation. * @see https://schema.org/LodgingReservation + * @see https://developers.google.com/gmail/markup/reference/hotel-reservation */ class KITINERARY_EXPORT LodgingReservation : public Reservation { KITINERARY_GADGET(LodgingReservation) - KITINERARY_PROPERTY(QDateTime, checkinDate, setCheckinDate) - KITINERARY_PROPERTY(QDateTime, checkoutDate, setCheckoutDate) + KITINERARY_PROPERTY(QDateTime, checkinTime, setCheckinTime) + KITINERARY_PROPERTY(QDateTime, checkoutTime, setCheckoutTime) Q_PROPERTY(QString checkinDateLocalized READ checkinDateLocalized STORED false CONSTANT) Q_PROPERTY(QString checkoutDateLocalized READ checkoutDateLocalized STORED false CONSTANT) private: QString checkinDateLocalized() const; QString checkoutDateLocalized() const; }; /** A flight reservation. * @see https://schema.org/FlightReservation * @see https://developers.google.com/gmail/markup/reference/flight-reservation */ class KITINERARY_EXPORT FlightReservation : public Reservation { KITINERARY_GADGET(FlightReservation) // Google extensions KITINERARY_PROPERTY(QString, airplaneSeat, setAirplaneSeat) KITINERARY_PROPERTY(QString, boardingGroup, setBoardingGroup) KITINERARY_PROPERTY(QUrl, ticketDownloadUrl, setTicketDownloadUrl) }; /** A train reservation. * @see https://schema.org/TrainReservation */ class KITINERARY_EXPORT TrainReservation : public Reservation { KITINERARY_GADGET(TrainReservation) }; /** A bus reservation. * @see https://schema.org/BusReservation */ class KITINERARY_EXPORT BusReservation : public Reservation { KITINERARY_GADGET(BusReservation) }; } Q_DECLARE_METATYPE(KItinerary::FlightReservation) Q_DECLARE_METATYPE(KItinerary::LodgingReservation) Q_DECLARE_METATYPE(KItinerary::TrainReservation) Q_DECLARE_METATYPE(KItinerary::BusReservation) #endif // KITINERARY_RESERVATION_H diff --git a/src/extractorpostprocessor.cpp b/src/extractorpostprocessor.cpp index 7527593..fe0f10f 100644 --- a/src/extractorpostprocessor.cpp +++ b/src/extractorpostprocessor.cpp @@ -1,287 +1,285 @@ /* Copyright (c) 2017 Volker Krause This library 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 library 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 Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "extractorpostprocessor.h" #include "calendarhandler.h" #include "jsonlddocument.h" #include "airportdb/airportdb.h" #include "iatabcbpparser.h" #include #include #include #include #include #include #include #include #include using namespace KItinerary; namespace KItinerary { class ExtractorPostprocessorPrivate { public: QVariant processProperty(QVariant obj, const char *name, QVariant (ExtractorPostprocessorPrivate::*processor)(QVariant) const) const; QVariant processFlightReservation(FlightReservation res) const; QVariant processFlight(QVariant flight) const; QVariant processAirport(QVariant airport) const; QVariant processAirline(QVariant airline) const; void processFlightTime(QVariant &flight, const char *timePropName, const char *airportPropName) const; QVariant processReservation(QVariant res) const; bool filterReservation(const QVariant &res) const; - bool filterLodgingReservation(const QVariant &res) const; + bool filterLodgingReservation(const LodgingReservation &res) const; bool filterFlight(const Flight &flight) const; bool filterAirport(const Airport &airport) const; bool filterTrainOrBusTrip(const QVariant &trip) const; bool filterTrainOrBusStation(const QVariant &station) const; QVector m_data; QDateTime m_contextDate; }; } ExtractorPostprocessor::ExtractorPostprocessor() : d(new ExtractorPostprocessorPrivate) { } ExtractorPostprocessor::ExtractorPostprocessor(ExtractorPostprocessor &&) = default; ExtractorPostprocessor::~ExtractorPostprocessor() = default; void ExtractorPostprocessor::process(const QVector &data) { d->m_data.reserve(data.size()); for (auto elem : data) { if (elem.userType() == qMetaTypeId()) { elem = d->processFlightReservation(elem.value()); } else if (elem.userType() == qMetaTypeId()) { elem = d->processReservation(elem); } else if (elem.userType() == qMetaTypeId()) { elem = d->processReservation(elem); } else if (elem.userType() == qMetaTypeId()) { elem = d->processReservation(elem); } if (d->filterReservation(elem)) { d->m_data.push_back(elem); } } std::stable_sort(d->m_data.begin(), d->m_data.end(), [](const QVariant &lhs, const QVariant &rhs) { return CalendarHandler::startDateTime(lhs) < CalendarHandler::startDateTime(rhs); }); } QVector ExtractorPostprocessor::result() const { return d->m_data; } void ExtractorPostprocessor::setContextDate(const QDateTime& dt) { d->m_contextDate = dt; } QVariant ExtractorPostprocessorPrivate::processProperty(QVariant obj, const char *name, QVariant (ExtractorPostprocessorPrivate::*processor)(QVariant) const) const { auto value = JsonLdDocument::readProperty(obj, name); value = (this->*processor)(value); JsonLdDocument::writeProperty(obj, name, value); return obj; } QVariant ExtractorPostprocessorPrivate::processFlightReservation(FlightReservation res) const { // expand ticketToken for IATA BCBP data auto bcbp = res.reservedTicket().value().ticketToken(); if (!bcbp.isEmpty()) { if (bcbp.startsWith(QLatin1String("aztecCode:"))) { bcbp = bcbp.mid(10); } else if (bcbp.startsWith(QLatin1String("qrCode:"))) { bcbp = bcbp.mid(7); } const auto bcbpData = IataBcbpParser::parse(bcbp, m_contextDate.date()); if (bcbpData.size() == 1) { res = JsonLdDocument::apply(bcbpData.at(0), QVariant::fromValue(res)).value(); } } res = processReservation(QVariant::fromValue(res)).value(); res = processProperty(QVariant::fromValue(res), "reservationFor", &ExtractorPostprocessorPrivate::processFlight).value(); return QVariant::fromValue(res); } QVariant ExtractorPostprocessorPrivate::processFlight(QVariant flight) const { flight = processProperty(flight, "departureAirport", &ExtractorPostprocessorPrivate::processAirport); flight = processProperty(flight, "arrivalAirport", &ExtractorPostprocessorPrivate::processAirport); flight = processProperty(flight, "airline", &ExtractorPostprocessorPrivate::processAirline); processFlightTime(flight, "boardingTime", "departureAirport"); processFlightTime(flight, "departureTime", "departureAirport"); processFlightTime(flight, "arrivalTime", "arrivalAirport"); return flight; } QVariant ExtractorPostprocessorPrivate::processAirport(QVariant airport) const { // clean up name const auto name = JsonLdDocument::readProperty(airport, "name").toString(); JsonLdDocument::writeProperty(airport, "name", name.trimmed()); // complete missing IATA codes auto iataCode = JsonLdDocument::readProperty(airport, "iataCode").toString(); if (iataCode.isEmpty()) { iataCode = AirportDb::iataCodeFromName(name).toString(); if (!iataCode.isEmpty()) { JsonLdDocument::writeProperty(airport, "iataCode", iataCode); } } // complete missing geo coordinates auto geo = JsonLdDocument::readProperty(airport, "geo"); if (!geo.value().isValid()) { const auto coord = AirportDb::coordinateForAirport(AirportDb::IataCode{iataCode}); if (coord.isValid()) { geo = QVariant::fromValue(GeoCoordinates()); JsonLdDocument::writeProperty(geo, "latitude", coord.latitude); JsonLdDocument::writeProperty(geo, "longitude", coord.longitude); JsonLdDocument::writeProperty(airport, "geo", geo); } } return airport; } QVariant ExtractorPostprocessorPrivate::processAirline(QVariant airline) const { const auto name = JsonLdDocument::readProperty(airline, "name").toString(); JsonLdDocument::writeProperty(airline, "name", name.trimmed()); return airline; } void ExtractorPostprocessorPrivate::processFlightTime(QVariant &flight, const char *timePropName, const char *airportPropName) const { const auto airport = JsonLdDocument::readProperty(flight, airportPropName); const auto iataCode = JsonLdDocument::readProperty(airport, "iataCode").toString(); if (iataCode.isEmpty()) { return; } auto dt = JsonLdDocument::readProperty(flight, timePropName).toDateTime(); if (!dt.isValid() || dt.timeSpec() == Qt::TimeZone) { return; } const auto tz = AirportDb::timezoneForAirport(AirportDb::IataCode{iataCode}); if (!tz.isValid()) { return; } // prefer our timezone over externally provided UTC offset, if they match if (dt.timeSpec() == Qt::OffsetFromUTC && tz.offsetFromUtc(dt) != dt.offsetFromUtc()) { return; } dt.setTimeSpec(Qt::TimeZone); dt.setTimeZone(tz); // if we updated from UTC offset to timezone spec here, QDateTime will compare equal // and the auto-generated property code will not actually update the property // so, clear the property first to force an update JsonLdDocument::writeProperty(flight, timePropName, QDateTime()); JsonLdDocument::writeProperty(flight, timePropName, dt); } QVariant ExtractorPostprocessorPrivate::processReservation(QVariant res) const { const auto viewUrl = JsonLdDocument::readProperty(res, "url").toUrl(); const auto modUrl = JsonLdDocument::readProperty(res, "modifyReservationUrl").toUrl(); const auto cancelUrl = JsonLdDocument::readProperty(res, "cancelReservationUrl").toUrl(); // remove duplicated urls if (modUrl.isValid() && viewUrl == modUrl) { JsonLdDocument::removeProperty(res, "modifyReservationUrl"); } if (cancelUrl.isValid() && viewUrl == cancelUrl) { JsonLdDocument::removeProperty(res, "cancelReservationUrl"); } return res; } bool ExtractorPostprocessorPrivate::filterReservation(const QVariant &res) const { const auto resFor = JsonLdDocument::readProperty(res, "reservationFor"); if (resFor.isNull()) { return false; } if (resFor.userType() == qMetaTypeId()) { return filterFlight(resFor.value()); } else if (resFor.userType() == qMetaTypeId()) { return filterTrainOrBusTrip(resFor); } else if (resFor.userType() == qMetaTypeId()) { return filterTrainOrBusTrip(resFor); } if (res.userType() == qMetaTypeId()) { - return filterLodgingReservation(res); + return filterLodgingReservation(res.value()); } return true; } -bool ExtractorPostprocessorPrivate::filterLodgingReservation(const QVariant &res) const +bool ExtractorPostprocessorPrivate::filterLodgingReservation(const LodgingReservation &res) const { - const auto checkinDate = JsonLdDocument::readProperty(res, "checkinDate").toDateTime(); - const auto checkoutDate = JsonLdDocument::readProperty(res, "checkoutDate").toDateTime(); - return checkinDate.isValid() && checkoutDate.isValid(); + return res.checkinTime().isValid() && res.checkoutTime().isValid(); } bool ExtractorPostprocessorPrivate::filterFlight(const Flight &flight) const { // this will be valid if either boarding time, departure time or departure day is set const auto validDate = flight.departureDay().isValid(); return filterAirport(flight.departureAirport()) && filterAirport(flight.arrivalAirport()) && validDate; } bool ExtractorPostprocessorPrivate::filterAirport(const Airport &airport) const { return !airport.iataCode().isEmpty() || !airport.name().isEmpty(); } bool ExtractorPostprocessorPrivate::filterTrainOrBusTrip(const QVariant &trip) const { const auto depDt = JsonLdDocument::readProperty(trip, "departureTime").toDateTime(); const auto arrDt = JsonLdDocument::readProperty(trip, "arrivalTime").toDateTime(); return filterTrainOrBusStation(JsonLdDocument::readProperty(trip, "departureStation")) && filterTrainOrBusStation(JsonLdDocument::readProperty(trip, "arrivalStation")) && depDt.isValid() && arrDt.isValid(); } bool ExtractorPostprocessorPrivate::filterTrainOrBusStation(const QVariant &station) const { return !JsonLdDocument::readProperty(station, "name").toString().isEmpty(); } diff --git a/src/jsonldimportfilter.cpp b/src/jsonldimportfilter.cpp index 004105a..2a71b50 100644 --- a/src/jsonldimportfilter.cpp +++ b/src/jsonldimportfilter.cpp @@ -1,70 +1,86 @@ /* Copyright (C) 2018 Volker Krause 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 . */ #include "jsonldimportfilter.h" +#include #include using namespace KItinerary; +static void renameProperty(QJsonObject &obj, const char *oldName, const char *newName) +{ + const auto value = obj.value(QLatin1String(oldName)); + if (!value.isNull() && !obj.contains(QLatin1String(newName))) { + obj.insert(QLatin1String(newName), value); + obj.remove(QLatin1String(oldName)); + } +} + static void filterTrainTrip(QJsonObject &trip) { if (trip.value(QLatin1String("@type")).toString() != QLatin1String("TrainTrip")) { return; } // move TrainTrip::trainCompany to TrainTrip::provider (as defined by schema.org) - const auto company = trip.value(QLatin1String("trainCompany")).toObject(); - if (!company.isEmpty() && !trip.contains(QLatin1String("provider"))) { - trip.insert(QLatin1String("provider"), company); - } + renameProperty(trip, "trainCompany", "provider"); +} + +static void filterLodgingReservation(QJsonObject &res) +{ + // check[in|out]Date -> check[in|out]Time (legacy Google format) + renameProperty(res, "checkinDate", "checkinTime"); + renameProperty(res, "checkoutDate", "checkoutTime"); } static void filterReservation(QJsonObject &res) { // move ticketToken to Ticket (Google vs. schema.org difference) const auto token = res.value(QLatin1String("ticketToken")).toString(); if (!token.isEmpty()) { auto ticket = res.value(QLatin1String("reservedTicket")).toObject(); if (ticket.isEmpty()) { ticket.insert(QLatin1String("@type"), QLatin1String("Ticket")); } if (!ticket.contains(QLatin1String("ticketToken"))) { ticket.insert(QLatin1String("ticketToken"), token); res.insert(QLatin1String("reservedTicket"), ticket); } } } QJsonObject JsonLdImportFilter::filterObject(const QJsonObject& obj) { QJsonObject res(obj); const auto type = obj.value(QLatin1String("@type")).toString(); if (type.endsWith(QLatin1String("Reservation"))) { filterReservation(res); } if (type == QLatin1String("TrainReservation")) { auto train = obj.value(QLatin1String("reservationFor")).toObject(); filterTrainTrip(train); if (!train.isEmpty()) { res.insert(QLatin1String("reservationFor"), train); } + } else if (type == QLatin1String("LodgingReservation")) { + filterLodgingReservation(res); } return res; } diff --git a/src/jsonldimportfilter.h b/src/jsonldimportfilter.h index c64aa83..122db64 100644 --- a/src/jsonldimportfilter.h +++ b/src/jsonldimportfilter.h @@ -1,35 +1,36 @@ /* Copyright (C) 2018 Volker Krause 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 . */ #ifndef KITINERARY_JSONLDIMPORTFILTER_H #define KITINERARY_JSONLDIMPORTFILTER_H class QJsonObject; namespace KItinerary { /** Filter input JSON for loading with JsonLdDocument, to deal with obsolete * or renamed elements. */ namespace JsonLdImportFilter { + /** Filter the top-level object @p obj for loading with JsonLdDocument. */ QJsonObject filterObject(const QJsonObject &obj); } } #endif // KITINERARY_JSONLDIMPORTFILTER_H diff --git a/src/mergeutil.cpp b/src/mergeutil.cpp index 4b010f9..0069819 100644 --- a/src/mergeutil.cpp +++ b/src/mergeutil.cpp @@ -1,149 +1,149 @@ /* Copyright (C) 2018 Volker Krause 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 . */ #include "mergeutil.h" #include #include #include #include #include #include #include #include #include #include using namespace KItinerary; static bool isSameTrainTrip(const TrainTrip &lhs, const TrainTrip &rhs); static bool isSameBusTrip(const BusTrip &lhs, const BusTrip &rhs); static bool isSameLodingBusiness(const LodgingBusiness &lhs, const LodgingBusiness &rhs); bool MergeUtil::isSameReservation(const QVariant& lhs, const QVariant& rhs) { 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()) { const auto lhsRes = lhs.value(); const auto rhsRes = rhs.value(); if (lhsRes.reservationNumber() != rhsRes.reservationNumber() || lhsRes.reservationNumber().isEmpty()) { return false; } const auto lhsFlight = lhsRes.reservationFor().value(); const auto rhsFlight = rhsRes.reservationFor().value(); if (!isSameFlight(lhsFlight, rhsFlight)) { return false; } } // train: booking ref, train number and depature day match if (lhs.userType() == qMetaTypeId()) { const auto lhsRes = lhs.value(); const auto rhsRes = rhs.value(); if (lhsRes.reservationNumber() != rhsRes.reservationNumber()) { return false; } const auto lhsTrip = lhsRes.reservationFor().value(); const auto rhsTrip = rhsRes.reservationFor().value(); if (!isSameTrainTrip(lhsTrip, rhsTrip)) { return false; } } // bus: booking ref, number and depature time match if (lhs.userType() == qMetaTypeId()) { const auto lhsRes = lhs.value(); const auto rhsRes = rhs.value(); if (lhsRes.reservationNumber() != rhsRes.reservationNumber()) { return false; } const auto lhsTrip = lhsRes.reservationFor().value(); const auto rhsTrip = rhsRes.reservationFor().value(); if (!isSameBusTrip(lhsTrip, rhsTrip)) { return false; } } // hotel: booking ref, checkin day, name match if (lhs.userType() == qMetaTypeId()) { const auto lhsRes = lhs.value(); const auto rhsRes = rhs.value(); if (lhsRes.reservationNumber() != rhsRes.reservationNumber()) { return false; } const auto lhsHotel = lhsRes.reservationFor().value(); const auto rhsHotel = rhsRes.reservationFor().value(); - if (!isSameLodingBusiness(lhsHotel, rhsHotel) || lhsRes.checkinDate() != rhsRes.checkinDate()) { + if (!isSameLodingBusiness(lhsHotel, rhsHotel) || lhsRes.checkinTime().date() != rhsRes.checkinTime().date()) { return false; } } // for all: underName either matches or is not set const auto lhsUN = JsonLdDocument::readProperty(lhs, "underName"); const auto rhsUN = JsonLdDocument::readProperty(rhs, "underName"); return lhsUN.isNull() || rhsUN.isNull() || isSamePerson(lhsUN.value(), rhsUN.value()); } bool MergeUtil::isSameFlight(const Flight& lhs, const Flight& rhs) { if (lhs.flightNumber().isEmpty() || rhs.flightNumber().isEmpty()) { return false; } return lhs.flightNumber() == rhs.flightNumber() && lhs.airline().iataCode() == rhs.airline().iataCode() && lhs.departureDay() == rhs.departureDay(); } static bool isSameTrainTrip(const TrainTrip &lhs, const TrainTrip &rhs) { if (lhs.trainNumber().isEmpty() || rhs.trainNumber().isEmpty()) { return false; } return lhs.trainName() == rhs.trainName() && lhs.trainNumber() == rhs.trainNumber() && lhs.departureTime().date() == rhs.departureTime().date(); } static bool isSameBusTrip(const BusTrip &lhs, const BusTrip &rhs) { if (lhs.busNumber().isEmpty() || rhs.busNumber().isEmpty()) { return false; } return lhs.busName() == rhs.busName() && lhs.busNumber() == rhs.busNumber() && lhs.departureTime() == rhs.departureTime(); } static bool isSameLodingBusiness(const LodgingBusiness &lhs, const LodgingBusiness &rhs) { if (lhs.name().isEmpty() || rhs.name().isEmpty()) { return false; } return lhs.name() == rhs.name(); } bool MergeUtil::isSamePerson(const Person& lhs, const Person& rhs) { // TODO: extend this once Person has familiy and given name fields return lhs.name().compare(rhs.name(), Qt::CaseInsensitive) == 0; }