diff --git a/framework/src/domain/eventcontroller.cpp b/framework/src/domain/eventcontroller.cpp index 59d8fa0d..fccd8241 100644 --- a/framework/src/domain/eventcontroller.cpp +++ b/framework/src/domain/eventcontroller.cpp @@ -1,272 +1,272 @@ /* * Copyright (C) 2017 Michael Bohlender, * Copyright (C) 2018 Christian Mollekopf, * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU 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 General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "eventcontroller.h" #include #include #include #include #include #include #include #include "eventoccurrencemodel.h" #include "recepientautocompletionmodel.h" #include "identitiesmodel.h" using namespace Sink::ApplicationDomain; static QPair parseEmailAddress(const QString &email) { KMime::Types::Mailbox mb; mb.fromUnicodeString(email); return {mb.name(), mb.address()}; } static QString assembleEmailAddress(const QString &name, const QString &email) { KMime::Types::Mailbox mb; mb.setName(name); mb.setAddress(email.toUtf8()); return mb.prettyAddress(); } class OrganizerSelector : public Selector { Q_OBJECT public: OrganizerSelector(EventController &controller) : Selector(new IdentitiesModel), mController(controller) { } void setCurrent(const QModelIndex &index) Q_DECL_OVERRIDE { if (index.isValid()) { auto currentAccountId = index.data(IdentitiesModel::AccountId).toByteArray(); const auto email = assembleEmailAddress(index.data(IdentitiesModel::Username).toString(), index.data(IdentitiesModel::Address).toString().toUtf8()); mController.setOrganizer(email); } else { SinkWarning() << "No valid identity for index: " << index; mController.clearOrganizer(); } } private: EventController &mController; }; class AttendeeCompleter : public Completer { public: AttendeeCompleter() : Completer(new RecipientAutocompletionModel) { } void setSearchString(const QString &s) { static_cast(model())->setFilter(s); Completer::setSearchString(s); } }; class AttendeeController : public Kube::ListPropertyController { Q_OBJECT public: AttendeeController() : Kube::ListPropertyController{{"name", "status"}} { } }; EventController::EventController() : Kube::Controller(), controller_attendees{new AttendeeController}, action_save{new Kube::ControllerAction{this, &EventController::save}}, mAttendeeCompleter{new AttendeeCompleter}, mIdentitySelector{new OrganizerSelector{*this}} { updateSaveAction(); } Completer *EventController::attendeeCompleter() const { return mAttendeeCompleter.data(); } Selector *EventController::identitySelector() const { return mIdentitySelector.data(); } void EventController::save() { using namespace Sink; using namespace Sink::ApplicationDomain; const auto calendar = getCalendar(); if (!calendar) { qWarning() << "No calendar selected"; return; } const auto occurrenceVariant = getEventOccurrence(); if (occurrenceVariant.isValid()) { const auto occurrence = occurrenceVariant.value(); Sink::ApplicationDomain::Event event = *occurrence.domainObject; //Apply the changed properties on top of what's existing auto calcoreEvent = KCalCore::ICalFormat().readIncidence(event.getIcal()).dynamicCast(); if(!calcoreEvent) { SinkWarning() << "Invalid ICal to process, ignoring..."; return; } saveToEvent(*calcoreEvent); event.setIcal(KCalCore::ICalFormat().toICalString(calcoreEvent).toUtf8()); event.setCalendar(*calendar); auto job = Store::modify(event) .then([&] (const KAsync::Error &error) { if (error) { SinkWarning() << "Failed to save the event: " << error; } emit done(); }); run(job); } else { Sink::ApplicationDomain::Event event(calendar->resourceInstanceIdentifier()); auto calcoreEvent = QSharedPointer::create(); calcoreEvent->setUid(QUuid::createUuid().toString()); saveToEvent(*calcoreEvent); event.setIcal(KCalCore::ICalFormat().toICalString(calcoreEvent).toUtf8()); event.setCalendar(*calendar); auto job = Store::create(event) .then([&] (const KAsync::Error &error) { if (error) { SinkWarning() << "Failed to save the event: " << error; } emit done(); }); run(job); } } void EventController::updateSaveAction() { saveAction()->setEnabled(!getSummary().isEmpty()); } static EventController::ParticipantStatus toStatus(KCalCore::Attendee::PartStat status) { switch(status) { case KCalCore::Attendee::Accepted: return EventController::Accepted; case KCalCore::Attendee::Declined: return EventController::Declined; case KCalCore::Attendee::NeedsAction: default: break; } return EventController::Unknown; } static KCalCore::Attendee::PartStat fromStatus(EventController::ParticipantStatus status) { switch(status) { case EventController::Accepted: return KCalCore::Attendee::Accepted; case EventController::Declined: return KCalCore::Attendee::Declined; case EventController::Unknown: break; } return KCalCore::Attendee::NeedsAction; } void EventController::populateFromEvent(const KCalCore::Event &event) { setSummary(event.summary()); setDescription(event.description()); setLocation(event.location()); setRecurring(event.recurs()); setAllDay(event.allDay()); setOrganizer(event.organizer()->fullName()); for (const auto &attendee : event.attendees()) { - attendeesController()->add({{"name", attendee->fullName()}, {"status", toStatus(attendee->status())}}); + attendeesController()->add({{"name", attendee->fullName()}, {"email", attendee->email()}, {"status", toStatus(attendee->status())}}); } } void EventController::saveToEvent(KCalCore::Event &event) { event.setSummary(getSummary()); event.setDescription(getDescription()); event.setLocation(getLocation()); event.setDtStart(getStart()); event.setDtEnd(getEnd()); event.setAllDay(getAllDay()); event.setOrganizer(getOrganizer()); event.clearAttendees(); KCalCore::Attendee::List attendees; attendeesController()->traverse([&] (const QVariantMap &map) { bool rsvp = true; KCalCore::Attendee::PartStat status = fromStatus(map["status"].value()); KCalCore::Attendee::Role role = KCalCore::Attendee::ReqParticipant; const auto [name, email] = parseEmailAddress(map["name"].toString()); event.addAttendee(KCalCore::Attendee::Ptr::create(name, email, rsvp, status, role, QString{})); }); } void EventController::init() { using namespace Sink; const auto occurrenceVariant = getEventOccurrence(); if (occurrenceVariant.isValid()) { const auto occurrence = occurrenceVariant.value(); Sink::ApplicationDomain::Event event = *occurrence.domainObject; setCalendar(ApplicationDomainType::Ptr::create(ApplicationDomainType::createEntity(event.resourceInstanceIdentifier(), event.getCalendar()))); auto icalEvent = KCalCore::ICalFormat().readIncidence(event.getIcal()).dynamicCast(); if(!icalEvent) { SinkWarning() << "Invalid ICal to process, ignoring..."; return; } populateFromEvent(*icalEvent); setStart(occurrence.start); setEnd(occurrence.end); } } void EventController::remove() { const auto occurrenceVariant = getEventOccurrence(); if (occurrenceVariant.isValid()) { const auto occurrence = occurrenceVariant.value(); Sink::ApplicationDomain::Event event = *occurrence.domainObject; run(Sink::Store::remove(event)); } } #include "eventcontroller.moc" diff --git a/framework/src/domain/invitationcontroller.cpp b/framework/src/domain/invitationcontroller.cpp index 9d1e27ef..172efd0d 100644 --- a/framework/src/domain/invitationcontroller.cpp +++ b/framework/src/domain/invitationcontroller.cpp @@ -1,196 +1,198 @@ /* * Copyright (C) 2017 Michael Bohlender, * Copyright (C) 2018 Christian Mollekopf, * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU 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 General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "invitationcontroller.h" #include #include #include #include #include #include #include #include "mailtemplates.h" #include "sinkutils.h" using namespace Sink::ApplicationDomain; InvitationController::InvitationController() : EventController(), action_accept{new Kube::ControllerAction{this, &InvitationController::accept}}, action_decline{new Kube::ControllerAction{this, &InvitationController::decline}} { } void InvitationController::loadICal(const QString &ical) { using namespace Sink; using namespace Sink::ApplicationDomain; KCalCore::Calendar::Ptr calendar(new KCalCore::MemoryCalendar{QTimeZone::systemTimeZone()}); auto msg = KCalCore::ICalFormat{}.parseScheduleMessage(calendar, ical.toUtf8()); if (!msg) { SinkWarning() << "Invalid scheudle message to process, ignoring..."; return; } auto icalEvent = msg->event().dynamicCast(); if (msg->method() != KCalCore::iTIPRequest) { SinkWarning() << "Invalid method " << msg->method(); return; } if(!icalEvent) { SinkWarning() << "Invalid ICal to process, ignoring..."; return; } Query query; query.request(); query.request(); query.filter(icalEvent->uid().toUtf8()); Store::fetchAll(query).then([this, icalEvent](const QList &events) { if (!events.isEmpty()) { setState(InvitationState::Accepted); auto icalEvent = KCalCore::ICalFormat().readIncidence(events.first()->getIcal()).dynamicCast(); if(!icalEvent) { SinkWarning() << "Invalid ICal to process, ignoring..."; return; } populateFromEvent(*icalEvent); setStart(icalEvent->dtStart()); setEnd(icalEvent->dtEnd()); setUid(icalEvent->uid().toUtf8()); } else { setState(InvitationState::Unknown); populateFromEvent(*icalEvent); setStart(icalEvent->dtStart()); setEnd(icalEvent->dtEnd()); setUid(icalEvent->uid().toUtf8()); } }).exec(); } void sendIMipMessage(const QByteArray &accountId, const QString &from, KCalCore::Event::Ptr event) { const auto organizerEmail = event->organizer()->fullName(); if (organizerEmail.isEmpty()) { qWarning() << "Failed to find the organizer to send the reply to " << organizerEmail; return; } QString body = "The invitation has been accepted"; auto msg = MailTemplates::createIMipMessage( from, {{organizerEmail}, {}, {}}, QString("\"%1\" has been accepted by %2").arg(event->summary()).arg("Meeeee"), body, KCalCore::ICalFormat{}.createScheduleMessage(event, KCalCore::iTIPReply)); qWarning() << "Msg " << msg->encodedContent(); SinkUtils::sendMail(msg->encodedContent(true), accountId) .then([&] (const KAsync::Error &error) { if (error) { SinkWarning() << "Failed to send message " << error; } }).exec(); } void InvitationController::storeEvent(InvitationState status) { using namespace Sink; using namespace Sink::ApplicationDomain; const auto calendar = getCalendar(); if (!calendar) { qWarning() << "No calendar selected"; return; } Query query; query.request() .request() .request(); auto job = Store::fetchAll(query) .then([=] (const QList &list) { if (list.isEmpty()) { qWarning() << "Failed to find an identity"; } QString fromAddress; QByteArray accountId; bool foundMatch = false; for (const auto &identity : list) { const auto id = attendeesController()->findByProperty("email", identity->getAddress()); if (!id.isEmpty()) { if (status == InvitationController::Accepted) { attendeesController()->setValue(id, "status", EventController::Accepted); } else { attendeesController()->setValue(id, "status", EventController::Declined); } fromAddress = identity->getAddress(); accountId = identity->getAccount(); foundMatch = true; + } else { + SinkLog() << "No identity found for " << identity->getAddress(); } } if (!foundMatch) { - qWarning() << "Failed to find a matching identity"; + qWarning() << "Failed to find a matching identity."; return KAsync::error("Failed to find a matching identity"); } auto calcoreEvent = QSharedPointer::create(); calcoreEvent->setUid(getUid()); saveToEvent(*calcoreEvent); sendIMipMessage(accountId, fromAddress, calcoreEvent); Event event(calendar->resourceInstanceIdentifier()); event.setIcal(KCalCore::ICalFormat().toICalString(calcoreEvent).toUtf8()); event.setCalendar(*calendar); return Store::create(event) .then([=] (const KAsync::Error &error) { if (error) { SinkWarning() << "Failed to save the event: " << error; } setState(status); emit done(); }); }); run(job); } void InvitationController::accept() { storeEvent(InvitationState::Accepted); } void InvitationController::decline() { storeEvent(InvitationState::Declined); }