Akonadi Calendar

itiphandler.cpp
1/*
2 SPDX-FileCopyrightText: 2002-2004 Klarälvdalens Datakonsult AB,
3 <info@klaralvdalens-datakonsult.se>
4
5 SPDX-FileCopyrightText: 2010 Bertjan Broeksema <broeksema@kde.org>
6 SPDX-FileCopyrightText: 2010 Klaralvdalens Datakonsult AB, a KDAB Group company <info@kdab.net>
7
8 SPDX-FileCopyrightText: 2012 SĂ©rgio Martins <iamsergio@gmail.com>
9
10 SPDX-License-Identifier: LGPL-2.0-or-later
11*/
12
13#include "itiphandler.h"
14#include "calendarsettings.h"
15#include "itiphandler_p.h"
16#include "itiphandlerhelper_p.h"
17#include "mailclient_p.h"
18#include "publishdialog.h"
19#include "utils_p.h"
20
21#include <KCalUtils/Stringify>
22#include <KCalendarCore/Attendee>
23#include <KCalendarCore/ICalFormat>
24
25#include <Akonadi/MessageQueueJob>
26#include <KIdentityManagementCore/IdentityManager>
27
28#include "akonadicalendar_debug.h"
29#include <KMessageBox>
30
31using namespace Akonadi;
32
33// async emittion
34static void emitiTipMessageProcessed(ITIPHandler *handler, ITIPHandler::Result resultCode, const QString &errorString)
35{
37 "iTipMessageProcessed",
39 Q_ARG(Akonadi::ITIPHandler::Result, resultCode),
40 Q_ARG(QString, errorString));
41}
42
43GroupwareUiDelegate::GroupwareUiDelegate(QObject *parent)
44 : QObject(parent)
45{
46}
47
48GroupwareUiDelegate::~GroupwareUiDelegate() = default;
49
50ITIPHandlerComponentFactory::ITIPHandlerComponentFactory(QObject *parent)
51 : QObject(parent)
52{
53}
54
55ITIPHandlerComponentFactory::~ITIPHandlerComponentFactory() = default;
56
57Akonadi::MessageQueueJob *ITIPHandlerComponentFactory::createMessageQueueJob(const KCalendarCore::IncidenceBase::Ptr &incidence,
59 QObject *parent)
60{
61 Q_UNUSED(incidence)
62 Q_UNUSED(identity)
64}
65
67ITIPHandlerComponentFactory::createITIPHanderDialogDelegate(const KCalendarCore::Incidence::Ptr &incidence, KCalendarCore::iTIPMethod method, QWidget *parent)
68{
69 return new ITIPHandlerDialogDelegate(incidence, method, parent);
70}
71
73 : QObject(parent)
74 , d(new ITIPHandlerPrivate(/*factory=*/nullptr, this))
75{
76 qRegisterMetaType<Akonadi::ITIPHandler::Result>("Akonadi::ITIPHandler::Result");
77}
78
80 : QObject(parent)
81 , d(new ITIPHandlerPrivate(factory, this))
82{
83 qRegisterMetaType<Akonadi::ITIPHandler::Result>("Akonadi::ITIPHandler::Result");
84}
85
87
88void ITIPHandler::processiTIPMessage(const QString &receiver, const QString &iCal, const QString &action)
89{
90 qCDebug(AKONADICALENDAR_LOG) << "processiTIPMessage called with receiver=" << receiver << "; action=" << action;
91
92 if (d->m_currentOperation != OperationNone) {
93 d->m_currentOperation = OperationNone;
94 qCritical() << "There can't be an operation in progress!" << d->m_currentOperation;
95 return;
96 }
97
98 d->m_currentOperation = OperationProcessiTIPMessage;
99
100 if (!d->isLoaded()) {
101 d->m_queuedInvitation.receiver = receiver;
102 d->m_queuedInvitation.iCal = iCal;
103 d->m_queuedInvitation.action = action;
104 return;
105 }
106
107 if (d->m_calendarLoadError) {
108 d->m_currentOperation = OperationNone;
109 qCritical() << "Error loading calendar";
110 emitiTipMessageProcessed(this, ResultError, i18n("Error loading calendar."));
111 return;
112 }
113
115 KCalendarCore::ScheduleMessage::Ptr message = format.parseScheduleMessage(d->calendar(), iCal);
116
117 if (!message) {
118 const QString errorMessage = format.exception() ? i18n("Error message: %1", KCalUtils::Stringify::errorMessage(*format.exception()))
119 : i18n("Unknown error while parsing iCal invitation");
120
121 qCritical() << "Error parsing" << errorMessage;
122
123 if (d->m_showDialogsOnError) {
124 KMessageBox::detailedError(nullptr, // mParent, TODO
125 i18n("Error while processing an invitation or update."),
126 errorMessage);
127 }
128
129 d->m_currentOperation = OperationNone;
130 emitiTipMessageProcessed(this, ResultError, errorMessage);
131
132 return;
133 }
134
135 d->m_method = static_cast<KCalendarCore::iTIPMethod>(message->method());
136
138 d->m_incidence = message->event().dynamicCast<KCalendarCore::Incidence>();
139 if (!d->m_incidence) {
140 qCritical() << "Invalid incidence";
141 d->m_currentOperation = OperationNone;
142 emitiTipMessageProcessed(this, ResultError, i18n("Invalid incidence"));
143 return;
144 }
145
146 if (action.startsWith(QLatin1StringView("accepted")) || action.startsWith(QLatin1StringView("tentative"))
147 || action.startsWith(QLatin1StringView("delegated")) || action.startsWith(QLatin1StringView("counter"))) {
148 // Find myself and set my status. This can't be done in the scheduler,
149 // since this does not know the choice I made in the KMail bpf
150 KCalendarCore::Attendee::List attendees = d->m_incidence->attendees();
151 for (auto &attendee : attendees) {
152 if (attendee.email() == receiver) {
153 if (action.startsWith(QLatin1StringView("accepted"))) {
154 attendee.setStatus(KCalendarCore::Attendee::Accepted);
155 } else if (action.startsWith(QLatin1StringView("tentative"))) {
156 attendee.setStatus(KCalendarCore::Attendee::Tentative);
157 } else if (CalendarSettings::self()->outlookCompatCounterProposals() && action.startsWith(QLatin1StringView("counter"))) {
158 attendee.setStatus(KCalendarCore::Attendee::Tentative);
159 } else if (action.startsWith(QLatin1StringView("delegated"))) {
160 attendee.setStatus(KCalendarCore::Attendee::Delegated);
161 }
162 break;
163 }
164 }
165 d->m_incidence->setAttendees(attendees);
166
167 if (CalendarSettings::self()->outlookCompatCounterProposals() || !action.startsWith(QLatin1StringView("counter"))) {
168 d->m_scheduler->acceptTransaction(d->m_incidence, d->calendar(), d->m_method, status, receiver);
169 return; // signal emitted in onSchedulerFinished().
170 }
171 // TODO: what happens here? we must emit a signal
172 } else if (action.startsWith(QLatin1StringView("cancel"))) {
173 // Delete the old incidence, if one is present
174 KCalendarCore::Incidence::Ptr existingIncidence = d->calendar()->incidenceFromSchedulingID(d->m_incidence->uid());
175 if (existingIncidence) {
176 d->m_scheduler->acceptTransaction(d->m_incidence, d->calendar(), KCalendarCore::iTIPCancel, status, receiver);
177 return; // signal emitted in onSchedulerFinished().
178 } else {
179 // We don't have the incidence, nothing to cancel
180 qCWarning(AKONADICALENDAR_LOG) << "Couldn't find the incidence to delete.\n"
181 << "You deleted it previously or didn't even accept the invitation it in the first place.\n"
182 << "; uid=" << d->m_incidence->uid() << "; identifier=" << d->m_incidence->instanceIdentifier()
183 << "; summary=" << d->m_incidence->summary();
184
185 qCDebug(AKONADICALENDAR_LOG) << "\n Here's what we do have with such a summary:";
186 const KCalendarCore::Incidence::List knownIncidences = calendar()->incidences();
187 for (const KCalendarCore::Incidence::Ptr &knownIncidence : knownIncidences) {
188 if (knownIncidence->summary() == d->m_incidence->summary()) {
189 qCDebug(AKONADICALENDAR_LOG) << "\nFound: uid=" << knownIncidence->uid() << "; identifier=" << knownIncidence->instanceIdentifier()
190 << "; schedulingId" << knownIncidence->schedulingID();
191 }
192 }
193
194 emitiTipMessageProcessed(this, ResultSuccess, QString());
195 }
196 } else if (action.startsWith(QLatin1StringView("reply"))) {
197 if (d->m_method != KCalendarCore::iTIPCounter) {
198 d->m_scheduler->acceptTransaction(d->m_incidence, d->calendar(), d->m_method, status, QString());
199 } else {
200 d->m_scheduler->acceptCounterProposal(d->m_incidence, d->calendar());
201 }
202 return; // signal emitted in onSchedulerFinished().
203 } else if (action.startsWith(QLatin1StringView("request"))) {
204 d->m_scheduler->acceptTransaction(d->m_incidence, d->calendar(), d->m_method, status, receiver);
205 return;
206 } else {
207 qCritical() << "Unknown incoming action" << action;
208
209 d->m_currentOperation = OperationNone;
210 emitiTipMessageProcessed(this, ResultError, i18n("Invalid action: %1", action));
211 }
212
213 if (action.startsWith(QLatin1StringView("counter"))) {
214 if (d->m_uiDelegate) {
215 Akonadi::Item item;
216 item.setMimeType(d->m_incidence->mimeType());
217 item.setPayload(KCalendarCore::Incidence::Ptr(d->m_incidence->clone()));
218
219 // TODO_KDE5: This blocks because m_uiDelegate is not a QObject and can't emit a finished()
220 // signal. Make async in kde5
221 d->m_uiDelegate->requestIncidenceEditor(item);
224 newIncidence = item.payload<KCalendarCore::Incidence::Ptr>();
225 }
226
227 if (*newIncidence == *d->m_incidence) {
228 emitiTipMessageProcessed(this, ResultCancelled, QString());
229 } else {
230 ITIPHandlerHelper::SendResult result = d->m_helper->sendCounterProposal(receiver, d->m_incidence, newIncidence);
231 if (result != ITIPHandlerHelper::ResultSuccess) {
232 // It gives success in all paths, this never happens
233 emitiTipMessageProcessed(this, ResultError, i18n("Error sending counter proposal"));
234 Q_ASSERT(false);
235 }
236 }
237 } else {
238 // This should never happen
239 qCWarning(AKONADICALENDAR_LOG) << "No UI delegate is set";
240 emitiTipMessageProcessed(this, ResultError, i18n("Could not start editor to edit counter proposal"));
241 }
242 }
243}
244
246{
247 if (!incidence) {
248 Q_ASSERT(false);
249 qCritical() << "Invalid incidence";
250 return;
251 }
252
253 d->m_queuedInvitation.method = method;
254 d->m_queuedInvitation.incidence = incidence;
255 d->m_parentWidget = parentWidget;
256
257 if (!d->isLoaded()) {
258 // This method will be called again once the calendar is loaded.
259 return;
260 }
261
262 Q_ASSERT(d->m_currentOperation == OperationNone);
263 if (d->m_currentOperation != OperationNone) {
264 qCritical() << "There can't be an operation in progress!" << d->m_currentOperation;
265 return;
266 }
267
268 if (incidence->attendeeCount() == 0 && method != KCalendarCore::iTIPPublish) {
269 if (d->m_showDialogsOnError) {
270 KMessageBox::information(parentWidget,
271 i18n("The item '%1' has no attendees. "
272 "Therefore no groupware message will be sent.",
273 incidence->summary()),
274 i18nc("@title:window", "Message Not Sent"),
275 QStringLiteral("ScheduleNoAttendees"));
276 }
277
278 return;
279 }
280
281 d->m_currentOperation = OperationSendiTIPMessage;
282
283 KCalendarCore::Incidence *incidenceCopy = incidence->clone();
284 incidenceCopy->registerObserver(nullptr);
285 incidenceCopy->clearAttendees();
286
287 d->m_scheduler->performTransaction(incidence, method);
288}
289
291{
292 Q_ASSERT(incidence);
293 if (!incidence) {
294 qCritical() << "Invalid incidence. Aborting.";
295 return;
296 }
297
298 Q_ASSERT(d->m_currentOperation == OperationNone);
299 if (d->m_currentOperation != OperationNone) {
300 qCritical() << "There can't be an operation in progress!" << d->m_currentOperation;
301 return;
302 }
303
304 d->m_queuedInvitation.incidence = incidence;
305 d->m_parentWidget = parentWidget;
306
307 d->m_currentOperation = OperationPublishInformation;
308
309 QPointer<Akonadi::PublishDialog> publishdlg = new Akonadi::PublishDialog();
310 if (incidence->attendeeCount() > 0) {
311 KCalendarCore::Attendee::List attendees = incidence->attendees();
314 for (it = attendees.constBegin(); it != end; ++it) {
315 publishdlg->addAttendee(*it);
316 }
317 }
318 if (publishdlg->exec() == QDialog::Accepted && publishdlg) {
319 d->m_scheduler->publish(incidence, publishdlg->addresses());
320 } else {
321 d->m_currentOperation = OperationNone;
323 }
324 delete publishdlg;
325}
326
327void ITIPHandler::sendAsICalendar(const KCalendarCore::Incidence::Ptr &originalIncidence, QWidget *parentWidget)
328{
329 Q_UNUSED(parentWidget)
330 Q_ASSERT(originalIncidence);
331 if (!originalIncidence) {
332 qCritical() << "Invalid incidence";
333 return;
334 }
335
336 // Clone so we can change organizer and recurid
337 KCalendarCore::Incidence::Ptr incidence = KCalendarCore::Incidence::Ptr(originalIncidence->clone());
338
339 QPointer<Akonadi::PublishDialog> publishdlg = new Akonadi::PublishDialog;
340 if (publishdlg->exec() == QDialog::Accepted && publishdlg) {
341 const QString recipients = publishdlg->addresses();
342 if (incidence->organizer().isEmpty()) {
343 incidence->setOrganizer(KCalendarCore::Person(Akonadi::CalendarUtils::fullName(), Akonadi::CalendarUtils::email()));
344 }
345
346 if (incidence->hasRecurrenceId()) {
347 // For an individual occurrence, recur id doesn't make sense, since we're not sending the whole recurrence series.
348 incidence->setRecurrenceId({});
349 }
350
352 const QString from = Akonadi::CalendarUtils::email();
353 const bool bccMe = Akonadi::CalendarSettings::self()->bcc();
354 const QString messageText = format.createScheduleMessage(incidence, KCalendarCore::iTIPRequest);
355 auto mailer = new MailClient(d->m_factory);
356 d->m_queuedInvitation.incidence = incidence;
357 connect(mailer, &MailClient::finished, d.get(), [this](Akonadi::MailClient::Result result, const QString &str) {
358 d->finishSendAsICalendar(result, str);
359 });
360
361 mailer->mailTo(incidence,
362 KIdentityManagementCore::IdentityManager::self()->identityForAddress(from),
363 from,
365 bccMe,
366 recipients,
367 messageText);
368 }
369 delete publishdlg;
370}
371
373{
374 d->m_uiDelegate = delegate;
375}
376
378{
379 if (d->m_calendar != calendar) {
380 d->m_calendar = calendar;
381 }
382}
383
385{
386 d->m_showDialogsOnError = enable;
387}
388
390{
391 return d->m_calendar;
392}
393
394#include "moc_itiphandler.cpp"
Ui delegate for editing counter proposals.
Definition itiphandler.h:50
Factory to create Akonadi::MessageQueueJob jobs or ITIPHandlerDialogDelegate objects.
Ui delegate for dialogs raised by the ITIPHandler and IncidenceChanger.
Definition itiphandler.h:66
Handles sending of iTip messages as well as processing incoming ones.
~ITIPHandler() override
Destroys this instance.
void setGroupwareUiDelegate(GroupwareUiDelegate *delegate)
Sets the UI delegate to edit counter proposals.
@ ResultCancelled
User cancelled the operation.
@ ResultSuccess
The invitation was successfully handled.
@ ResultError
An unexpected error occurred.
ITIPHandler(QObject *parent=nullptr)
Creates a new ITIPHandler instance.
Akonadi::CalendarBase::Ptr calendar() const
Returns the calendar used by this itip handler.
void setShowDialogsOnError(bool enable)
Sets if the ITIP handler should show dialogs on error.
void sendiTIPMessage(KCalendarCore::iTIPMethod method, const KCalendarCore::Incidence::Ptr &incidence, QWidget *parentWidget=nullptr)
Sends an iTip message.
void processiTIPMessage(const QString &receiver, const QString &iCal, const QString &type)
Processes a received iTip message.
void sendAsICalendar(const KCalendarCore::Incidence::Ptr &incidence, QWidget *parentWidget=nullptr)
Sends an e-mail with the incidence attached as iCalendar source.
void setCalendar(const Akonadi::CalendarBase::Ptr &calendar)
Sets the calendar that the itip handler should use.
void publishInformation(const KCalendarCore::Incidence::Ptr &incidence, QWidget *parentWidget=nullptr)
Publishes incidence incidence.
void informationPublished(Akonadi::ITIPHandler::Result, const QString &errorMessage)
Signal emitted after an incidence was published with publishInformation()
void setPayload(const T &p)
void setMimeType(const QString &mimeType)
bool hasPayload() const
T payload() const
Exception * exception() const
QString createScheduleMessage(const IncidenceBase::Ptr &incidence, iTIPMethod method)
ScheduleMessage::Ptr parseScheduleMessage(const Calendar::Ptr &calendar, const QString &string)
void registerObserver(IncidenceObserver *observer)
QSharedPointer< Incidence > Ptr
static IdentityManager * self()
Q_SCRIPTABLE CaptureState status()
QString i18nc(const char *context, const char *text, const TYPE &arg...)
QString i18n(const char *text, const TYPE &arg...)
FreeBusyManager::Singleton.
KCALUTILS_EXPORT QString errorMessage(const KCalendarCore::Exception &exception)
void information(QWidget *parent, const QString &text, const QString &title=QString(), const QString &dontShowAgainName=QString(), Options options=Notify)
void detailedError(QWidget *parent, const QString &text, const QString &details, const QString &title, const KGuiItem &buttonOk, Options options=Notify)
typedef ConstIterator
const_iterator constBegin() const const
const_iterator constEnd() const const
bool invokeMethod(QObject *context, Functor &&function, FunctorReturnType *ret)
Q_EMITQ_EMIT
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
QObject * parent() const const
bool startsWith(QChar c, Qt::CaseSensitivity cs) const const
QueuedConnection
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Tue Mar 26 2024 11:17:16 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.