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

KDE's Doxygen guidelines are available online.