20 #include "incidencechanger.h"
21 #include "incidencechanger_p.h"
22 #include "mailscheduler_p.h"
25 #include <akonadi/itemcreatejob.h>
26 #include <akonadi/itemmodifyjob.h>
27 #include <akonadi/itemdeletejob.h>
28 #include <akonadi/transactionsequence.h>
31 #include <KLocalizedString>
33 #include <KMessageBox>
34 #include <KStandardGuiItem>
38 using namespace Akonadi;
39 using namespace KCalCore;
41 #ifdef PLEASE_TEST_INVITATIONS
42 # define RUNNING_UNIT_TESTS true
44 # define RUNNING_UNIT_TESTS false
59 return ITIPHandlerHelper::ActionDontSendMessage;
61 return ITIPHandlerHelper::ActionSendMessage;
63 return ITIPHandlerHelper::ActionAsk;
69 static void emitCreateFinished(IncidenceChanger *changer,
71 const Akonadi::Item &item,
72 Akonadi::IncidenceChanger::ResultCode resultCode,
73 const QString &errorString)
75 QMetaObject::invokeMethod(changer,
"createFinished", Qt::QueuedConnection,
77 Q_ARG(Akonadi::Item, item),
78 Q_ARG(Akonadi::IncidenceChanger::ResultCode, resultCode),
79 Q_ARG(QString, errorString));
83 static void emitModifyFinished(IncidenceChanger *changer,
85 const Akonadi::Item &item,
86 IncidenceChanger::ResultCode resultCode,
87 const QString &errorString)
89 QMetaObject::invokeMethod(changer,
"modifyFinished", Qt::QueuedConnection,
91 Q_ARG(Akonadi::Item, item),
92 Q_ARG(Akonadi::IncidenceChanger::ResultCode, resultCode),
93 Q_ARG(QString, errorString));
97 static void emitDeleteFinished(IncidenceChanger *changer,
99 const QVector<Akonadi::Item::Id> &itemIdList,
100 IncidenceChanger::ResultCode resultCode,
101 const QString &errorString)
103 QMetaObject::invokeMethod(changer,
"deleteFinished", Qt::QueuedConnection,
104 Q_ARG(
int, changeId),
105 Q_ARG(QVector<Akonadi::Item::Id>, itemIdList),
106 Q_ARG(Akonadi::IncidenceChanger::ResultCode, resultCode),
107 Q_ARG(QString, errorString));
111 class ConflictPreventerPrivate;
112 class ConflictPreventer {
113 friend class ConflictPreventerPrivate;
115 static ConflictPreventer*
self();
118 QHash<Akonadi::Item::Id, int> mLatestRevisionByItemId;
120 ConflictPreventer() {}
121 ~ConflictPreventer() {}
124 class ConflictPreventerPrivate {
126 ConflictPreventer instance;
129 K_GLOBAL_STATIC(ConflictPreventerPrivate, sConflictPreventerPrivate)
131 ConflictPreventer* ConflictPreventer::self()
133 return &sConflictPreventerPrivate->instance;
136 IncidenceChanger::Private::Private(
bool enableHistory, IncidenceChanger *qq) : q(qq)
139 mShowDialogsOnError =
true;
140 mHistory = enableHistory ?
new History(
this) : 0;
141 mUseHistory = enableHistory;
142 mDestinationPolicy = DestinationPolicyDefault;
143 mRespectsCollectionRights =
false;
144 mGroupwareCommunication =
false;
145 mLatestAtomicOperationId = 0;
146 mBatchOperationInProgress =
false;
147 mAutoAdjustRecurrence =
true;
148 m_collectionFetchJob = 0;
149 m_invitationPolicy = InvitationPolicyAsk;
151 qRegisterMetaType<QVector<Akonadi::Item::Id> >(
"QVector<Akonadi::Item::Id>");
152 qRegisterMetaType<Akonadi::Item::Id>(
"Akonadi::Item::Id");
153 qRegisterMetaType<Akonadi::Item>(
"Akonadi::Item");
154 qRegisterMetaType<Akonadi::IncidenceChanger::ResultCode>(
155 "Akonadi::IncidenceChanger::ResultCode");
158 IncidenceChanger::Private::~Private()
160 if (!mAtomicOperations.isEmpty() ||
161 !mQueuedModifications.isEmpty() ||
162 !mModificationsInProgress.isEmpty()) {
163 kDebug() <<
"Normal if the application was being used. "
164 "But might indicate a memory leak if it wasn't";
168 bool IncidenceChanger::Private::atomicOperationIsValid(uint atomicOperationId)
const
171 return mAtomicOperations.contains(atomicOperationId) &&
172 !mAtomicOperations[atomicOperationId]->m_endCalled;
175 bool IncidenceChanger::Private::hasRights(
const Collection &collection,
176 IncidenceChanger::ChangeType changeType)
const
179 switch (changeType) {
180 case ChangeTypeCreate:
183 case ChangeTypeModify:
186 case ChangeTypeDelete:
190 Q_ASSERT_X(
false,
"hasRights",
"invalid type");
193 return !collection.
isValid() || !mRespectsCollectionRights || result;
196 Akonadi::Job* IncidenceChanger::Private::parentJob(
const Change::Ptr &change)
const
198 return (mBatchOperationInProgress && !change->queuedModification) ?
199 mAtomicOperations[mLatestAtomicOperationId]->transaction() : 0;
202 void IncidenceChanger::Private::queueModification(Change::Ptr change)
207 const Akonadi::Item::Id
id = change->newItem.id();
208 if (mQueuedModifications.contains(
id)) {
209 Change::Ptr toBeDiscarded = mQueuedModifications.take(
id);
210 toBeDiscarded->resultCode = ResultCodeModificationDiscarded;
211 toBeDiscarded->completed =
true;
212 mChangeById.remove(toBeDiscarded->id);
215 change->queuedModification =
true;
216 mQueuedModifications[id] = change;
219 void IncidenceChanger::Private::performNextModification(Akonadi::Item::Id
id)
221 mModificationsInProgress.remove(
id);
223 if (mQueuedModifications.contains(
id)) {
224 const Change::Ptr change = mQueuedModifications.take(
id);
225 performModification(change);
229 void IncidenceChanger::Private::handleTransactionJobResult(KJob *job)
233 Q_ASSERT(transaction);
234 Q_ASSERT(mAtomicOperationByTransaction.contains(transaction));
236 const uint atomicOperationId = mAtomicOperationByTransaction.take(transaction);
238 Q_ASSERT(mAtomicOperations.contains(atomicOperationId));
239 AtomicOperation *operation = mAtomicOperations[atomicOperationId];
241 Q_ASSERT(operation->m_id == atomicOperationId);
243 if (!operation->rolledback())
244 operation->setRolledback();
245 kError() <<
"Transaction failed, everything was rolledback. "
246 << job->errorString();
248 Q_ASSERT(operation->m_endCalled);
249 Q_ASSERT(!operation->pendingJobs());
252 if (!operation->pendingJobs() && operation->m_endCalled) {
253 delete mAtomicOperations.take(atomicOperationId);
254 mBatchOperationInProgress =
false;
256 operation->m_transactionCompleted =
true;
260 void IncidenceChanger::Private::handleCreateJobResult(KJob *job)
264 ResultCode resultCode = ResultCodeSuccess;
266 Change::Ptr change = mChangeForJob.take(job);
267 mChangeById.remove(change->id);
271 Akonadi::Item item = j->
item();
274 if (change->atomicOperationId != 0) {
275 AtomicOperation *a = mAtomicOperations[change->atomicOperationId];
276 a->m_numCompletedChanges++;
277 change->completed =
true;
278 description = a->m_description;
282 item = change->newItem;
283 resultCode = ResultCodeJobError;
285 kError() << errorString;
286 if (mShowDialogsOnError) {
287 KMessageBox::sorry(change->parentWidget,
288 i18n(
"Error while trying to create calendar item. Error was: %1",
292 Q_ASSERT(item.isValid());
293 Q_ASSERT(item.hasPayload<KCalCore::Incidence::Ptr>());
294 change->newItem = item;
295 handleInvitationsAfterChange(change);
297 if (change->recordToHistory) {
298 mHistory->recordCreation(item, description, change->atomicOperationId);
302 change->errorString = errorString;
303 change->resultCode = resultCode;
307 void IncidenceChanger::Private::handleDeleteJobResult(KJob *job)
311 ResultCode resultCode = ResultCodeSuccess;
313 Change::Ptr change = mChangeForJob.take(job);
314 mChangeById.remove(change->id);
319 QSharedPointer<DeletionChange> deletionChange = change.staticCast<DeletionChange>();
321 foreach(
const Akonadi::Item &item, items) {
322 deletionChange->mItemIds.append(item.id());
325 if (change->atomicOperationId != 0) {
326 AtomicOperation *a = mAtomicOperations[change->atomicOperationId];
327 a->m_numCompletedChanges++;
328 change->completed =
true;
329 description = a->m_description;
332 resultCode = ResultCodeJobError;
334 kError() << errorString;
335 if (mShowDialogsOnError) {
336 KMessageBox::sorry(change->parentWidget,
337 i18n(
"Error while trying to delete calendar item. Error was: %1",
341 foreach(
const Item &item, items) {
343 mDeletedItemIds.remove(mDeletedItemIds.indexOf(item.id()));
346 if (change->recordToHistory) {
348 mHistory->recordDeletions(items, description, change->atomicOperationId);
351 handleInvitationsAfterChange(change);
354 change->errorString = errorString;
355 change->resultCode = resultCode;
359 void IncidenceChanger::Private::handleModifyJobResult(KJob *job)
362 ResultCode resultCode = ResultCodeSuccess;
363 Change::Ptr change = mChangeForJob.take(job);
364 mChangeById.remove(change->id);
367 const Item item = j->
item();
368 Q_ASSERT(mDirtyFieldsByJob.contains(job));
369 Q_ASSERT(item.hasPayload<KCalCore::Incidence::Ptr>());
370 item.payload<KCalCore::Incidence::Ptr>()->setDirtyFields(mDirtyFieldsByJob.value(job));
371 const QSet<KCalCore::IncidenceBase::Field> dirtyFields = mDirtyFieldsByJob.value(job);
373 if (change->atomicOperationId != 0) {
374 AtomicOperation *a = mAtomicOperations[change->atomicOperationId];
375 a->m_numCompletedChanges++;
376 change->completed =
true;
377 description = a->m_description;
380 if (deleteAlreadyCalled(item.id())) {
384 resultCode = ResultCodeAlreadyDeleted;
386 kWarning() <<
"Trying to change item " << item.id() <<
" while deletion is in progress.";
388 resultCode = ResultCodeJobError;
390 kError() << errorString;
392 if (mShowDialogsOnError) {
393 KMessageBox::sorry(change->parentWidget,
394 i18n(
"Error while trying to modify calendar item. Error was: %1",
398 ConflictPreventer::self()->mLatestRevisionByItemId[item.id()] = item.revision();
399 change->newItem = item;
400 if (change->recordToHistory && !change->originalItems.isEmpty()) {
401 Q_ASSERT(change->originalItems.count() == 1);
402 mHistory->recordModification(change->originalItems.first(), item,
403 description, change->atomicOperationId);
406 handleInvitationsAfterChange(change);
409 change->errorString = errorString;
410 change->resultCode = resultCode;
413 QMetaObject::invokeMethod(
this,
"performNextModification",
414 Qt::QueuedConnection,
415 Q_ARG(Akonadi::Item::Id, item.id()));
418 bool IncidenceChanger::Private::deleteAlreadyCalled(Akonadi::Item::Id
id)
const
420 return mDeletedItemIds.contains(
id);
423 bool IncidenceChanger::Private::handleInvitationsBeforeChange(
const Change::Ptr &change)
426 if (mGroupwareCommunication) {
429 if (m_invitationPolicy == InvitationPolicySend) {
430 handler.setDefaultAction(ITIPHandlerHelper::ActionSendMessage);
431 }
else if (m_invitationPolicy == InvitationPolicyDontSend) {
432 handler.setDefaultAction(ITIPHandlerHelper::ActionDontSendMessage);
433 }
else if (mInvitationStatusByAtomicOperation.contains(change->atomicOperationId)) {
434 handler.setDefaultAction(actionFromStatus(mInvitationStatusByAtomicOperation.value(change->atomicOperationId)));
437 switch (change->type) {
438 case IncidenceChanger::ChangeTypeCreate:
441 case IncidenceChanger::ChangeTypeDelete:
444 Q_ASSERT(!change->originalItems.isEmpty());
445 foreach(
const Akonadi::Item &item, change->originalItems) {
446 Q_ASSERT(item.hasPayload<KCalCore::Incidence::Ptr>());
447 Incidence::Ptr incidence = CalendarUtils::incidence(item);
448 if (!incidence->supportsGroupwareCommunication()) {
453 if (Akonadi::CalendarUtils::thatIsMe(incidence->organizer()->email())) {
454 status = handler.sendIncidenceDeletedMessage(KCalCore::iTIPCancel, incidence);
455 if (change->atomicOperationId) {
456 mInvitationStatusByAtomicOperation.insert(change->atomicOperationId, status);
458 result = status != ITIPHandlerHelper::ResultFailAbortUpdate;
464 case IncidenceChanger::ChangeTypeModify:
466 if (change->originalItems.isEmpty()) {
470 Q_ASSERT(change->originalItems.count() == 1);
471 Incidence::Ptr oldIncidence = CalendarUtils::incidence(change->originalItems.first());
472 Incidence::Ptr newIncidence = CalendarUtils::incidence(change->newItem);
474 if (!oldIncidence->supportsGroupwareCommunication()) {
478 const bool weAreOrganizer = Akonadi::CalendarUtils::thatIsMe(newIncidence->organizer()->email());
479 if (RUNNING_UNIT_TESTS && !weAreOrganizer) {
483 if (m_invitationPolicy == InvitationPolicySend) {
485 }
else if (m_invitationPolicy == InvitationPolicyDontSend) {
490 const bool modify = handler.handleIncidenceAboutToBeModified(newIncidence);
495 if (newIncidence->type() == oldIncidence->type()) {
496 IncidenceBase *i1 = newIncidence.data();
497 IncidenceBase *i2 = oldIncidence.data();
511 bool IncidenceChanger::Private::handleInvitationsAfterChange(
const Change::Ptr &change)
513 if (change->useGroupwareCommunication) {
516 const bool alwaysSend = m_invitationPolicy == InvitationPolicySend;
517 const bool neverSend = m_invitationPolicy == InvitationPolicyDontSend;
519 handler.setDefaultAction(ITIPHandlerHelper::ActionSendMessage);
523 handler.setDefaultAction(ITIPHandlerHelper::ActionDontSendMessage);
526 switch (change->type) {
527 case IncidenceChanger::ChangeTypeCreate:
529 Incidence::Ptr incidence = CalendarUtils::incidence(change->newItem);
530 if (incidence->supportsGroupwareCommunication()) {
532 handler.sendIncidenceCreatedMessage(KCalCore::iTIPRequest, incidence);
534 if (status == ITIPHandlerHelper::ResultFailAbortUpdate) {
535 kError() <<
"Sending invitations failed, but did not delete the incidence";
538 const uint atomicOperationId = change->atomicOperationId;
539 if (atomicOperationId != 0) {
540 mInvitationStatusByAtomicOperation.insert(atomicOperationId, status);
545 case IncidenceChanger::ChangeTypeDelete:
547 Q_ASSERT(!change->originalItems.isEmpty());
548 foreach(
const Akonadi::Item &item, change->originalItems) {
549 Q_ASSERT(item.hasPayload());
550 Incidence::Ptr incidence = CalendarUtils::incidence(item);
552 if (!incidence->supportsGroupwareCommunication())
555 if (!Akonadi::CalendarUtils::thatIsMe(incidence->organizer()->email())) {
556 const QStringList myEmails = Akonadi::CalendarUtils::allEmails();
557 bool notifyOrganizer =
false;
558 for (QStringList::ConstIterator it = myEmails.begin(); it != myEmails.end(); ++it) {
559 const QString email = *it;
560 KCalCore::Attendee::Ptr me(incidence->attendeeByMail(email));
562 if (me->status() == KCalCore::Attendee::Accepted ||
563 me->status() == KCalCore::Attendee::Delegated) {
564 notifyOrganizer =
true;
566 KCalCore::Attendee::Ptr newMe(
new KCalCore::Attendee(*me));
567 newMe->setStatus(KCalCore::Attendee::Declined);
568 incidence->clearAttendees();
569 incidence->addAttendee(newMe);
574 if (notifyOrganizer) {
575 MailScheduler scheduler;
576 scheduler.performTransaction(incidence, KCalCore::iTIPReply);
582 case IncidenceChanger::ChangeTypeModify:
584 if (change->originalItems.isEmpty()) {
588 Q_ASSERT(change->originalItems.count() == 1);
589 Incidence::Ptr oldIncidence = CalendarUtils::incidence(change->originalItems.first());
590 Incidence::Ptr newIncidence = CalendarUtils::incidence(change->newItem);
592 if (!newIncidence->supportsGroupwareCommunication() ||
593 !Akonadi::CalendarUtils::thatIsMe(newIncidence->organizer()->email())) {
598 if (!neverSend && !alwaysSend && mInvitationStatusByAtomicOperation.contains(change->atomicOperationId)) {
599 handler.setDefaultAction(actionFromStatus(mInvitationStatusByAtomicOperation.value(change->atomicOperationId)));
602 const bool attendeeStatusChanged = myAttendeeStatusChanged(newIncidence,
604 Akonadi::CalendarUtils::allEmails());
608 attendeeStatusChanged);
610 if (change->atomicOperationId != 0) {
611 mInvitationStatusByAtomicOperation.insert(change->atomicOperationId, status);
624 bool IncidenceChanger::Private::myAttendeeStatusChanged(
const Incidence::Ptr &newInc,
625 const Incidence::Ptr &oldInc,
626 const QStringList &myEmails)
630 const Attendee::Ptr oldMe = oldInc->attendeeByMails(myEmails);
631 const Attendee::Ptr newMe = newInc->attendeeByMails(myEmails);
633 return oldMe && newMe && oldMe->status() != newMe->status();
636 IncidenceChanger::IncidenceChanger(QObject *parent) : QObject(parent)
637 , d(new Private(true, this))
641 IncidenceChanger::IncidenceChanger(
bool enableHistory, QObject *parent) : QObject(parent)
642 , d(new Private(enableHistory, this))
646 IncidenceChanger::~IncidenceChanger()
651 int IncidenceChanger::createIncidence(
const Incidence::Ptr &incidence,
657 kWarning() <<
"An invalid payload is not allowed.";
658 d->cancelTransaction();
662 const uint atomicOperationId = d->mBatchOperationInProgress ? d->mLatestAtomicOperationId : 0;
664 const Change::Ptr change(
new CreationChange(
this, ++d->mLatestChangeId,
665 atomicOperationId, parent));
666 const int changeId = change->id;
667 Q_ASSERT(!(d->mBatchOperationInProgress && !d->mAtomicOperations.contains(atomicOperationId)));
668 if (d->mBatchOperationInProgress && d->mAtomicOperations[atomicOperationId]->rolledback()) {
669 const QString errorMessage = d->showErrorDialog(ResultCodeRolledback, parent);
670 kWarning() << errorMessage;
672 change->resultCode = ResultCodeRolledback;
673 change->errorString = errorMessage;
674 d->cleanupTransaction();
679 item.setPayload<KCalCore::Incidence::Ptr>(incidence);
680 item.setMimeType(incidence->mimeType());
682 change->newItem = item;
684 d->step1DetermineDestinationCollection(change, collection);
689 int IncidenceChanger::deleteIncidence(
const Item &item, QWidget *parent)
694 return deleteIncidences(list, parent);
697 int IncidenceChanger::deleteIncidences(
const Item::List &items, QWidget *parent)
700 if (items.isEmpty()) {
701 kError() <<
"Delete what?";
702 d->cancelTransaction();
706 foreach(
const Item &item, items) {
707 if (!item.isValid()) {
708 kError() <<
"Items must be valid!";
709 d->cancelTransaction();
714 const uint atomicOperationId = d->mBatchOperationInProgress ? d->mLatestAtomicOperationId : 0;
715 const int changeId = ++d->mLatestChangeId;
716 const Change::Ptr change(
new DeletionChange(
this, changeId, atomicOperationId, parent));
718 foreach(
const Item &item, items) {
719 if (!d->hasRights(item.parentCollection(), ChangeTypeDelete)) {
720 kWarning() <<
"Item " << item.id() <<
" can't be deleted due to ACL restrictions";
721 const QString errorString = d->showErrorDialog(ResultCodePermissions, parent);
722 change->resultCode = ResultCodePermissions;
723 change->errorString = errorString;
724 d->cancelTransaction();
729 if (!d->allowAtomicOperation(atomicOperationId, change)) {
730 const QString errorString = d->showErrorDialog(ResultCodeDuplicateId, parent);
731 change->resultCode = ResultCodeDuplicateId;
732 change->errorString = errorString;
733 kWarning() << errorString;
734 d->cancelTransaction();
738 Item::List itemsToDelete;
739 foreach(
const Item &item, items) {
740 if (d->deleteAlreadyCalled(item.id())) {
742 kDebug() <<
"Item " << item.id() <<
" already deleted or being deleted, skipping";
744 itemsToDelete.append(item);
748 if (d->mBatchOperationInProgress && d->mAtomicOperations[atomicOperationId]->rolledback()) {
749 const QString errorMessage = d->showErrorDialog(ResultCodeRolledback, parent);
750 change->resultCode = ResultCodeRolledback;
751 change->errorString = errorMessage;
752 kError() << errorMessage;
753 d->cleanupTransaction();
757 if (itemsToDelete.isEmpty()) {
758 QVector<Akonadi::Item::Id> itemIdList;
759 itemIdList.append(Item().
id());
760 kDebug() <<
"Items already deleted or being deleted, skipping";
761 const QString errorMessage =
762 i18n(
"That calendar item was already deleted, or currently being deleted.");
764 change->resultCode = ResultCodeAlreadyDeleted;
765 change->errorString = errorMessage;
766 d->cancelTransaction();
767 kWarning() << errorMessage;
770 change->originalItems = itemsToDelete;
771 d->handleInvitationsBeforeChange(change);
774 d->mChangeForJob.insert(deleteJob, change);
775 d->mChangeById.insert(changeId, change);
777 if (d->mBatchOperationInProgress) {
778 AtomicOperation *atomic = d->mAtomicOperations[atomicOperationId];
780 atomic->addChange(change);
783 foreach(
const Item &item, itemsToDelete) {
784 d->mDeletedItemIds << item.id();
788 if (d->mDeletedItemIds.count() > 100)
789 d->mDeletedItemIds.remove(0, 50);
792 connect(deleteJob, SIGNAL(result(KJob*)),
793 d, SLOT(handleDeleteJobResult(KJob*)), Qt::QueuedConnection);
798 int IncidenceChanger::modifyIncidence(
const Item &changedItem,
799 const KCalCore::Incidence::Ptr &originalPayload,
802 if (!changedItem.isValid() || !changedItem.hasPayload<Incidence::Ptr>()) {
803 kWarning() <<
"An invalid item or payload is not allowed.";
804 d->cancelTransaction();
808 if (!d->hasRights(changedItem.parentCollection(), ChangeTypeModify)) {
809 kWarning() <<
"Item " << changedItem.id() <<
" can't be deleted due to ACL restrictions";
810 const int changeId = ++d->mLatestChangeId;
811 const QString errorString = d->showErrorDialog(ResultCodePermissions, parent);
812 emitModifyFinished(
this, changeId, changedItem, ResultCodePermissions, errorString);
813 d->cancelTransaction();
818 changedItem.payload<Incidence::Ptr>()->setLastModified(KDateTime::currentUtcDateTime());
820 const uint atomicOperationId = d->mBatchOperationInProgress ? d->mLatestAtomicOperationId : 0;
821 const int changeId = ++d->mLatestChangeId;
822 ModificationChange *modificationChange =
new ModificationChange(
this, changeId,
823 atomicOperationId, parent);
824 Change::Ptr change(modificationChange);
826 if (originalPayload) {
827 Item originalItem(changedItem);
828 originalItem.setPayload<KCalCore::Incidence::Ptr>(originalPayload);
829 modificationChange->originalItems << originalItem;
832 modificationChange->newItem = changedItem;
833 d->mChangeById.insert(changeId, change);
835 if (!d->allowAtomicOperation(atomicOperationId, change)) {
836 const QString errorString = d->showErrorDialog(ResultCodeDuplicateId, parent);
837 change->resultCode = ResultCodeDuplicateId;
838 change->errorString = errorString;
839 d->cancelTransaction();
840 kWarning() <<
"Atomic operation now allowed";
844 if (d->mBatchOperationInProgress && d->mAtomicOperations[atomicOperationId]->rolledback()) {
845 const QString errorMessage = d->showErrorDialog(ResultCodeRolledback, parent);
846 kError() << errorMessage;
847 d->cleanupTransaction();
848 emitModifyFinished(
this, changeId, changedItem, ResultCodeRolledback, errorMessage);
850 d->adjustRecurrence(originalPayload, CalendarUtils::incidence(modificationChange->newItem));
851 d->performModification(change);
857 void IncidenceChanger::Private::performModification(Change::Ptr change)
859 const Item::Id
id = change->newItem.id();
860 Akonadi::Item &newItem = change->newItem;
861 Q_ASSERT(newItem.isValid());
862 Q_ASSERT(newItem.hasPayload<Incidence::Ptr>());
864 const int changeId = change->id;
866 if (deleteAlreadyCalled(
id)) {
868 kDebug() <<
"Item " <<
id <<
" already deleted or being deleted, skipping";
871 emitModifyFinished(q, change->id, newItem, ResultCodeAlreadyDeleted,
872 i18n(
"That calendar item was already deleted, or currently being deleted."));
876 const uint atomicOperationId = change->atomicOperationId;
877 const bool hasAtomicOperationId = atomicOperationId != 0;
878 if (hasAtomicOperationId &&
879 mAtomicOperations[atomicOperationId]->rolledback()) {
880 const QString errorMessage = showErrorDialog(ResultCodeRolledback, 0);
881 kError() << errorMessage;
882 emitModifyFinished(q, changeId, newItem, ResultCodeRolledback, errorMessage);
886 const bool userCancelled = !handleInvitationsBeforeChange(change);
889 kDebug() <<
"User cancelled, giving up";
890 emitModifyFinished(q, changeId, newItem, ResultCodeUserCanceled, QString());
894 QHash<Akonadi::Item::Id, int> &latestRevisionByItemId =
895 ConflictPreventer::self()->mLatestRevisionByItemId;
896 if (latestRevisionByItemId.contains(
id) &&
897 latestRevisionByItemId[id] > newItem.revision()) {
905 newItem.setRevision(latestRevisionByItemId[
id]);
908 Incidence::Ptr incidence = CalendarUtils::incidence(newItem);
910 const int revision = incidence->revision();
911 incidence->setRevision(revision + 1);
916 newItem.setRemoteRevision(QString());
918 if (mModificationsInProgress.contains(newItem.id())) {
921 queueModification(change);
924 mChangeForJob.insert(modifyJob, change);
925 mDirtyFieldsByJob.insert(modifyJob, incidence->dirtyFields());
927 if (hasAtomicOperationId) {
928 AtomicOperation *atomic = mAtomicOperations[atomicOperationId];
930 atomic->addChange(change);
933 mModificationsInProgress[newItem.id()] = change;
935 connect(modifyJob, SIGNAL(result(KJob*)),
936 SLOT(handleModifyJobResult(KJob*)), Qt::QueuedConnection);
940 void IncidenceChanger::startAtomicOperation(
const QString &operationDescription)
942 if (d->mBatchOperationInProgress) {
943 kDebug() <<
"An atomic operation is already in progress.";
947 ++d->mLatestAtomicOperationId;
948 d->mBatchOperationInProgress =
true;
950 AtomicOperation *atomicOperation =
new AtomicOperation(d, d->mLatestAtomicOperationId);
951 atomicOperation->m_description = operationDescription;
952 d->mAtomicOperations.insert(d->mLatestAtomicOperationId, atomicOperation);
955 void IncidenceChanger::endAtomicOperation()
957 if (!d->mBatchOperationInProgress) {
958 kDebug() <<
"No atomic operation is in progress.";
962 Q_ASSERT_X(d->mLatestAtomicOperationId != 0,
963 "IncidenceChanger::endAtomicOperation()",
964 "Call startAtomicOperation() first.");
966 Q_ASSERT(d->mAtomicOperations.contains(d->mLatestAtomicOperationId));
967 AtomicOperation *atomicOperation = d->mAtomicOperations[d->mLatestAtomicOperationId];
968 Q_ASSERT(atomicOperation);
969 atomicOperation->m_endCalled =
true;
971 const bool allJobsCompleted = !atomicOperation->pendingJobs();
973 if (allJobsCompleted && atomicOperation->rolledback() &&
974 atomicOperation->m_transactionCompleted) {
976 delete d->mAtomicOperations.take(d->mLatestAtomicOperationId);
977 d->mBatchOperationInProgress =
false;
984 void IncidenceChanger::setShowDialogsOnError(
bool enable)
986 d->mShowDialogsOnError = enable;
989 bool IncidenceChanger::showDialogsOnError()
const
991 return d->mShowDialogsOnError;
994 void IncidenceChanger::setRespectsCollectionRights(
bool respects)
996 d->mRespectsCollectionRights = respects;
999 bool IncidenceChanger::respectsCollectionRights()
const
1001 return d->mRespectsCollectionRights;
1004 void IncidenceChanger::setDestinationPolicy(IncidenceChanger::DestinationPolicy destinationPolicy)
1006 d->mDestinationPolicy = destinationPolicy;
1009 IncidenceChanger::DestinationPolicy IncidenceChanger::destinationPolicy()
const
1011 return d->mDestinationPolicy;
1016 d->mDefaultCollection = collection;
1019 Collection IncidenceChanger::defaultCollection()
const
1021 return d->mDefaultCollection;
1024 bool IncidenceChanger::historyEnabled()
const
1026 return d->mUseHistory;
1029 void IncidenceChanger::setHistoryEnabled(
bool enable)
1031 if (d->mUseHistory != enable) {
1032 d->mUseHistory = enable;
1033 if (enable && !d->mHistory)
1034 d->mHistory =
new History(
this);
1038 History* IncidenceChanger::history()
const
1043 bool IncidenceChanger::deletedRecently(Akonadi::Item::Id
id)
const
1045 return d->deleteAlreadyCalled(
id);
1048 void IncidenceChanger::setGroupwareCommunication(
bool enabled)
1050 d->mGroupwareCommunication = enabled;
1053 bool IncidenceChanger::groupwareCommunication()
const
1055 return d->mGroupwareCommunication;
1058 void IncidenceChanger::setAutoAdjustRecurrence(
bool enable)
1060 d->mAutoAdjustRecurrence = enable;
1063 bool IncidenceChanger::autoAdjustRecurrence()
const
1065 return d->mAutoAdjustRecurrence;
1068 void IncidenceChanger::setInvitationPolicy(IncidenceChanger::InvitationPolicy policy)
1070 d->m_invitationPolicy = policy;
1073 IncidenceChanger::InvitationPolicy IncidenceChanger::invitationPolicy()
const
1075 return d->m_invitationPolicy;
1080 return d->mLastCollectionUsed;
1083 QString IncidenceChanger::Private::showErrorDialog(IncidenceChanger::ResultCode resultCode,
1086 QString errorString;
1087 switch (resultCode) {
1088 case IncidenceChanger::ResultCodePermissions:
1089 errorString = i18n(
"Operation can not be performed due to ACL restrictions");
1091 case IncidenceChanger::ResultCodeInvalidUserCollection:
1092 errorString = i18n(
"The chosen collection is invalid");
1094 case IncidenceChanger::ResultCodeInvalidDefaultCollection:
1095 errorString = i18n(
"Default collection is invalid or doesn't have proper ACLs"
1096 " and DestinationPolicyNeverAsk was used");
1098 case IncidenceChanger::ResultCodeDuplicateId:
1099 errorString = i18n(
"Duplicate item id in a group operation");
1101 case IncidenceChanger::ResultCodeRolledback:
1102 errorString = i18n(
"One change belonging to a group of changes failed. "
1103 "All changes are being rolled back.");
1107 return QString(i18n(
"Unknown error"));
1110 if (mShowDialogsOnError) {
1111 KMessageBox::sorry(parent, errorString);
1117 void IncidenceChanger::Private::adjustRecurrence(
const KCalCore::Incidence::Ptr &originalIncidence,
1118 const KCalCore::Incidence::Ptr &incidence)
1120 if (!originalIncidence || !incidence->recurs() || incidence->hasRecurrenceId() || !mAutoAdjustRecurrence
1121 || !incidence->dirtyFields().contains(KCalCore::Incidence::FieldDtStart)) {
1125 const QDate originalDate = originalIncidence->dtStart().date();
1126 const QDate newStartDate = incidence->dtStart().date();
1128 if (!originalDate.isValid() || !newStartDate.isValid() || originalDate == newStartDate)
1131 KCalCore::Recurrence *recurrence = incidence->recurrence();
1132 switch (recurrence->recurrenceType()) {
1133 case KCalCore::Recurrence::rWeekly: {
1134 QBitArray days = recurrence->days();
1135 const int oldIndex = originalDate.dayOfWeek()-1;
1136 const int newIndex = newStartDate.dayOfWeek()-1;
1137 if (oldIndex != newIndex) {
1138 days.clearBit(oldIndex);
1139 days.setBit(newIndex);
1140 recurrence->setWeekly(recurrence->frequency(), days);
1151 const QDate recurrenceEndDate = recurrence->defaultRRule() ? recurrence->defaultRRule()->endDt().date() : QDate();
1152 if (recurrenceEndDate.isValid() && recurrenceEndDate < newStartDate) {
1153 recurrence->setEndDate(newStartDate);
1157 void IncidenceChanger::Private::cancelTransaction()
1159 if (mBatchOperationInProgress) {
1160 mAtomicOperations[mLatestAtomicOperationId]->setRolledback();
1164 void IncidenceChanger::Private::cleanupTransaction()
1166 Q_ASSERT(mAtomicOperations.contains(mLatestAtomicOperationId));
1167 AtomicOperation *operation = mAtomicOperations[mLatestAtomicOperationId];
1168 Q_ASSERT(operation);
1169 Q_ASSERT(operation->rolledback());
1170 if (!operation->pendingJobs() && operation->m_endCalled && operation->m_transactionCompleted) {
1171 delete mAtomicOperations.take(mLatestAtomicOperationId);
1172 mBatchOperationInProgress =
false;
1176 bool IncidenceChanger::Private::allowAtomicOperation(
int atomicOperationId,
1177 const Change::Ptr &change)
const
1180 if (atomicOperationId > 0) {
1181 Q_ASSERT(mAtomicOperations.contains(atomicOperationId));
1182 AtomicOperation *operation = mAtomicOperations.value(atomicOperationId);
1184 if (change->type == ChangeTypeCreate) {
1186 }
else if (change->type == ChangeTypeModify) {
1187 allow = !operation->m_itemIdsInOperation.contains(change->newItem.id());
1188 }
else if (change->type == ChangeTypeDelete) {
1189 DeletionChange::Ptr deletion = change.staticCast<DeletionChange>();
1190 foreach(Akonadi::Item::Id
id, deletion->mItemIds) {
1191 if (operation->m_itemIdsInOperation.contains(
id)) {
1200 kWarning() <<
"Each change belonging to a group operation"
1201 <<
"must have a different Akonadi::Item::Id";
1208 void ModificationChange::emitCompletionSignal()
1210 emitModifyFinished(changer,
id, newItem, resultCode, errorString);
1214 void CreationChange::emitCompletionSignal()
1217 emitCreateFinished(changer,
id, newItem, resultCode, errorString);
1221 void DeletionChange::emitCompletionSignal()
1223 emitDeleteFinished(changer,
id, mItemIds, resultCode, errorString);
1258 AtomicOperation::AtomicOperation(IncidenceChanger::Private *icp,
1259 uint ident) : m_id(ident)
1260 , m_endCalled(false)
1261 , m_numCompletedChanges(0)
1262 , m_transactionCompleted(false)
1263 , m_wasRolledback(false)
1265 , m_incidenceChangerPrivate(icp)
1268 Q_ASSERT(m_id != 0);
1273 if (!m_transaction) {
1277 m_incidenceChangerPrivate->mAtomicOperationByTransaction.insert(m_transaction, m_id);
1279 QObject::connect(m_transaction, SIGNAL(result(KJob*)),
1280 m_incidenceChangerPrivate, SLOT(handleTransactionJobResult(KJob*)));
1283 return m_transaction;
Item item() const
Returns the created item with the new unique id, or an invalid item if the job failed.
Represents a collection of PIM items.
Item item() const
Returns the modified and stored item including the changed revision number.
Base class for all actions in the Akonadi storage.
Can change items in this collection.
void setAutomaticCommittingEnabled(bool enable)
Disable automatic committing.
Job that deletes items from the Akonadi storage.
Can delete items in this collection.
History class for implementing undo/redo of calendar operations.
Can create new items in this collection.
The invitation was sent to all attendees.
Job that creates a new item in the Akonadi storage.
Rights rights() const
Returns the rights the user has on the collection.
This class handles sending of invitations to attendees when Incidences (e.g.
Sending was canceled by the user, meaning there are local changes of which other attendees are not aw...
Base class for jobs that need to run a sequence of sub-jobs in a transaction.
Job that modifies an existing item in the Akonadi storage.
virtual QString errorString() const
Returns the error string, if there has been an error, an empty string otherwise.
bool isValid() const
Returns whether the entity is valid.
Item::List deletedItems() const
Returns the items passed on in the constructor.