diff --git a/plugins/messageviewer/bodypartformatter/semantic/semanticmemento.cpp b/plugins/messageviewer/bodypartformatter/semantic/semanticmemento.cpp index 5e7b405d..ddbb42eb 100644 --- a/plugins/messageviewer/bodypartformatter/semantic/semanticmemento.cpp +++ b/plugins/messageviewer/bodypartformatter/semantic/semanticmemento.cpp @@ -1,115 +1,141 @@ /* 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 "semanticmemento.h" #include #include #include +#include +#include #include #include using namespace KItinerary; void SemanticMemento::detach() { } bool SemanticMemento::isParsed(const KMime::ContentIndex &index) const { return m_parsedParts.contains(index); } void SemanticMemento::setParsed(const KMime::ContentIndex &index) { m_parsedParts.insert(index); } void SemanticMemento::setMessageDate(const QDateTime &contextDt) { m_postProc.setContextDate(contextDt); } void SemanticMemento::appendStructuredData(const QVector &data) { m_pendingStructuredData.append(data); } void SemanticMemento::appendUnstructuredData(const QVector &data) { m_postProc.process(data); } bool SemanticMemento::hasData() const { return !m_data.isEmpty() || !m_pendingStructuredData.isEmpty() || !m_postProc.result().isEmpty(); } -QVector SemanticMemento::data() +QVector SemanticMemento::data() { if (!m_pendingStructuredData.isEmpty()) { m_postProc.process(m_pendingStructuredData); m_pendingStructuredData.clear(); } if (m_data.isEmpty() && !m_postProc.result().isEmpty()) { + // perform calendar lookup and merge results + std::vector> resolvedEvents; + resolvedEvents.reserve(m_postProc.result().size()); const auto calendar = CalendarSupport::calendarSingleton(true); for (const auto &r : m_postProc.result()) { - ReservationData data; - data.res = r; - data.expanded = false; + auto e = std::make_pair(r, CalendarHandler::findEvent(calendar, r)); + if (e.second) { + const auto existingRes = CalendarHandler::reservationsForEvent(e.second); + for (const auto &ev : existingRes) { + if (MergeUtil::isSame(ev, e.first)) { + e.first = JsonLdDocument::apply(ev, e.first); + break; + } + } + } + resolvedEvents.push_back(e); + } - data.event = CalendarHandler::findEvent(calendar, data.res); - if (data.event) { - const auto existingRes = CalendarHandler::reservationForEvent(data.event); - data.res = JsonLdDocument::apply(existingRes, data.res); + // merge multi-traveler elements + for (auto it = resolvedEvents.begin(); it != resolvedEvents.end();) { + TripData data; + data.reservations.push_back((*it).first); + data.event = (*it).second; + data.expanded = false; + ++it; + + if (JsonLd::canConvert(data.reservations.at(0))) { + const auto trip = JsonLd::convert(data.reservations.at(0)).reservationFor(); + for (; it != resolvedEvents.end(); ++it) { + if (!JsonLd::canConvert((*it).first) || !MergeUtil::isSame(JsonLd::convert((*it).first).reservationFor(), trip)) { + break; + } + data.reservations.push_back((*it).first); + } } m_data.push_back(data); } } return m_data; } void SemanticMemento::toggleExpanded(int index) { if (index >= m_data.size()) { return; } m_data[index].expanded = !m_data.at(index).expanded; } void SemanticMemento::addPass(KPkPass::Pass *pass, const QByteArray &rawData) { m_passes.emplace_back(PassData{pass->passTypeIdentifier(), pass->serialNumber(), rawData}); } QByteArray SemanticMemento::rawPassData(const QString &passTypeIdentifier, const QString &serialNumber) const { for (const auto &pass : m_passes) { if (pass.passTypeIdentifier == passTypeIdentifier && pass.serialNumber == serialNumber) { return pass.rawData; } } return {}; } diff --git a/plugins/messageviewer/bodypartformatter/semantic/semanticmemento.h b/plugins/messageviewer/bodypartformatter/semantic/semanticmemento.h index 00272fae..9a229242 100644 --- a/plugins/messageviewer/bodypartformatter/semantic/semanticmemento.h +++ b/plugins/messageviewer/bodypartformatter/semantic/semanticmemento.h @@ -1,84 +1,84 @@ /* 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 SEMANTICMEMENTO_H #define SEMANTICMEMENTO_H #include #include #include #include #include #include #include namespace KPkPass { class Pass; } namespace KMime { class ContentIndex; } class QDateTime; /** Memento holding the semantic information extracted for an email. */ class SemanticMemento : public MimeTreeParser::Interface::BodyPartMemento { public: ~SemanticMemento() override = default; void detach() override; bool isParsed(const KMime::ContentIndex &index) const; void setParsed(const KMime::ContentIndex &index); void setMessageDate(const QDateTime &contextDt); void appendUnstructuredData(const QVector &data); void appendStructuredData(const QVector &data); bool hasData() const; - struct ReservationData { - QVariant res; + struct TripData { + QVector reservations; KCalCore::Event::Ptr event; bool expanded; }; - QVector data(); + QVector data(); void toggleExpanded(int index); void addPass(KPkPass::Pass *pass, const QByteArray &rawData); QByteArray rawPassData(const QString &passTypeIdentifier, const QString &serialNumber) const; private: QVector m_pendingStructuredData; QSet m_parsedParts; KItinerary::ExtractorPostprocessor m_postProc; - QVector m_data; + QVector m_data; struct PassData { QString passTypeIdentifier; QString serialNumber; QByteArray rawData; }; std::vector m_passes; }; #endif // SEMANTICMEMENTO_H diff --git a/plugins/messageviewer/bodypartformatter/semantic/semanticrenderer.cpp b/plugins/messageviewer/bodypartformatter/semantic/semanticrenderer.cpp index f0723999..73f6f842 100644 --- a/plugins/messageviewer/bodypartformatter/semantic/semanticrenderer.cpp +++ b/plugins/messageviewer/bodypartformatter/semantic/semanticrenderer.cpp @@ -1,207 +1,194 @@ /* 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 "semanticrenderer.h" #include "semanticmemento.h" #include "semantic_debug.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace KItinerary; // Grantlee has no Q_GADGET support yet #define GRANTLEE_MAKE_GADGET(Class) \ GRANTLEE_BEGIN_LOOKUP(Class) \ const auto idx = Class::staticMetaObject.indexOfProperty(property.toUtf8().constData()); \ if (idx < 0) { \ return {};} \ const auto mp = Class::staticMetaObject.property(idx); \ return mp.readOnGadget(&object); \ GRANTLEE_END_LOOKUP GRANTLEE_MAKE_GADGET(Airport) GRANTLEE_MAKE_GADGET(Airline) GRANTLEE_MAKE_GADGET(Flight) GRANTLEE_MAKE_GADGET(FlightReservation) GRANTLEE_MAKE_GADGET(LodgingBusiness) GRANTLEE_MAKE_GADGET(LodgingReservation) GRANTLEE_MAKE_GADGET(Person) GRANTLEE_MAKE_GADGET(PostalAddress) GRANTLEE_MAKE_GADGET(Seat) GRANTLEE_MAKE_GADGET(Ticket) GRANTLEE_MAKE_GADGET(TrainStation) GRANTLEE_MAKE_GADGET(TrainTrip) GRANTLEE_MAKE_GADGET(TrainReservation) GRANTLEE_MAKE_GADGET(BusStation) GRANTLEE_MAKE_GADGET(BusTrip) GRANTLEE_MAKE_GADGET(BusReservation) GRANTLEE_MAKE_GADGET(CancelAction) GRANTLEE_MAKE_GADGET(CheckInAction) GRANTLEE_MAKE_GADGET(DownloadAction) GRANTLEE_MAKE_GADGET(UpdateAction) GRANTLEE_MAKE_GADGET(ViewAction) SemanticRenderer::SemanticRenderer() { Grantlee::registerMetaType(); Grantlee::registerMetaType(); Grantlee::registerMetaType(); Grantlee::registerMetaType(); Grantlee::registerMetaType(); Grantlee::registerMetaType(); Grantlee::registerMetaType(); Grantlee::registerMetaType(); Grantlee::registerMetaType(); Grantlee::registerMetaType(); Grantlee::registerMetaType(); Grantlee::registerMetaType(); Grantlee::registerMetaType(); Grantlee::registerMetaType(); Grantlee::registerMetaType(); Grantlee::registerMetaType(); Grantlee::registerMetaType(); Grantlee::registerMetaType(); Grantlee::registerMetaType(); Grantlee::registerMetaType(); Grantlee::registerMetaType(); } bool SemanticRenderer::render(const MimeTreeParser::MessagePartPtr &msgPart, MessageViewer::HtmlWriter *htmlWriter, MessageViewer::RenderContext *context) const { Q_UNUSED(context); const auto mpList = msgPart.dynamicCast(); if (!msgPart->isRoot() || !mpList->hasSubParts()) { return false; } const auto node = mpList->subParts().at(0)->content(); const auto nodeHelper = msgPart->nodeHelper(); if (!nodeHelper || !node) { return false; } auto memento = dynamic_cast(nodeHelper->bodyPartMemento(node->topLevel(), "org.kde.messageviewer.semanticData")); if (!memento || !memento->hasData()) { return false; } const auto extractedData = memento->data(); if (extractedData.isEmpty()) { // hasData() will not be correct for filtered structured data on the first pass through here... return false; } const auto dir = nodeHelper->createTempDir(QStringLiteral("semantic")); auto c = MessageViewer::MessagePartRendererManager::self()->createContext(); const auto pal = qGuiApp->palette(); QVariantMap style; style.insert(QStringLiteral("frameColor"), pal.link().color().name()); style.insert(QStringLiteral("expandIcon"), MessageViewer::IconNameCache::instance()->iconPathFromLocal(QStringLiteral("quoteexpand.png"))); style.insert(QStringLiteral("collapseIcon"), MessageViewer::IconNameCache::instance()->iconPathFromLocal(QStringLiteral("quotecollapse.png"))); c.insert(QStringLiteral("style"), style); // Grantlee can't do indexed map/array lookups, so we need to interleave this here already QVariantList elems; elems.reserve(extractedData.size()); - for (int i = 0; i < extractedData.size();) { + for (int i = 0; i < extractedData.size(); ++i) { QVariantMap data; QVariantMap state; const auto d = extractedData.at(i); state.insert(QStringLiteral("expanded"), d.expanded); data.insert(QStringLiteral("state"), state); data.insert(QStringLiteral("groupId"), i); QVector reservations; - if (JsonLd::canConvert(d.res)) { - const auto trip = JsonLd::convert(d.res).reservationFor(); - // merge multi-traveler elements - for (; i < extractedData.size(); ++i) { - const auto d = extractedData.at(i); - if (!JsonLd::canConvert(d.res) || !MergeUtil::isSame(JsonLd::convert(d.res).reservationFor(), trip)) { - break; - } - QVariantMap m; - m.insert(QStringLiteral("reservation"), d.res); - - // generate ticket barcodes - const auto ticket = JsonLd::convert(d.res).reservedTicket().value(); - std::unique_ptr barcode; - switch (ticket.ticketTokenType()) { - case Ticket::AztecCode: - barcode.reset(Prison::createBarcode(Prison::Aztec)); - barcode->setData(ticket.ticketTokenData()); - break; - case Ticket::QRCode: - barcode.reset(Prison::createBarcode(Prison::QRCode)); - barcode->setData(ticket.ticketTokenData()); - break; - default: - break; - } - if (barcode) { - barcode->toImage(barcode->minimumSize()); // minimumSize is only available after we rendered once... - const auto img = barcode->toImage(barcode->minimumSize()); - const auto fileName = dir + QStringLiteral("/ticketToken") + QString::number(i) + QStringLiteral(".png"); - img.save(fileName); - m.insert(QStringLiteral("ticketToken"), fileName); - nodeHelper->addTempFile(fileName); - } - - reservations.push_back(m); - } - } else { + for (const auto &r : d.reservations) { QVariantMap m; - m.insert(QStringLiteral("reservation"), d.res); + m.insert(QStringLiteral("reservation"), r); + + // generate ticket barcodes + const auto ticket = JsonLd::convert(r).reservedTicket().value(); + std::unique_ptr barcode; + switch (ticket.ticketTokenType()) { + case Ticket::AztecCode: + barcode.reset(Prison::createBarcode(Prison::Aztec)); + barcode->setData(ticket.ticketTokenData()); + break; + case Ticket::QRCode: + barcode.reset(Prison::createBarcode(Prison::QRCode)); + barcode->setData(ticket.ticketTokenData()); + break; + default: + break; + } + if (barcode) { + barcode->toImage(barcode->minimumSize()); // minimumSize is only available after we rendered once... + const auto img = barcode->toImage(barcode->minimumSize()); + const auto fileName = dir + QStringLiteral("/ticketToken") + QString::number(i) + QStringLiteral(".png"); + img.save(fileName); + m.insert(QStringLiteral("ticketToken"), fileName); + nodeHelper->addTempFile(fileName); + } + reservations.push_back(m); - ++i; } data.insert(QStringLiteral("reservations"), QVariant::fromValue(reservations)); elems.push_back(data); } c.insert(QStringLiteral("data"), elems); auto t = MessageViewer::MessagePartRendererManager::self()->loadByName(QStringLiteral(":/org.kde.messageviewer/semantic/semantic.html")); const_cast(t->engine())->addDefaultLibrary(QStringLiteral("kitinerary_grantlee_extension")); Grantlee::OutputStream s(htmlWriter->stream()); t->render(&s, &c); return false; // yes, false, we want the rest of the email rendered normally after this } diff --git a/plugins/messageviewer/bodypartformatter/semantic/semanticurlhandler.cpp b/plugins/messageviewer/bodypartformatter/semantic/semanticurlhandler.cpp index 359b82af..c8772504 100644 --- a/plugins/messageviewer/bodypartformatter/semantic/semanticurlhandler.cpp +++ b/plugins/messageviewer/bodypartformatter/semantic/semanticurlhandler.cpp @@ -1,396 +1,399 @@ /* 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 "semanticurlhandler.h" #include "semanticmemento.h" #include "semantic_debug.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace KItinerary; SemanticUrlHandler::SemanticUrlHandler() { m_appPath = QStandardPaths::findExecutable(QStringLiteral("itinerary")); } bool SemanticUrlHandler::handleClick(MessageViewer::Viewer *viewerInstance, MimeTreeParser::Interface::BodyPart *part, const QString &path) const { Q_UNUSED(viewerInstance); if (path == QLatin1String("semanticAction")) { return true; } if (path.startsWith(QLatin1String("semanticExpand?"))) { auto idx = path.midRef(15).toInt(); auto m = memento(part); m->toggleExpanded(idx); const auto nodeHelper = part->nodeHelper(); emit nodeHelper->update(MimeTreeParser::Delayed); return true; } return false; } template static QString placeName(const T &place) { return place.name().replace(QLatin1Char('&'), QLatin1String("&&")); // avoid & being turned into an action accelerator; } template <> QString placeName(const Airport &place) { if (place.name().isEmpty()) { return place.iataCode(); } return place.name().replace(QLatin1Char('&'), QLatin1String("&&")); // avoid & being turned into an action accelerator; } static void addGoToMapAction(QMenu *menu, const GeoCoordinates &geo, const QString &placeName, int zoom = 17) { if (geo.isValid()) { auto action = menu->addAction(QIcon::fromTheme(QStringLiteral("map-symbolic")), i18n("Show \'%1\' On Map", placeName)); QObject::connect(action, &QAction::triggered, menu, [geo, zoom]() { QUrl url; url.setScheme(QStringLiteral("https")); url.setHost(QStringLiteral("www.openstreetmap.org")); url.setPath(QStringLiteral("/")); const QString fragment = QLatin1String("map=") + QString::number(zoom) + QLatin1Char('/') + QString::number(geo.latitude()) + QLatin1Char('/') + QString::number(geo.longitude()); url.setFragment(fragment); QDesktopServices::openUrl(url); }); } } static void addGoToMapAction(QMenu *menu, const PostalAddress &addr, const QString &placeName) { if (!addr.addressLocality().isEmpty()) { auto action = menu->addAction(QIcon::fromTheme(QStringLiteral("map-symbolic")), i18n("Show \'%1\' On Map", placeName)); QObject::connect(action, &QAction::triggered, menu, [addr]() { QUrl url; url.setScheme(QStringLiteral("https")); url.setHost(QStringLiteral("www.openstreetmap.org")); url.setPath(QStringLiteral("/search")); const QString queryString = addr.streetAddress() + QLatin1String(", ") + addr.postalCode() + QLatin1Char(' ') + addr.addressLocality() + QLatin1String(", ") + addr.addressCountry(); QUrlQuery query; query.addQueryItem(QStringLiteral("query"), queryString); url.setQuery(query); QDesktopServices::openUrl(url); }); } } template static void addGoToMapAction(QMenu *menu, const T &place) { const auto name = placeName(place); // zoom out further from airports, they are larger and you usually want to go further away from them addGoToMapAction(menu, place.geo(), name, std::is_same::value ? 12 : 17); if (!place.geo().isValid()) { addGoToMapAction(menu, place.address(), name); } } static bool canAddToCalendar(SemanticMemento *m) { for (const auto &d : m->data()) { - if (JsonLd::isA(d.res)) { - const auto f = d.res.value().reservationFor().value(); + if (JsonLd::isA(d.reservations.at(0))) { + const auto f = d.reservations.at(0).value().reservationFor().value(); if (f.departureTime().isValid() && f.arrivalTime().isValid()) { return true; } continue; - } else if (SortUtil::startDateTime(d.res).isValid()) { + } else if (SortUtil::startDateTime(d.reservations.at(0)).isValid()) { return true; } } return false; } bool SemanticUrlHandler::handleContextMenuRequest(MimeTreeParser::Interface::BodyPart *part, const QString &path, const QPoint &p) const { Q_UNUSED(part); if (path != QLatin1String("semanticAction")) { return false; } const auto m = memento(part); if (!m || !m->hasData()) { return false; } const auto date = dateForReservation(m); QMenu menu; QAction *action = nullptr; if (date.isValid()) { action = menu.addAction(QIcon::fromTheme(QStringLiteral("view-calendar")), i18n("Show Calendar")); QObject::connect(action, &QAction::triggered, this, [this, date](){ showCalendar(date); }); } action = menu.addAction(QIcon::fromTheme(QStringLiteral("appointment-new")), i18n("Add To Calendar")); action->setEnabled(canAddToCalendar(m)); QObject::connect(action, &QAction::triggered, this, [this, m](){ addToCalendar(m); }); QSet places; for (const auto &d : m->data()) { - if (JsonLd::isA(d.res)) { - addGoToMapAction(&menu, d.res.value().reservationFor().value()); - } else if (JsonLd::isA(d.res)) { - const auto flight = d.res.value().reservationFor().value(); + const auto res = d.reservations.at(0); // for multi-traveler reservations all subsequent ones are equal regarding what we are interested here + if (JsonLd::isA(res)) { + addGoToMapAction(&menu, res.value().reservationFor().value()); + } else if (JsonLd::isA(res)) { + const auto flight = res.value().reservationFor().value(); auto airport = flight.departureAirport(); if (!places.contains(airport.iataCode())) { addGoToMapAction(&menu, airport); places.insert(airport.iataCode()); } airport = flight.arrivalAirport(); if (!places.contains(airport.iataCode())) { addGoToMapAction(&menu, airport); places.insert(airport.iataCode()); } - } else if (JsonLd::isA(d.res)) { - const auto trip = d.res.value().reservationFor().value(); + } else if (JsonLd::isA(res)) { + const auto trip = res.value().reservationFor().value(); if (!places.contains(trip.departureStation().name())) { addGoToMapAction(&menu, trip.departureStation()); places.insert(trip.departureStation().name()); } if (!places.contains(trip.arrivalStation().name())) { addGoToMapAction(&menu, trip.arrivalStation()); places.insert(trip.arrivalStation().name()); } - } else if (JsonLd::isA(d.res)) { - const auto trip = d.res.value().reservationFor().value(); + } else if (JsonLd::isA(res)) { + const auto trip = res.value().reservationFor().value(); if (!places.contains(trip.departureStation().name())) { addGoToMapAction(&menu, trip.departureStation()); places.insert(trip.departureStation().name()); } if (!places.contains(trip.arrivalStation().name())) { addGoToMapAction(&menu, trip.arrivalStation()); places.insert(trip.arrivalStation().name()); } } } if (!m_appPath.isEmpty()) { action = menu.addAction(QIcon::fromTheme(QStringLiteral("map-globe")), i18n("Import into KDE Itinerary")); QObject::connect(action, &QAction::triggered, this, [this, part]() { openInApp(part); }); } menu.exec(p); return true; } QString SemanticUrlHandler::statusBarMessage(MimeTreeParser::Interface::BodyPart *part, const QString &path) const { Q_UNUSED(part); if (path == QLatin1String("semanticAction")) { return i18n("Add reservation to your calendar."); } return {}; } SemanticMemento *SemanticUrlHandler::memento(MimeTreeParser::Interface::BodyPart *part) const { const auto node = part->content()->topLevel(); const auto nodeHelper = part->nodeHelper(); if (!nodeHelper || !node) { return nullptr; } return dynamic_cast(nodeHelper->bodyPartMemento(node->topLevel(), "org.kde.messageviewer.semanticData")); } QDate SemanticUrlHandler::dateForReservation(SemanticMemento *memento) const { for (const auto &d : memento->data()) { - const auto dt = SortUtil::startDateTime(d.res); + const auto dt = SortUtil::startDateTime(d.reservations.at(0)); if (dt.isValid()) { return dt.date(); } } return {}; } void SemanticUrlHandler::showCalendar(const QDate &date) const { // ensure KOrganizer or Kontact are running QString error, dbusService; const auto result = KDBusServiceStarter::self()->findServiceFor(QStringLiteral("DBUS/Organizer"), {}, &error, &dbusService) == 0; if (!result) { qCWarning(SEMANTIC_LOG) << "Failed to start KOrganizer" << error << dbusService; } // switch to KOrganizer if we are using Kontact std::unique_ptr kontactIface( new QDBusInterface(QStringLiteral("org.kde.kontact"), QStringLiteral("/KontactInterface"), QStringLiteral("org.kde.kontact.KontactInterface"), QDBusConnection::sessionBus())); if (kontactIface->isValid()) { kontactIface->call(QStringLiteral("selectPlugin"), QStringLiteral("kontact_korganizerplugin")); } // select the date of the reservation std::unique_ptr korgIface( new QDBusInterface(QStringLiteral("org.kde.korganizer"), QStringLiteral("/Calendar"), QStringLiteral("org.kde.Korganizer.Calendar"), QDBusConnection::sessionBus())); if (!korgIface->isValid()) { qCWarning(SEMANTIC_LOG) << "Calendar interface is not valid! " << korgIface->lastError().message(); return; } korgIface->call(QStringLiteral("showEventView")); korgIface->call(QStringLiteral("showDate"), date); } -static void attachPass(const KCalCore::Event::Ptr &event, const QVariant &reservation, SemanticMemento *memento) +static void attachPass(const KCalCore::Event::Ptr &event, const QVector &reservations, SemanticMemento *memento) { - if (!JsonLd::canConvert(reservation)) { - return; - } + for (const auto &reservation : reservations) { + if (!JsonLd::canConvert(reservation)) { + return; + } - const auto res = JsonLd::convert(reservation); - const auto data = memento->rawPassData(res.pkpassPassTypeIdentifier(), res.pkpassSerialNumber()); - if (data.isEmpty()) { - return; - } + const auto res = JsonLd::convert(reservation); + const auto data = memento->rawPassData(res.pkpassPassTypeIdentifier(), res.pkpassSerialNumber()); + if (data.isEmpty()) { + return; + } - event->deleteAttachments(QStringLiteral("application/vnd.apple.pkpass")); - using namespace KCalCore; - Attachment::Ptr att(new Attachment(data.toBase64(), QStringLiteral("application/vnd.apple.pkpass"))); - att->setLabel(i18n("Boarding Pass")); - event->addAttachment(att); + event->deleteAttachments(QStringLiteral("application/vnd.apple.pkpass")); + using namespace KCalCore; + Attachment::Ptr att(new Attachment(data.toBase64(), QStringLiteral("application/vnd.apple.pkpass"))); + att->setLabel(i18n("Boarding Pass")); // TODO add passenger name after string freeze is lifted + event->addAttachment(att); + } } void SemanticUrlHandler::addToCalendar(SemanticMemento *memento) const { using namespace KCalCore; const auto calendar = CalendarSupport::calendarSingleton(true); for (const auto &d : memento->data()) { auto event = d.event; if (!event) { event.reset(new Event); - CalendarHandler::fillEvent(d.res, event); + CalendarHandler::fillEvent(d.reservations, event); if (!event->dtStart().isValid() || !event->dtEnd().isValid() || event->summary().isEmpty()) { continue; } - attachPass(event, d.res, memento); + attachPass(event, d.reservations, memento); calendar->addEvent(event); } else { event->startUpdates(); - CalendarHandler::fillEvent(d.res, event); + CalendarHandler::fillEvent(d.reservations, event); event->endUpdates(); - attachPass(event, d.res, memento); + attachPass(event, d.reservations, memento); calendar->modifyIncidence(event); } } } void SemanticUrlHandler::openInApp(MimeTreeParser::Interface::BodyPart *part) const { QTemporaryFile f(QStringLiteral("itinerary.XXXXXX.jsonld")); if (!f.open()) { qCWarning(SEMANTIC_LOG) << "Failed to open temporary file:" << f.errorString(); return; } const auto m = memento(part); const auto extractedData = m->data(); QVector data; - data.resize(extractedData.size()); + data.reserve(extractedData.size()); for (const auto &d : m->data()) { - data.push_back(d.res); + data += d.reservations; } f.write(QJsonDocument(JsonLdDocument::toJson(data)).toJson()); f.close(); part->nodeHelper()->addTempFile(f.fileName()); f.setAutoRemove(false); QStringList args(f.fileName()); // add pkpass attachments for (const auto &elem : data) { if (!JsonLd::canConvert(elem)) { continue; } const auto res = JsonLd::convert(elem); const auto b = m->rawPassData(res.pkpassPassTypeIdentifier(), res.pkpassSerialNumber()); if (b.isEmpty()) { continue; } QTemporaryFile f(QStringLiteral("itinerary.XXXXXX.pkpass")); if (!f.open()) { qCWarning(SEMANTIC_LOG) << "Failed to open temporary file:" << f.errorString(); return; } f.write(b); part->nodeHelper()->addTempFile(f.fileName()); f.setAutoRemove(false); args.push_back(f.fileName()); } QProcess::startDetached(m_appPath, args); }