Akonadi Calendar

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

KDE's Doxygen guidelines are available online.