• Skip to content
  • Skip to link menu
  • KDE API Reference
  • kdepimlibs-4.14.10 API Reference
  • KDE Home
  • Contact Us
 

akonadi

  • akonadi
  • calendar
incidencechanger.cpp
1/*
2 Copyright (C) 2004 Reinhold Kainhofer <reinhold@kainhofer.com>
3 Copyright (C) 2010-2012 Sérgio Martins <iamsergio@gmail.com>
4
5 This library is free software; you can redistribute it and/or modify it
6 under the terms of the GNU Library General Public License as published by
7 the Free Software Foundation; either version 2 of the License, or (at your
8 option) any later version.
9
10 This library is distributed in the hope that it will be useful, but WITHOUT
11 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
12 FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
13 License for more details.
14
15 You should have received a copy of the GNU Library General Public License
16 along with this library; see the file COPYING.LIB. If not, write to the
17 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
18 02110-1301, USA.
19*/
20#include "incidencechanger.h"
21#include "incidencechanger_p.h"
22#include "mailscheduler_p.h"
23#include "utils_p.h"
24
25#include <akonadi/itemcreatejob.h>
26#include <akonadi/itemmodifyjob.h>
27#include <akonadi/itemdeletejob.h>
28#include <akonadi/transactionsequence.h>
29
30#include <KJob>
31#include <KLocalizedString>
32#include <KGuiItem>
33#include <KMessageBox>
34
35#include <QBitArray>
36
37using namespace Akonadi;
38using namespace KCalCore;
39
40#ifdef PLEASE_TEST_INVITATIONS
41# define RUNNING_UNIT_TESTS true
42#else
43# define RUNNING_UNIT_TESTS false
44#endif
45
46ITIPHandlerHelper::Action actionFromStatus(ITIPHandlerHelper::SendResult result)
47{
48 //enum SendResult {
49 // Canceled, /**< Sending was canceled by the user, meaning there are
50 // local changes of which other attendees are not aware. */
51 // FailKeepUpdate, /**< Sending failed, the changes to the incidence must be kept. */
52 // FailAbortUpdate, /**< Sending failed, the changes to the incidence must be undone. */
53 // NoSendingNeeded, /**< In some cases it is not needed to send an invitation
54 // (e.g. when we are the only attendee) */
55 // Success
56 switch (result) {
57 case ITIPHandlerHelper::ResultCanceled:
58 return ITIPHandlerHelper::ActionDontSendMessage;
59 case ITIPHandlerHelper::ResultSuccess:
60 return ITIPHandlerHelper::ActionSendMessage;
61 default:
62 return ITIPHandlerHelper::ActionAsk;
63 }
64}
65
66namespace Akonadi {
67// Does a queued emit, with QMetaObject::invokeMethod
68static void emitCreateFinished(IncidenceChanger *changer,
69 int changeId,
70 const Akonadi::Item &item,
71 Akonadi::IncidenceChanger::ResultCode resultCode,
72 const QString &errorString)
73{
74 QMetaObject::invokeMethod(changer, "createFinished", Qt::QueuedConnection,
75 Q_ARG(int, changeId),
76 Q_ARG(Akonadi::Item, item),
77 Q_ARG(Akonadi::IncidenceChanger::ResultCode, resultCode),
78 Q_ARG(QString, errorString));
79}
80
81// Does a queued emit, with QMetaObject::invokeMethod
82static void emitModifyFinished(IncidenceChanger *changer,
83 int changeId,
84 const Akonadi::Item &item,
85 IncidenceChanger::ResultCode resultCode,
86 const QString &errorString)
87{
88 QMetaObject::invokeMethod(changer, "modifyFinished", Qt::QueuedConnection,
89 Q_ARG(int, changeId),
90 Q_ARG(Akonadi::Item, item),
91 Q_ARG(Akonadi::IncidenceChanger::ResultCode, resultCode),
92 Q_ARG(QString, errorString));
93}
94
95// Does a queued emit, with QMetaObject::invokeMethod
96static void emitDeleteFinished(IncidenceChanger *changer,
97 int changeId,
98 const QVector<Akonadi::Item::Id> &itemIdList,
99 IncidenceChanger::ResultCode resultCode,
100 const QString &errorString)
101{
102 QMetaObject::invokeMethod(changer, "deleteFinished", Qt::QueuedConnection,
103 Q_ARG(int, changeId),
104 Q_ARG(QVector<Akonadi::Item::Id>, itemIdList),
105 Q_ARG(Akonadi::IncidenceChanger::ResultCode, resultCode),
106 Q_ARG(QString, errorString));
107}
108}
109
110class ConflictPreventerPrivate;
111class ConflictPreventer {
112 friend class ConflictPreventerPrivate;
113public:
114 static ConflictPreventer* self();
115
116 // To avoid conflicts when the two modifications came from within the same application
117 QHash<Akonadi::Item::Id, int> mLatestRevisionByItemId;
118private:
119 ConflictPreventer() {}
120 ~ConflictPreventer() {}
121};
122
123class ConflictPreventerPrivate {
124public:
125 ConflictPreventer instance;
126};
127
128K_GLOBAL_STATIC(ConflictPreventerPrivate, sConflictPreventerPrivate)
129
130ConflictPreventer* ConflictPreventer::self()
131{
132 return &sConflictPreventerPrivate->instance;
133}
134
135IncidenceChanger::Private::Private(bool enableHistory, IncidenceChanger *qq) : q(qq)
136{
137 mLatestChangeId = 0;
138 mShowDialogsOnError = true;
139 mHistory = enableHistory ? new History(this) : 0;
140 mUseHistory = enableHistory;
141 mDestinationPolicy = DestinationPolicyDefault;
142 mRespectsCollectionRights = false;
143 mGroupwareCommunication = false;
144 mLatestAtomicOperationId = 0;
145 mBatchOperationInProgress = false;
146 mAutoAdjustRecurrence = true;
147 m_collectionFetchJob = 0;
148 m_invitationPolicy = InvitationPolicyAsk;
149
150 qRegisterMetaType<QVector<Akonadi::Item::Id> >("QVector<Akonadi::Item::Id>");
151 qRegisterMetaType<Akonadi::Item::Id>("Akonadi::Item::Id");
152 qRegisterMetaType<Akonadi::Item>("Akonadi::Item");
153 qRegisterMetaType<Akonadi::IncidenceChanger::ResultCode>(
154 "Akonadi::IncidenceChanger::ResultCode");
155}
156
157IncidenceChanger::Private::~Private()
158{
159 if (!mAtomicOperations.isEmpty() ||
160 !mQueuedModifications.isEmpty() ||
161 !mModificationsInProgress.isEmpty()) {
162 kDebug() << "Normal if the application was being used. "
163 "But might indicate a memory leak if it wasn't";
164 }
165}
166
167bool IncidenceChanger::Private::atomicOperationIsValid(uint atomicOperationId) const
168{
169 // Changes must be done between startAtomicOperation() and endAtomicOperation()
170 return mAtomicOperations.contains(atomicOperationId) &&
171 !mAtomicOperations[atomicOperationId]->m_endCalled;
172}
173
174bool IncidenceChanger::Private::hasRights(const Collection &collection,
175 IncidenceChanger::ChangeType changeType) const
176{
177 bool result = false;
178 switch (changeType) {
179 case ChangeTypeCreate:
180 result = collection.rights() & Akonadi::Collection::CanCreateItem;
181 break;
182 case ChangeTypeModify:
183 result = collection.rights() & Akonadi::Collection::CanChangeItem;
184 break;
185 case ChangeTypeDelete:
186 result = collection.rights() & Akonadi::Collection::CanDeleteItem;
187 break;
188 default:
189 Q_ASSERT_X(false, "hasRights", "invalid type");
190 }
191
192 return !collection.isValid() || !mRespectsCollectionRights || result;
193}
194
195Akonadi::Job* IncidenceChanger::Private::parentJob(const Change::Ptr &change) const
196{
197 return (mBatchOperationInProgress && !change->queuedModification) ?
198 mAtomicOperations[mLatestAtomicOperationId]->transaction() : 0;
199}
200
201void IncidenceChanger::Private::queueModification(Change::Ptr change)
202{
203 // If there's already a change queued we just discard it
204 // and send the newer change, which already includes
205 // previous modifications
206 const Akonadi::Item::Id id = change->newItem.id();
207 if (mQueuedModifications.contains(id)) {
208 Change::Ptr toBeDiscarded = mQueuedModifications.take(id);
209 toBeDiscarded->resultCode = ResultCodeModificationDiscarded;
210 toBeDiscarded->completed = true;
211 mChangeById.remove(toBeDiscarded->id);
212 }
213
214 change->queuedModification = true;
215 mQueuedModifications[id] = change;
216}
217
218void IncidenceChanger::Private::performNextModification(Akonadi::Item::Id id)
219{
220 mModificationsInProgress.remove(id);
221
222 if (mQueuedModifications.contains(id)) {
223 const Change::Ptr change = mQueuedModifications.take(id);
224 performModification(change);
225 }
226}
227
228void IncidenceChanger::Private::handleTransactionJobResult(KJob *job)
229{
230 //kDebug();
231 TransactionSequence *transaction = qobject_cast<TransactionSequence*>(job);
232 Q_ASSERT(transaction);
233 Q_ASSERT(mAtomicOperationByTransaction.contains(transaction));
234
235 const uint atomicOperationId = mAtomicOperationByTransaction.take(transaction);
236
237 Q_ASSERT(mAtomicOperations.contains(atomicOperationId));
238 AtomicOperation *operation = mAtomicOperations[atomicOperationId];
239 Q_ASSERT(operation);
240 Q_ASSERT(operation->m_id == atomicOperationId);
241 if (job->error()) {
242 if (!operation->rolledback())
243 operation->setRolledback();
244 kError() << "Transaction failed, everything was rolledback. "
245 << job->errorString();
246 } else {
247 Q_ASSERT(operation->m_endCalled);
248 Q_ASSERT(!operation->pendingJobs());
249 }
250
251 if (!operation->pendingJobs() && operation->m_endCalled) {
252 delete mAtomicOperations.take(atomicOperationId);
253 mBatchOperationInProgress = false;
254 } else {
255 operation->m_transactionCompleted = true;
256 }
257}
258
259void IncidenceChanger::Private::handleCreateJobResult(KJob *job)
260{
261 //kDebug();
262 QString errorString;
263 ResultCode resultCode = ResultCodeSuccess;
264
265 Change::Ptr change = mChangeForJob.take(job);
266 mChangeById.remove(change->id);
267
268 const ItemCreateJob *j = qobject_cast<const ItemCreateJob*>(job);
269 Q_ASSERT(j);
270 Akonadi::Item item = j->item();
271
272 QString description;
273 if (change->atomicOperationId != 0) {
274 AtomicOperation *a = mAtomicOperations[change->atomicOperationId];
275 a->m_numCompletedChanges++;
276 change->completed = true;
277 description = a->m_description;
278 }
279
280 if (j->error()) {
281 item = change->newItem;
282 resultCode = ResultCodeJobError;
283 errorString = j->errorString();
284 kError() << errorString;
285 if (mShowDialogsOnError) {
286 KMessageBox::sorry(change->parentWidget,
287 i18n("Error while trying to create calendar item. Error was: %1",
288 errorString));
289 }
290 } else {
291 Q_ASSERT(item.isValid());
292 Q_ASSERT(item.hasPayload<KCalCore::Incidence::Ptr>());
293 change->newItem = item;
294 handleInvitationsAfterChange(change);
295 // for user undo/redo
296 if (change->recordToHistory) {
297 mHistory->recordCreation(item, description, change->atomicOperationId);
298 }
299 }
300
301 change->errorString = errorString;
302 change->resultCode = resultCode;
303 // puff, change finally goes out of scope, and emits the incidenceCreated signal.
304}
305
306void IncidenceChanger::Private::handleDeleteJobResult(KJob *job)
307{
308 //kDebug();
309 QString errorString;
310 ResultCode resultCode = ResultCodeSuccess;
311
312 Change::Ptr change = mChangeForJob.take(job);
313 mChangeById.remove(change->id);
314
315 const ItemDeleteJob *j = qobject_cast<const ItemDeleteJob*>(job);
316 const Item::List items = j->deletedItems();
317
318 QSharedPointer<DeletionChange> deletionChange = change.staticCast<DeletionChange>();
319
320 foreach(const Akonadi::Item &item, items) {
321 deletionChange->mItemIds.append(item.id());
322 }
323 QString description;
324 if (change->atomicOperationId != 0) {
325 AtomicOperation *a = mAtomicOperations[change->atomicOperationId];
326 a->m_numCompletedChanges++;
327 change->completed = true;
328 description = a->m_description;
329 }
330 if (j->error()) {
331 resultCode = ResultCodeJobError;
332 errorString = j->errorString();
333 kError() << errorString;
334 if (mShowDialogsOnError) {
335 KMessageBox::sorry(change->parentWidget,
336 i18n("Error while trying to delete calendar item. Error was: %1",
337 errorString));
338 }
339
340 foreach(const Item &item, items) {
341 // Werent deleted due to error
342 mDeletedItemIds.remove(mDeletedItemIds.indexOf(item.id()));
343 }
344 } else { // success
345 if (change->recordToHistory) {
346 Q_ASSERT(mHistory);
347 mHistory->recordDeletions(items, description, change->atomicOperationId);
348 }
349
350 handleInvitationsAfterChange(change);
351 }
352
353 change->errorString = errorString;
354 change->resultCode = resultCode;
355 // puff, change finally goes out of scope, and emits the incidenceDeleted signal.
356}
357
358void IncidenceChanger::Private::handleModifyJobResult(KJob *job)
359{
360 QString errorString;
361 ResultCode resultCode = ResultCodeSuccess;
362 Change::Ptr change = mChangeForJob.take(job);
363 mChangeById.remove(change->id);
364
365 const ItemModifyJob *j = qobject_cast<const ItemModifyJob*>(job);
366 const Item item = j->item();
367 Q_ASSERT(mDirtyFieldsByJob.contains(job));
368 Q_ASSERT(item.hasPayload<KCalCore::Incidence::Ptr>());
369 item.payload<KCalCore::Incidence::Ptr>()->setDirtyFields(mDirtyFieldsByJob.value(job));
370 const QSet<KCalCore::IncidenceBase::Field> dirtyFields = mDirtyFieldsByJob.value(job);
371 QString description;
372 if (change->atomicOperationId != 0) {
373 AtomicOperation *a = mAtomicOperations[change->atomicOperationId];
374 a->m_numCompletedChanges++;
375 change->completed = true;
376 description = a->m_description;
377 }
378 if (j->error()) {
379 if (deleteAlreadyCalled(item.id())) {
380 // User deleted the item almost at the same time he changed it. We could just return success
381 // but the delete is probably already recorded to History, and that would make undo not work
382 // in the proper order.
383 resultCode = ResultCodeAlreadyDeleted;
384 errorString = j->errorString();
385 kWarning() << "Trying to change item " << item.id() << " while deletion is in progress.";
386 } else {
387 resultCode = ResultCodeJobError;
388 errorString = j->errorString();
389 kError() << errorString;
390 }
391 if (mShowDialogsOnError) {
392 KMessageBox::sorry(change->parentWidget,
393 i18n("Error while trying to modify calendar item. Error was: %1",
394 errorString));
395 }
396 } else { // success
397 ConflictPreventer::self()->mLatestRevisionByItemId[item.id()] = item.revision();
398 change->newItem = item;
399 if (change->recordToHistory && !change->originalItems.isEmpty()) {
400 Q_ASSERT(change->originalItems.count() == 1);
401 mHistory->recordModification(change->originalItems.first(), item,
402 description, change->atomicOperationId);
403 }
404
405 handleInvitationsAfterChange(change);
406 }
407
408 change->errorString = errorString;
409 change->resultCode = resultCode;
410 // puff, change finally goes out of scope, and emits the incidenceModified signal.
411
412 QMetaObject::invokeMethod(this, "performNextModification",
413 Qt::QueuedConnection,
414 Q_ARG(Akonadi::Item::Id, item.id()));
415}
416
417bool IncidenceChanger::Private::deleteAlreadyCalled(Akonadi::Item::Id id) const
418{
419 return mDeletedItemIds.contains(id);
420}
421
422bool IncidenceChanger::Private::handleInvitationsBeforeChange(const Change::Ptr &change)
423{
424 bool result = true;
425 if (mGroupwareCommunication) {
426 ITIPHandlerHelper handler(change->parentWidget); // TODO make async
427
428 if (m_invitationPolicy == InvitationPolicySend) {
429 handler.setDefaultAction(ITIPHandlerHelper::ActionSendMessage);
430 } else if (m_invitationPolicy == InvitationPolicyDontSend) {
431 handler.setDefaultAction(ITIPHandlerHelper::ActionDontSendMessage);
432 } else if (mInvitationStatusByAtomicOperation.contains(change->atomicOperationId)) {
433 handler.setDefaultAction(actionFromStatus(mInvitationStatusByAtomicOperation.value(change->atomicOperationId)));
434 }
435
436 switch (change->type) {
437 case IncidenceChanger::ChangeTypeCreate:
438 // nothing needs to be done
439 break;
440 case IncidenceChanger::ChangeTypeDelete:
441 {
442 ITIPHandlerHelper::SendResult status;
443 Q_ASSERT(!change->originalItems.isEmpty());
444 foreach(const Akonadi::Item &item, change->originalItems) {
445 Q_ASSERT(item.hasPayload<KCalCore::Incidence::Ptr>());
446 Incidence::Ptr incidence = CalendarUtils::incidence(item);
447 if (!incidence->supportsGroupwareCommunication()) {
448 continue;
449 }
450 // We only send CANCEL if we're the organizer.
451 // If we're not, then we send REPLY with PartStat=Declined in handleInvitationsAfterChange()
452 if (Akonadi::CalendarUtils::thatIsMe(incidence->organizer()->email())) {
453 status = handler.sendIncidenceDeletedMessage(KCalCore::iTIPCancel, incidence);
454 if (change->atomicOperationId) {
455 mInvitationStatusByAtomicOperation.insert(change->atomicOperationId, status);
456 }
457 result = status != ITIPHandlerHelper::ResultFailAbortUpdate;
458 //TODO: with some status we want to break immediately
459 }
460 }
461 }
462 break;
463 case IncidenceChanger::ChangeTypeModify:
464 {
465 if (change->originalItems.isEmpty()) {
466 break;
467 }
468
469 Q_ASSERT(change->originalItems.count() == 1);
470 Incidence::Ptr oldIncidence = CalendarUtils::incidence(change->originalItems.first());
471 Incidence::Ptr newIncidence = CalendarUtils::incidence(change->newItem);
472
473 if (!oldIncidence->supportsGroupwareCommunication()) {
474 break;
475 }
476
477 const bool weAreOrganizer = Akonadi::CalendarUtils::thatIsMe(newIncidence->organizer()->email());
478 if (RUNNING_UNIT_TESTS && !weAreOrganizer) {
479 // This is a bit of a workaround when running tests. I don't want to show the
480 // "You're not organizer, do you want to modify event?" dialog in unit-tests, but want
481 // to emulate a "yes" and a "no" press.
482 if (m_invitationPolicy == InvitationPolicySend) {
483 return true;
484 } else if (m_invitationPolicy == InvitationPolicyDontSend) {
485 return false;
486 }
487 }
488
489 const bool modify = handler.handleIncidenceAboutToBeModified(newIncidence);
490 if (modify) {
491 break;
492 }
493
494 if (newIncidence->type() == oldIncidence->type()) {
495 IncidenceBase *i1 = newIncidence.data();
496 IncidenceBase *i2 = oldIncidence.data();
497 *i1 = *i2;
498 }
499 result = false;
500 }
501 break;
502 default:
503 Q_ASSERT(false);
504 result = false;
505 }
506 }
507 return result;
508}
509
510bool IncidenceChanger::Private::handleInvitationsAfterChange(const Change::Ptr &change)
511{
512 if (change->useGroupwareCommunication) {
513 ITIPHandlerHelper handler(change->parentWidget); // TODO make async
514
515 const bool alwaysSend = m_invitationPolicy == InvitationPolicySend;
516 const bool neverSend = m_invitationPolicy == InvitationPolicyDontSend;
517 if (alwaysSend) {
518 handler.setDefaultAction(ITIPHandlerHelper::ActionSendMessage);
519 }
520
521 if (neverSend) {
522 handler.setDefaultAction(ITIPHandlerHelper::ActionDontSendMessage);
523 }
524
525 switch (change->type) {
526 case IncidenceChanger::ChangeTypeCreate:
527 {
528 Incidence::Ptr incidence = CalendarUtils::incidence(change->newItem);
529 if (incidence->supportsGroupwareCommunication()) {
530 const ITIPHandlerHelper::SendResult status =
531 handler.sendIncidenceCreatedMessage(KCalCore::iTIPRequest, incidence);
532
533 if (status == ITIPHandlerHelper::ResultFailAbortUpdate) {
534 kError() << "Sending invitations failed, but did not delete the incidence";
535 }
536
537 const uint atomicOperationId = change->atomicOperationId;
538 if (atomicOperationId != 0) {
539 mInvitationStatusByAtomicOperation.insert(atomicOperationId, status);
540 }
541 }
542 }
543 break;
544 case IncidenceChanger::ChangeTypeDelete:
545 {
546 Q_ASSERT(!change->originalItems.isEmpty());
547 foreach(const Akonadi::Item &item, change->originalItems) {
548 Q_ASSERT(item.hasPayload());
549 Incidence::Ptr incidence = CalendarUtils::incidence(item);
550 Q_ASSERT(incidence);
551 if (!incidence->supportsGroupwareCommunication())
552 continue;
553
554 if (!Akonadi::CalendarUtils::thatIsMe(incidence->organizer()->email())) {
555 const QStringList myEmails = Akonadi::CalendarUtils::allEmails();
556 bool notifyOrganizer = false;
557 for (QStringList::ConstIterator it = myEmails.begin(); it != myEmails.end(); ++it) {
558 const QString email = *it;
559 KCalCore::Attendee::Ptr me(incidence->attendeeByMail(email));
560 if (me) {
561 if (me->status() == KCalCore::Attendee::Accepted ||
562 me->status() == KCalCore::Attendee::Delegated) {
563 notifyOrganizer = true;
564 }
565 KCalCore::Attendee::Ptr newMe(new KCalCore::Attendee(*me));
566 newMe->setStatus(KCalCore::Attendee::Declined);
567 incidence->clearAttendees();
568 incidence->addAttendee(newMe);
569 break;
570 }
571 }
572
573 if (notifyOrganizer) {
574 MailScheduler scheduler; // TODO make async
575 scheduler.performTransaction(incidence, KCalCore::iTIPReply);
576 }
577 }
578 }
579 }
580 break;
581 case IncidenceChanger::ChangeTypeModify:
582 {
583 if (change->originalItems.isEmpty()) {
584 break;
585 }
586
587 Q_ASSERT(change->originalItems.count() == 1);
588 Incidence::Ptr oldIncidence = CalendarUtils::incidence(change->originalItems.first());
589 Incidence::Ptr newIncidence = CalendarUtils::incidence(change->newItem);
590
591 if (!newIncidence->supportsGroupwareCommunication() ||
592 !Akonadi::CalendarUtils::thatIsMe(newIncidence->organizer()->email())) {
593 // If we're not the organizer, the user already saw the "Do you really want to do this, incidence will become out of sync"
594 break;
595 }
596
597 if (!neverSend && !alwaysSend && mInvitationStatusByAtomicOperation.contains(change->atomicOperationId)) {
598 handler.setDefaultAction(actionFromStatus(mInvitationStatusByAtomicOperation.value(change->atomicOperationId)));
599 }
600
601 const bool attendeeStatusChanged = myAttendeeStatusChanged(newIncidence,
602 oldIncidence,
603 Akonadi::CalendarUtils::allEmails());
604
605 ITIPHandlerHelper::SendResult status = handler.sendIncidenceModifiedMessage(KCalCore::iTIPRequest,
606 newIncidence,
607 attendeeStatusChanged);
608
609 if (change->atomicOperationId != 0) {
610 mInvitationStatusByAtomicOperation.insert(change->atomicOperationId, status);
611 }
612 }
613 break;
614 default:
615 Q_ASSERT(false);
616 return false;
617 }
618 }
619 return true;
620}
621
623bool IncidenceChanger::Private::myAttendeeStatusChanged(const Incidence::Ptr &newInc,
624 const Incidence::Ptr &oldInc,
625 const QStringList &myEmails)
626{
627 Q_ASSERT(newInc);
628 Q_ASSERT(oldInc);
629 const Attendee::Ptr oldMe = oldInc->attendeeByMails(myEmails);
630 const Attendee::Ptr newMe = newInc->attendeeByMails(myEmails);
631
632 return oldMe && newMe && oldMe->status() != newMe->status();
633}
634
635IncidenceChanger::IncidenceChanger(QObject *parent) : QObject(parent)
636 , d(new Private(true, this))
637{
638}
639
640IncidenceChanger::IncidenceChanger(bool enableHistory, QObject *parent) : QObject(parent)
641 , d(new Private(enableHistory, this))
642{
643}
644
645IncidenceChanger::~IncidenceChanger()
646{
647 delete d;
648}
649
650int IncidenceChanger::createIncidence(const Incidence::Ptr &incidence,
651 const Collection &collection,
652 QWidget *parent)
653{
654 //kDebug();
655 if (!incidence) {
656 kWarning() << "An invalid payload is not allowed.";
657 d->cancelTransaction();
658 return -1;
659 }
660
661 const uint atomicOperationId = d->mBatchOperationInProgress ? d->mLatestAtomicOperationId : 0;
662
663 const Change::Ptr change(new CreationChange(this, ++d->mLatestChangeId,
664 atomicOperationId, parent));
665 const int changeId = change->id;
666 Q_ASSERT(!(d->mBatchOperationInProgress && !d->mAtomicOperations.contains(atomicOperationId)));
667 if (d->mBatchOperationInProgress && d->mAtomicOperations[atomicOperationId]->rolledback()) {
668 const QString errorMessage = d->showErrorDialog(ResultCodeRolledback, parent);
669 kWarning() << errorMessage;
670
671 change->resultCode = ResultCodeRolledback;
672 change->errorString = errorMessage;
673 d->cleanupTransaction();
674 return changeId;
675 }
676
677 Item item;
678 item.setPayload<KCalCore::Incidence::Ptr>(incidence);
679 item.setMimeType(incidence->mimeType());
680
681 change->newItem = item;
682
683 d->step1DetermineDestinationCollection(change, collection);
684
685 return change->id;
686}
687
688int IncidenceChanger::deleteIncidence(const Item &item, QWidget *parent)
689{
690 Item::List list;
691 list.append(item);
692
693 return deleteIncidences(list, parent);
694}
695
696int IncidenceChanger::deleteIncidences(const Item::List &items, QWidget *parent)
697{
698 //kDebug();
699 if (items.isEmpty()) {
700 kError() << "Delete what?";
701 d->cancelTransaction();
702 return -1;
703 }
704
705 foreach(const Item &item, items) {
706 if (!item.isValid()) {
707 kError() << "Items must be valid!";
708 d->cancelTransaction();
709 return -1;
710 }
711 }
712
713 const uint atomicOperationId = d->mBatchOperationInProgress ? d->mLatestAtomicOperationId : 0;
714 const int changeId = ++d->mLatestChangeId;
715 const Change::Ptr change(new DeletionChange(this, changeId, atomicOperationId, parent));
716
717 foreach(const Item &item, items) {
718 if (!d->hasRights(item.parentCollection(), ChangeTypeDelete)) {
719 kWarning() << "Item " << item.id() << " can't be deleted due to ACL restrictions";
720 const QString errorString = d->showErrorDialog(ResultCodePermissions, parent);
721 change->resultCode = ResultCodePermissions;
722 change->errorString = errorString;
723 d->cancelTransaction();
724 return changeId;
725 }
726 }
727
728 if (!d->allowAtomicOperation(atomicOperationId, change)) {
729 const QString errorString = d->showErrorDialog(ResultCodeDuplicateId, parent);
730 change->resultCode = ResultCodeDuplicateId;
731 change->errorString = errorString;
732 kWarning() << errorString;
733 d->cancelTransaction();
734 return changeId;
735 }
736
737 Item::List itemsToDelete;
738 foreach(const Item &item, items) {
739 if (d->deleteAlreadyCalled(item.id())) {
740 // IncidenceChanger::deleteIncidence() called twice, ignore this one.
741 kDebug() << "Item " << item.id() << " already deleted or being deleted, skipping";
742 } else {
743 itemsToDelete.append(item);
744 }
745 }
746
747 if (d->mBatchOperationInProgress && d->mAtomicOperations[atomicOperationId]->rolledback()) {
748 const QString errorMessage = d->showErrorDialog(ResultCodeRolledback, parent);
749 change->resultCode = ResultCodeRolledback;
750 change->errorString = errorMessage;
751 kError() << errorMessage;
752 d->cleanupTransaction();
753 return changeId;
754 }
755
756 if (itemsToDelete.isEmpty()) {
757 QVector<Akonadi::Item::Id> itemIdList;
758 itemIdList.append(Item().id());
759 kDebug() << "Items already deleted or being deleted, skipping";
760 const QString errorMessage =
761 i18n("That calendar item was already deleted, or currently being deleted.");
762 // Queued emit because return must be executed first, otherwise caller won't know this workId
763 change->resultCode = ResultCodeAlreadyDeleted;
764 change->errorString = errorMessage;
765 d->cancelTransaction();
766 kWarning() << errorMessage;
767 return changeId;
768 }
769 change->originalItems = itemsToDelete;
770 d->handleInvitationsBeforeChange(change);
771
772 ItemDeleteJob *deleteJob = new ItemDeleteJob(itemsToDelete, d->parentJob(change));
773 d->mChangeForJob.insert(deleteJob, change);
774 d->mChangeById.insert(changeId, change);
775
776 if (d->mBatchOperationInProgress) {
777 AtomicOperation *atomic = d->mAtomicOperations[atomicOperationId];
778 Q_ASSERT(atomic);
779 atomic->addChange(change);
780 }
781
782 foreach(const Item &item, itemsToDelete) {
783 d->mDeletedItemIds << item.id();
784 }
785
786 // Do some cleanup
787 if (d->mDeletedItemIds.count() > 100)
788 d->mDeletedItemIds.remove(0, 50);
789
790 // QueuedConnection because of possible sync exec calls.
791 connect(deleteJob, SIGNAL(result(KJob*)),
792 d, SLOT(handleDeleteJobResult(KJob*)), Qt::QueuedConnection);
793
794 return changeId;
795}
796
797int IncidenceChanger::modifyIncidence(const Item &changedItem,
798 const KCalCore::Incidence::Ptr &originalPayload,
799 QWidget *parent)
800{
801 if (!changedItem.isValid() || !changedItem.hasPayload<Incidence::Ptr>()) {
802 kWarning() << "An invalid item or payload is not allowed.";
803 d->cancelTransaction();
804 return -1;
805 }
806
807 if (!d->hasRights(changedItem.parentCollection(), ChangeTypeModify)) {
808 kWarning() << "Item " << changedItem.id() << " can't be deleted due to ACL restrictions";
809 const int changeId = ++d->mLatestChangeId;
810 const QString errorString = d->showErrorDialog(ResultCodePermissions, parent);
811 emitModifyFinished(this, changeId, changedItem, ResultCodePermissions, errorString);
812 d->cancelTransaction();
813 return changeId;
814 }
815
816 //TODO also update revision here instead of in the editor
817 changedItem.payload<Incidence::Ptr>()->setLastModified(KDateTime::currentUtcDateTime());
818
819 const uint atomicOperationId = d->mBatchOperationInProgress ? d->mLatestAtomicOperationId : 0;
820 const int changeId = ++d->mLatestChangeId;
821 ModificationChange *modificationChange = new ModificationChange(this, changeId,
822 atomicOperationId, parent);
823 Change::Ptr change(modificationChange);
824
825 if (originalPayload) {
826 Item originalItem(changedItem);
827 originalItem.setPayload<KCalCore::Incidence::Ptr>(originalPayload);
828 modificationChange->originalItems << originalItem;
829 }
830
831 modificationChange->newItem = changedItem;
832 d->mChangeById.insert(changeId, change);
833
834 if (!d->allowAtomicOperation(atomicOperationId, change)) {
835 const QString errorString = d->showErrorDialog(ResultCodeDuplicateId, parent);
836 change->resultCode = ResultCodeDuplicateId;
837 change->errorString = errorString;
838 d->cancelTransaction();
839 kWarning() << "Atomic operation now allowed";
840 return changeId;
841 }
842
843 if (d->mBatchOperationInProgress && d->mAtomicOperations[atomicOperationId]->rolledback()) {
844 const QString errorMessage = d->showErrorDialog(ResultCodeRolledback, parent);
845 kError() << errorMessage;
846 d->cleanupTransaction();
847 emitModifyFinished(this, changeId, changedItem, ResultCodeRolledback, errorMessage);
848 } else {
849 d->adjustRecurrence(originalPayload, CalendarUtils::incidence(modificationChange->newItem));
850 d->performModification(change);
851 }
852
853 return changeId;
854}
855
856void IncidenceChanger::Private::performModification(Change::Ptr change)
857{
858 const Item::Id id = change->newItem.id();
859 Akonadi::Item &newItem = change->newItem;
860 Q_ASSERT(newItem.isValid());
861 Q_ASSERT(newItem.hasPayload<Incidence::Ptr>());
862
863 const int changeId = change->id;
864
865 if (deleteAlreadyCalled(id)) {
866 // IncidenceChanger::deleteIncidence() called twice, ignore this one.
867 kDebug() << "Item " << id << " already deleted or being deleted, skipping";
868
869 // Queued emit because return must be executed first, otherwise caller won't know this workId
870 emitModifyFinished(q, change->id, newItem, ResultCodeAlreadyDeleted,
871 i18n("That calendar item was already deleted, or currently being deleted."));
872 return;
873 }
874
875 const uint atomicOperationId = change->atomicOperationId;
876 const bool hasAtomicOperationId = atomicOperationId != 0;
877 if (hasAtomicOperationId &&
878 mAtomicOperations[atomicOperationId]->rolledback()) {
879 const QString errorMessage = showErrorDialog(ResultCodeRolledback, 0);
880 kError() << errorMessage;
881 emitModifyFinished(q, changeId, newItem, ResultCodeRolledback, errorMessage);
882 return;
883 }
884
885 const bool userCancelled = !handleInvitationsBeforeChange(change);
886 if (userCancelled) {
887 // User got a "You're not the organizer, do you really want to send" dialog, and said "no"
888 kDebug() << "User cancelled, giving up";
889 emitModifyFinished(q, changeId, newItem, ResultCodeUserCanceled, QString());
890 return;
891 }
892
893 QHash<Akonadi::Item::Id, int> &latestRevisionByItemId =
894 ConflictPreventer::self()->mLatestRevisionByItemId;
895 if (latestRevisionByItemId.contains(id) &&
896 latestRevisionByItemId[id] > newItem.revision()) {
897 /* When a ItemModifyJob ends, the application can still modify the old items if the user
898 * is quick because the ETM wasn't updated yet, and we'll get a STORE error, because
899 * we are not modifying the latest revision.
900 *
901 * When a job ends, we keep the new revision in mLatestRevisionByItemId
902 * so we can update the item's revision
903 */
904 newItem.setRevision(latestRevisionByItemId[id]);
905 }
906
907 Incidence::Ptr incidence = CalendarUtils::incidence(newItem);
908 { // increment revision ( KCalCore revision, not akonadi )
909 const int revision = incidence->revision();
910 incidence->setRevision(revision + 1);
911 }
912
913 // Dav Fix
914 // Don't write back remote revision since we can't make sure it is the current one
915 newItem.setRemoteRevision(QString());
916
917 if (mModificationsInProgress.contains(newItem.id())) {
918 // There's already a ItemModifyJob running for this item ID
919 // Let's wait for it to end.
920 queueModification(change);
921 } else {
922 ItemModifyJob *modifyJob = new ItemModifyJob(newItem, parentJob(change));
923 mChangeForJob.insert(modifyJob, change);
924 mDirtyFieldsByJob.insert(modifyJob, incidence->dirtyFields());
925
926 if (hasAtomicOperationId) {
927 AtomicOperation *atomic = mAtomicOperations[atomicOperationId];
928 Q_ASSERT(atomic);
929 atomic->addChange(change);
930 }
931
932 mModificationsInProgress[newItem.id()] = change;
933 // QueuedConnection because of possible sync exec calls.
934 connect(modifyJob, SIGNAL(result(KJob*)),
935 SLOT(handleModifyJobResult(KJob*)), Qt::QueuedConnection);
936 }
937}
938
939void IncidenceChanger::startAtomicOperation(const QString &operationDescription)
940{
941 if (d->mBatchOperationInProgress) {
942 kDebug() << "An atomic operation is already in progress.";
943 return;
944 }
945
946 ++d->mLatestAtomicOperationId;
947 d->mBatchOperationInProgress = true;
948
949 AtomicOperation *atomicOperation = new AtomicOperation(d, d->mLatestAtomicOperationId);
950 atomicOperation->m_description = operationDescription;
951 d->mAtomicOperations.insert(d->mLatestAtomicOperationId, atomicOperation);
952}
953
954void IncidenceChanger::endAtomicOperation()
955{
956 if (!d->mBatchOperationInProgress) {
957 kDebug() << "No atomic operation is in progress.";
958 return;
959 }
960
961 Q_ASSERT_X(d->mLatestAtomicOperationId != 0,
962 "IncidenceChanger::endAtomicOperation()",
963 "Call startAtomicOperation() first.");
964
965 Q_ASSERT(d->mAtomicOperations.contains(d->mLatestAtomicOperationId));
966 AtomicOperation *atomicOperation = d->mAtomicOperations[d->mLatestAtomicOperationId];
967 Q_ASSERT(atomicOperation);
968 atomicOperation->m_endCalled = true;
969
970 const bool allJobsCompleted = !atomicOperation->pendingJobs();
971
972 if (allJobsCompleted && atomicOperation->rolledback() &&
973 atomicOperation->m_transactionCompleted) {
974 // The transaction job already completed, we can cleanup:
975 delete d->mAtomicOperations.take(d->mLatestAtomicOperationId);
976 d->mBatchOperationInProgress = false;
977 }/* else if ( allJobsCompleted ) {
978 Q_ASSERT( atomicOperation->transaction );
979 atomicOperation->transaction->commit(); we using autocommit now
980 }*/
981}
982
983void IncidenceChanger::setShowDialogsOnError(bool enable)
984{
985 d->mShowDialogsOnError = enable;
986}
987
988bool IncidenceChanger::showDialogsOnError() const
989{
990 return d->mShowDialogsOnError;
991}
992
993void IncidenceChanger::setRespectsCollectionRights(bool respects)
994{
995 d->mRespectsCollectionRights = respects;
996}
997
998bool IncidenceChanger::respectsCollectionRights() const
999{
1000 return d->mRespectsCollectionRights;
1001}
1002
1003void IncidenceChanger::setDestinationPolicy(IncidenceChanger::DestinationPolicy destinationPolicy)
1004{
1005 d->mDestinationPolicy = destinationPolicy;
1006}
1007
1008IncidenceChanger::DestinationPolicy IncidenceChanger::destinationPolicy() const
1009{
1010 return d->mDestinationPolicy;
1011}
1012
1013void IncidenceChanger::setDefaultCollection(const Akonadi::Collection &collection)
1014{
1015 d->mDefaultCollection = collection;
1016}
1017
1018Collection IncidenceChanger::defaultCollection() const
1019{
1020 return d->mDefaultCollection;
1021}
1022
1023bool IncidenceChanger::historyEnabled() const
1024{
1025 return d->mUseHistory;
1026}
1027
1028void IncidenceChanger::setHistoryEnabled(bool enable)
1029{
1030 if (d->mUseHistory != enable) {
1031 d->mUseHistory = enable;
1032 if (enable && !d->mHistory)
1033 d->mHistory = new History(this);
1034 }
1035}
1036
1037History* IncidenceChanger::history() const
1038{
1039 return d->mHistory;
1040}
1041
1042bool IncidenceChanger::deletedRecently(Akonadi::Item::Id id) const
1043{
1044 return d->deleteAlreadyCalled(id);
1045}
1046
1047void IncidenceChanger::setGroupwareCommunication(bool enabled)
1048{
1049 d->mGroupwareCommunication = enabled;
1050}
1051
1052bool IncidenceChanger::groupwareCommunication() const
1053{
1054 return d->mGroupwareCommunication;
1055}
1056
1057void IncidenceChanger::setAutoAdjustRecurrence(bool enable)
1058{
1059 d->mAutoAdjustRecurrence = enable;
1060}
1061
1062bool IncidenceChanger::autoAdjustRecurrence() const
1063{
1064 return d->mAutoAdjustRecurrence;
1065}
1066
1067void IncidenceChanger::setInvitationPolicy(IncidenceChanger::InvitationPolicy policy)
1068{
1069 d->m_invitationPolicy = policy;
1070}
1071
1072IncidenceChanger::InvitationPolicy IncidenceChanger::invitationPolicy() const
1073{
1074 return d->m_invitationPolicy;
1075}
1076
1077Akonadi::Collection IncidenceChanger::lastCollectionUsed() const
1078{
1079 return d->mLastCollectionUsed;
1080}
1081
1082QString IncidenceChanger::Private::showErrorDialog(IncidenceChanger::ResultCode resultCode,
1083 QWidget *parent)
1084{
1085 QString errorString;
1086 switch (resultCode) {
1087 case IncidenceChanger::ResultCodePermissions:
1088 errorString = i18n("Operation can not be performed due to ACL restrictions");
1089 break;
1090 case IncidenceChanger::ResultCodeInvalidUserCollection:
1091 errorString = i18n("The chosen collection is invalid");
1092 break;
1093 case IncidenceChanger::ResultCodeInvalidDefaultCollection:
1094 errorString = i18n("Default collection is invalid or doesn't have proper ACLs"
1095 " and DestinationPolicyNeverAsk was used");
1096 break;
1097 case IncidenceChanger::ResultCodeDuplicateId:
1098 errorString = i18n("Duplicate item id in a group operation");
1099 break;
1100 case IncidenceChanger::ResultCodeRolledback:
1101 errorString = i18n("One change belonging to a group of changes failed. "
1102 "All changes are being rolled back.");
1103 break;
1104 default:
1105 Q_ASSERT(false);
1106 return QString(i18n("Unknown error"));
1107 }
1108
1109 if (mShowDialogsOnError) {
1110 KMessageBox::sorry(parent, errorString);
1111 }
1112
1113 return errorString;
1114}
1115
1116void IncidenceChanger::Private::adjustRecurrence(const KCalCore::Incidence::Ptr &originalIncidence,
1117 const KCalCore::Incidence::Ptr &incidence)
1118{
1119 if (!originalIncidence || !incidence->recurs() || incidence->hasRecurrenceId() || !mAutoAdjustRecurrence
1120 || !incidence->dirtyFields().contains(KCalCore::Incidence::FieldDtStart)) {
1121 return;
1122 }
1123
1124 const QDate originalDate = originalIncidence->dtStart().date();
1125 const QDate newStartDate = incidence->dtStart().date();
1126
1127 if (!originalDate.isValid() || !newStartDate.isValid() || originalDate == newStartDate)
1128 return;
1129
1130 KCalCore::Recurrence *recurrence = incidence->recurrence();
1131 switch (recurrence->recurrenceType()) {
1132 case KCalCore::Recurrence::rWeekly: {
1133 QBitArray days = recurrence->days();
1134 const int oldIndex = originalDate.dayOfWeek()-1; // QDate returns [1-7];
1135 const int newIndex = newStartDate.dayOfWeek()-1;
1136 if (oldIndex != newIndex) {
1137 days.clearBit(oldIndex);
1138 days.setBit(newIndex);
1139 recurrence->setWeekly(recurrence->frequency(), days);
1140 }
1141 }
1142 default:
1143 break; // Other types not implemented
1144 }
1145
1146 // Now fix cases where dtstart would be bigger than the recurrence end rendering it impossible for a view to show it:
1147 // To retrieve the recurrence end don't trust Recurrence::endDt() since it returns dtStart if the rrule's end is < than dtstart,
1148 // it seems someone made Recurrence::endDt() more robust, but getNextOccurrences() still craps out. So lets fix it here
1149 // there's no reason to write bogus ical to disk.
1150 const QDate recurrenceEndDate = recurrence->defaultRRule() ? recurrence->defaultRRule()->endDt().date() : QDate();
1151 if (recurrenceEndDate.isValid() && recurrenceEndDate < newStartDate) {
1152 recurrence->setEndDate(newStartDate);
1153 }
1154}
1155
1156void IncidenceChanger::Private::cancelTransaction()
1157{
1158 if (mBatchOperationInProgress) {
1159 mAtomicOperations[mLatestAtomicOperationId]->setRolledback();
1160 }
1161}
1162
1163void IncidenceChanger::Private::cleanupTransaction()
1164{
1165 Q_ASSERT(mAtomicOperations.contains(mLatestAtomicOperationId));
1166 AtomicOperation *operation = mAtomicOperations[mLatestAtomicOperationId];
1167 Q_ASSERT(operation);
1168 Q_ASSERT(operation->rolledback());
1169 if (!operation->pendingJobs() && operation->m_endCalled && operation->m_transactionCompleted) {
1170 delete mAtomicOperations.take(mLatestAtomicOperationId);
1171 mBatchOperationInProgress = false;
1172 }
1173}
1174
1175bool IncidenceChanger::Private::allowAtomicOperation(int atomicOperationId,
1176 const Change::Ptr &change) const
1177{
1178 bool allow = true;
1179 if (atomicOperationId > 0) {
1180 Q_ASSERT(mAtomicOperations.contains(atomicOperationId));
1181 AtomicOperation *operation = mAtomicOperations.value(atomicOperationId);
1182
1183 if (change->type == ChangeTypeCreate) {
1184 allow = true;
1185 } else if (change->type == ChangeTypeModify) {
1186 allow = !operation->m_itemIdsInOperation.contains(change->newItem.id());
1187 } else if (change->type == ChangeTypeDelete) {
1188 DeletionChange::Ptr deletion = change.staticCast<DeletionChange>();
1189 foreach(Akonadi::Item::Id id, deletion->mItemIds) {
1190 if (operation->m_itemIdsInOperation.contains(id)) {
1191 allow = false;
1192 break;
1193 }
1194 }
1195 }
1196 }
1197
1198 if (!allow) {
1199 kWarning() << "Each change belonging to a group operation"
1200 << "must have a different Akonadi::Item::Id";
1201 }
1202
1203 return allow;
1204}
1205
1207void ModificationChange::emitCompletionSignal()
1208{
1209 emitModifyFinished(changer, id, newItem, resultCode, errorString);
1210}
1211
1213void CreationChange::emitCompletionSignal()
1214{
1215 // Does a queued emit, with QMetaObject::invokeMethod
1216 emitCreateFinished(changer, id, newItem, resultCode, errorString);
1217}
1218
1220void DeletionChange::emitCompletionSignal()
1221{
1222 emitDeleteFinished(changer, id, mItemIds, resultCode, errorString);
1223}
1224
1257AtomicOperation::AtomicOperation(IncidenceChanger::Private *icp,
1258 uint ident) : m_id(ident)
1259 , m_endCalled(false)
1260 , m_numCompletedChanges(0)
1261 , m_transactionCompleted(false)
1262 , m_wasRolledback(false)
1263 , m_transaction(0)
1264 , m_incidenceChangerPrivate(icp)
1265
1266{
1267 Q_ASSERT(m_id != 0);
1268}
1269
1270Akonadi::TransactionSequence *AtomicOperation::transaction()
1271{
1272 if (!m_transaction) {
1273 m_transaction = new Akonadi::TransactionSequence;
1274 m_transaction->setAutomaticCommittingEnabled(true);
1275
1276 m_incidenceChangerPrivate->mAtomicOperationByTransaction.insert(m_transaction, m_id);
1277
1278 QObject::connect(m_transaction, SIGNAL(result(KJob*)),
1279 m_incidenceChangerPrivate, SLOT(handleTransactionJobResult(KJob*)));
1280 }
1281
1282 return m_transaction;
1283}
Akonadi::Collection
Represents a collection of PIM items.
Definition collection.h:76
Akonadi::Collection::CanDeleteItem
@ CanDeleteItem
Can delete items in this collection.
Definition collection.h:90
Akonadi::Collection::CanCreateItem
@ CanCreateItem
Can create new items in this collection.
Definition collection.h:89
Akonadi::Collection::CanChangeItem
@ CanChangeItem
Can change items in this collection.
Definition collection.h:88
Akonadi::History
History class for implementing undo/redo of calendar operations.
Definition history.h:58
Akonadi::ITIPHandlerHelper
This class handles sending of invitations to attendees when Incidences (e.g.
Definition itiphandlerhelper_p.h:67
Akonadi::ITIPHandlerHelper::SendResult
SendResult
Definition itiphandlerhelper_p.h:73
Akonadi::ITIPHandlerHelper::ResultCanceled
@ ResultCanceled
Sending was canceled by the user, meaning there are local changes of which other attendees are not aw...
Definition itiphandlerhelper_p.h:74
Akonadi::ITIPHandlerHelper::ResultFailAbortUpdate
@ ResultFailAbortUpdate
Sending failed, the changes to the incidence must be undone.
Definition itiphandlerhelper_p.h:77
Akonadi::ITIPHandlerHelper::ResultSuccess
@ ResultSuccess
The invitation was sent to all attendees.
Definition itiphandlerhelper_p.h:81
Akonadi::ItemCreateJob
Job that creates a new item in the Akonadi storage.
Definition itemcreatejob.h:74
Akonadi::ItemCreateJob::item
Item item() const
Returns the created item with the new unique id, or an invalid item if the job failed.
Definition itemcreatejob.cpp:255
Akonadi::ItemDeleteJob
Job that deletes items from the Akonadi storage.
Definition itemdeletejob.h:63
Akonadi::ItemDeleteJob::deletedItems
Item::List deletedItems() const
Returns the items passed on in the constructor.
Definition itemdeletejob.cpp:85
Akonadi::ItemModifyJob
Job that modifies an existing item in the Akonadi storage.
Definition itemmodifyjob.h:98
Akonadi::ItemModifyJob::item
Item item() const
Returns the modified and stored item including the changed revision number.
Definition itemmodifyjob.cpp:465
Akonadi::Job
Base class for all actions in the Akonadi storage.
Definition job.h:87
Akonadi::Job::errorString
virtual QString errorString() const
Returns the error string, if there has been an error, an empty string otherwise.
Definition job.cpp:301
Akonadi::TransactionSequence
Base class for jobs that need to run a sequence of sub-jobs in a transaction.
Definition transactionsequence.h:70
Akonadi::TransactionSequence::setAutomaticCommittingEnabled
void setAutomaticCommittingEnabled(bool enable)
Disable automatic committing.
Definition transactionsequence.cpp:209
Akonadi
FreeBusyManager::Singleton.
Definition actionstatemanager_p.h:28
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Wed Jan 24 2024 00:00:00 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.

akonadi

Skip menu "akonadi"
  • Main Page
  • Namespace List
  • Namespace Members
  • Alphabetical List
  • Class List
  • Class Hierarchy
  • Class Members
  • File List
  • Related Pages

kdepimlibs-4.14.10 API Reference

Skip menu "kdepimlibs-4.14.10 API Reference"
  • akonadi
  •   contact
  •   kmime
  •   socialutils
  • kabc
  • kalarmcal
  • kblog
  • kcal
  • kcalcore
  • kcalutils
  • kholidays
  • kimap
  • kioslave
  •   imap4
  •   mbox
  •   nntp
  • kldap
  • kmbox
  • kmime
  • kontactinterface
  • kpimidentities
  • kpimtextedit
  • kpimutils
  • kresources
  • ktnef
  • kxmlrpcclient
  • mailtransport
  • microblog
  • qgpgme
  • syndication
  •   atom
  •   rdf
  •   rss2
Report problems with this website to our bug tracking system.
Contact the specific authors with questions and comments about the page contents.

KDE® and the K Desktop Environment® logo are registered trademarks of KDE e.V. | Legal