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>
38 using namespace KCalCore;
40 #ifdef PLEASE_TEST_INVITATIONS
41 # define RUNNING_UNIT_TESTS true
43 # define RUNNING_UNIT_TESTS false
58 return ITIPHandlerHelper::ActionDontSendMessage;
60 return ITIPHandlerHelper::ActionSendMessage;
62 return ITIPHandlerHelper::ActionAsk;
68 static void emitCreateFinished(IncidenceChanger *changer,
70 const Akonadi::Item &item,
71 Akonadi::IncidenceChanger::ResultCode resultCode,
72 const QString &errorString)
74 QMetaObject::invokeMethod(changer,
"createFinished", Qt::QueuedConnection,
76 Q_ARG(Akonadi::Item, item),
77 Q_ARG(Akonadi::IncidenceChanger::ResultCode, resultCode),
78 Q_ARG(QString, errorString));
82 static void emitModifyFinished(IncidenceChanger *changer,
84 const Akonadi::Item &item,
85 IncidenceChanger::ResultCode resultCode,
86 const QString &errorString)
88 QMetaObject::invokeMethod(changer,
"modifyFinished", Qt::QueuedConnection,
90 Q_ARG(Akonadi::Item, item),
91 Q_ARG(Akonadi::IncidenceChanger::ResultCode, resultCode),
92 Q_ARG(QString, errorString));
96 static void emitDeleteFinished(IncidenceChanger *changer,
98 const QVector<Akonadi::Item::Id> &itemIdList,
99 IncidenceChanger::ResultCode resultCode,
100 const QString &errorString)
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));
110 class ConflictPreventerPrivate;
111 class ConflictPreventer {
112 friend class ConflictPreventerPrivate;
114 static ConflictPreventer*
self();
117 QHash<Akonadi::Item::Id, int> mLatestRevisionByItemId;
119 ConflictPreventer() {}
120 ~ConflictPreventer() {}
123 class ConflictPreventerPrivate {
125 ConflictPreventer instance;
128 K_GLOBAL_STATIC(ConflictPreventerPrivate, sConflictPreventerPrivate)
130 ConflictPreventer* ConflictPreventer::self()
132 return &sConflictPreventerPrivate->instance;
135 IncidenceChanger::Private::Private(
bool enableHistory, IncidenceChanger *qq) : q(qq)
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;
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");
157 IncidenceChanger::Private::~Private()
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";
167 bool IncidenceChanger::Private::atomicOperationIsValid(uint atomicOperationId)
const
170 return mAtomicOperations.contains(atomicOperationId) &&
171 !mAtomicOperations[atomicOperationId]->m_endCalled;
174 bool IncidenceChanger::Private::hasRights(
const Collection &collection,
175 IncidenceChanger::ChangeType changeType)
const
178 switch (changeType) {
179 case ChangeTypeCreate:
182 case ChangeTypeModify:
185 case ChangeTypeDelete:
189 Q_ASSERT_X(
false,
"hasRights",
"invalid type");
192 return !collection.isValid() || !mRespectsCollectionRights || result;
195 Akonadi::Job* IncidenceChanger::Private::parentJob(
const Change::Ptr &change)
const
197 return (mBatchOperationInProgress && !change->queuedModification) ?
198 mAtomicOperations[mLatestAtomicOperationId]->transaction() : 0;
201 void IncidenceChanger::Private::queueModification(Change::Ptr change)
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);
214 change->queuedModification =
true;
215 mQueuedModifications[id] = change;
218 void IncidenceChanger::Private::performNextModification(Akonadi::Item::Id
id)
220 mModificationsInProgress.remove(
id);
222 if (mQueuedModifications.contains(
id)) {
223 const Change::Ptr change = mQueuedModifications.take(
id);
224 performModification(change);
228 void IncidenceChanger::Private::handleTransactionJobResult(KJob *job)
232 Q_ASSERT(transaction);
233 Q_ASSERT(mAtomicOperationByTransaction.contains(transaction));
235 const uint atomicOperationId = mAtomicOperationByTransaction.take(transaction);
237 Q_ASSERT(mAtomicOperations.contains(atomicOperationId));
238 AtomicOperation *operation = mAtomicOperations[atomicOperationId];
240 Q_ASSERT(operation->m_id == atomicOperationId);
242 if (!operation->rolledback())
243 operation->setRolledback();
244 kError() <<
"Transaction failed, everything was rolledback. "
245 << job->errorString();
247 Q_ASSERT(operation->m_endCalled);
248 Q_ASSERT(!operation->pendingJobs());
251 if (!operation->pendingJobs() && operation->m_endCalled) {
252 delete mAtomicOperations.take(atomicOperationId);
253 mBatchOperationInProgress =
false;
255 operation->m_transactionCompleted =
true;
259 void IncidenceChanger::Private::handleCreateJobResult(KJob *job)
263 ResultCode resultCode = ResultCodeSuccess;
265 Change::Ptr change = mChangeForJob.take(job);
266 mChangeById.remove(change->id);
268 const ItemCreateJob *j = qobject_cast<const ItemCreateJob*>(job);
270 Akonadi::Item item = j->
item();
273 if (change->atomicOperationId != 0) {
274 AtomicOperation *a = mAtomicOperations[change->atomicOperationId];
275 a->m_numCompletedChanges++;
276 change->completed =
true;
277 description = a->m_description;
281 item = change->newItem;
282 resultCode = ResultCodeJobError;
284 kError() << errorString;
285 if (mShowDialogsOnError) {
286 KMessageBox::sorry(change->parentWidget,
287 i18n(
"Error while trying to create calendar item. Error was: %1",
291 Q_ASSERT(item.isValid());
292 Q_ASSERT(item.hasPayload<KCalCore::Incidence::Ptr>());
293 change->newItem = item;
294 handleInvitationsAfterChange(change);
296 if (change->recordToHistory) {
297 mHistory->recordCreation(item, description, change->atomicOperationId);
301 change->errorString = errorString;
302 change->resultCode = resultCode;
306 void IncidenceChanger::Private::handleDeleteJobResult(KJob *job)
310 ResultCode resultCode = ResultCodeSuccess;
312 Change::Ptr change = mChangeForJob.take(job);
313 mChangeById.remove(change->id);
315 const ItemDeleteJob *j = qobject_cast<const ItemDeleteJob*>(job);
318 QSharedPointer<DeletionChange> deletionChange = change.staticCast<DeletionChange>();
320 foreach(
const Akonadi::Item &item, items) {
321 deletionChange->mItemIds.append(item.id());
324 if (change->atomicOperationId != 0) {
325 AtomicOperation *a = mAtomicOperations[change->atomicOperationId];
326 a->m_numCompletedChanges++;
327 change->completed =
true;
328 description = a->m_description;
331 resultCode = ResultCodeJobError;
333 kError() << errorString;
334 if (mShowDialogsOnError) {
335 KMessageBox::sorry(change->parentWidget,
336 i18n(
"Error while trying to delete calendar item. Error was: %1",
340 foreach(
const Item &item, items) {
342 mDeletedItemIds.remove(mDeletedItemIds.indexOf(item.id()));
345 if (change->recordToHistory) {
347 mHistory->recordDeletions(items, description, change->atomicOperationId);
350 handleInvitationsAfterChange(change);
353 change->errorString = errorString;
354 change->resultCode = resultCode;
358 void IncidenceChanger::Private::handleModifyJobResult(KJob *job)
361 ResultCode resultCode = ResultCodeSuccess;
362 Change::Ptr change = mChangeForJob.take(job);
363 mChangeById.remove(change->id);
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);
372 if (change->atomicOperationId != 0) {
373 AtomicOperation *a = mAtomicOperations[change->atomicOperationId];
374 a->m_numCompletedChanges++;
375 change->completed =
true;
376 description = a->m_description;
379 if (deleteAlreadyCalled(item.id())) {
383 resultCode = ResultCodeAlreadyDeleted;
385 kWarning() <<
"Trying to change item " << item.id() <<
" while deletion is in progress.";
387 resultCode = ResultCodeJobError;
389 kError() << errorString;
391 if (mShowDialogsOnError) {
392 KMessageBox::sorry(change->parentWidget,
393 i18n(
"Error while trying to modify calendar item. Error was: %1",
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);
405 handleInvitationsAfterChange(change);
408 change->errorString = errorString;
409 change->resultCode = resultCode;
412 QMetaObject::invokeMethod(
this,
"performNextModification",
413 Qt::QueuedConnection,
414 Q_ARG(Akonadi::Item::Id, item.id()));
417 bool IncidenceChanger::Private::deleteAlreadyCalled(Akonadi::Item::Id
id)
const
419 return mDeletedItemIds.contains(
id);
422 bool IncidenceChanger::Private::handleInvitationsBeforeChange(
const Change::Ptr &change)
425 if (mGroupwareCommunication) {
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)));
436 switch (change->type) {
437 case IncidenceChanger::ChangeTypeCreate:
440 case IncidenceChanger::ChangeTypeDelete:
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()) {
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);
457 result = status != ITIPHandlerHelper::ResultFailAbortUpdate;
463 case IncidenceChanger::ChangeTypeModify:
465 if (change->originalItems.isEmpty()) {
469 Q_ASSERT(change->originalItems.count() == 1);
470 Incidence::Ptr oldIncidence = CalendarUtils::incidence(change->originalItems.first());
471 Incidence::Ptr newIncidence = CalendarUtils::incidence(change->newItem);
473 if (!oldIncidence->supportsGroupwareCommunication()) {
477 const bool weAreOrganizer = Akonadi::CalendarUtils::thatIsMe(newIncidence->organizer()->email());
478 if (RUNNING_UNIT_TESTS && !weAreOrganizer) {
482 if (m_invitationPolicy == InvitationPolicySend) {
484 }
else if (m_invitationPolicy == InvitationPolicyDontSend) {
489 const bool modify = handler.handleIncidenceAboutToBeModified(newIncidence);
494 if (newIncidence->type() == oldIncidence->type()) {
495 IncidenceBase *i1 = newIncidence.data();
496 IncidenceBase *i2 = oldIncidence.data();
510 bool IncidenceChanger::Private::handleInvitationsAfterChange(
const Change::Ptr &change)
512 if (change->useGroupwareCommunication) {
515 const bool alwaysSend = m_invitationPolicy == InvitationPolicySend;
516 const bool neverSend = m_invitationPolicy == InvitationPolicyDontSend;
518 handler.setDefaultAction(ITIPHandlerHelper::ActionSendMessage);
522 handler.setDefaultAction(ITIPHandlerHelper::ActionDontSendMessage);
525 switch (change->type) {
526 case IncidenceChanger::ChangeTypeCreate:
528 Incidence::Ptr incidence = CalendarUtils::incidence(change->newItem);
529 if (incidence->supportsGroupwareCommunication()) {
531 handler.sendIncidenceCreatedMessage(KCalCore::iTIPRequest, incidence);
533 if (status == ITIPHandlerHelper::ResultFailAbortUpdate) {
534 kError() <<
"Sending invitations failed, but did not delete the incidence";
537 const uint atomicOperationId = change->atomicOperationId;
538 if (atomicOperationId != 0) {
539 mInvitationStatusByAtomicOperation.insert(atomicOperationId, status);
544 case IncidenceChanger::ChangeTypeDelete:
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);
551 if (!incidence->supportsGroupwareCommunication())
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));
561 if (me->status() == KCalCore::Attendee::Accepted ||
562 me->status() == KCalCore::Attendee::Delegated) {
563 notifyOrganizer =
true;
565 KCalCore::Attendee::Ptr newMe(
new KCalCore::Attendee(*me));
566 newMe->setStatus(KCalCore::Attendee::Declined);
567 incidence->clearAttendees();
568 incidence->addAttendee(newMe);
573 if (notifyOrganizer) {
574 MailScheduler scheduler;
575 scheduler.performTransaction(incidence, KCalCore::iTIPReply);
581 case IncidenceChanger::ChangeTypeModify:
583 if (change->originalItems.isEmpty()) {
587 Q_ASSERT(change->originalItems.count() == 1);
588 Incidence::Ptr oldIncidence = CalendarUtils::incidence(change->originalItems.first());
589 Incidence::Ptr newIncidence = CalendarUtils::incidence(change->newItem);
591 if (!newIncidence->supportsGroupwareCommunication() ||
592 !Akonadi::CalendarUtils::thatIsMe(newIncidence->organizer()->email())) {
597 if (!neverSend && !alwaysSend && mInvitationStatusByAtomicOperation.contains(change->atomicOperationId)) {
598 handler.setDefaultAction(actionFromStatus(mInvitationStatusByAtomicOperation.value(change->atomicOperationId)));
601 const bool attendeeStatusChanged = myAttendeeStatusChanged(newIncidence,
603 Akonadi::CalendarUtils::allEmails());
607 attendeeStatusChanged);
609 if (change->atomicOperationId != 0) {
610 mInvitationStatusByAtomicOperation.insert(change->atomicOperationId, status);
623 bool IncidenceChanger::Private::myAttendeeStatusChanged(
const Incidence::Ptr &newInc,
624 const Incidence::Ptr &oldInc,
625 const QStringList &myEmails)
629 const Attendee::Ptr oldMe = oldInc->attendeeByMails(myEmails);
630 const Attendee::Ptr newMe = newInc->attendeeByMails(myEmails);
632 return oldMe && newMe && oldMe->status() != newMe->status();
635 IncidenceChanger::IncidenceChanger(QObject *parent) : QObject(parent)
636 , d(new Private(true, this))
640 IncidenceChanger::IncidenceChanger(
bool enableHistory, QObject *parent) : QObject(parent)
641 , d(new Private(enableHistory, this))
645 IncidenceChanger::~IncidenceChanger()
650 int IncidenceChanger::createIncidence(
const Incidence::Ptr &incidence,
656 kWarning() <<
"An invalid payload is not allowed.";
657 d->cancelTransaction();
661 const uint atomicOperationId = d->mBatchOperationInProgress ? d->mLatestAtomicOperationId : 0;
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;
671 change->resultCode = ResultCodeRolledback;
672 change->errorString = errorMessage;
673 d->cleanupTransaction();
678 item.setPayload<KCalCore::Incidence::Ptr>(incidence);
679 item.setMimeType(incidence->mimeType());
681 change->newItem = item;
683 d->step1DetermineDestinationCollection(change, collection);
688 int IncidenceChanger::deleteIncidence(
const Item &item, QWidget *parent)
693 return deleteIncidences(list, parent);
696 int IncidenceChanger::deleteIncidences(
const Item::List &items, QWidget *parent)
699 if (items.isEmpty()) {
700 kError() <<
"Delete what?";
701 d->cancelTransaction();
705 foreach(
const Item &item, items) {
706 if (!item.isValid()) {
707 kError() <<
"Items must be valid!";
708 d->cancelTransaction();
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));
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();
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();
737 Item::List itemsToDelete;
738 foreach(
const Item &item, items) {
739 if (d->deleteAlreadyCalled(item.id())) {
741 kDebug() <<
"Item " << item.id() <<
" already deleted or being deleted, skipping";
743 itemsToDelete.append(item);
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();
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.");
763 change->resultCode = ResultCodeAlreadyDeleted;
764 change->errorString = errorMessage;
765 d->cancelTransaction();
766 kWarning() << errorMessage;
769 change->originalItems = itemsToDelete;
770 d->handleInvitationsBeforeChange(change);
773 d->mChangeForJob.insert(deleteJob, change);
774 d->mChangeById.insert(changeId, change);
776 if (d->mBatchOperationInProgress) {
777 AtomicOperation *atomic = d->mAtomicOperations[atomicOperationId];
779 atomic->addChange(change);
782 foreach(
const Item &item, itemsToDelete) {
783 d->mDeletedItemIds << item.id();
787 if (d->mDeletedItemIds.count() > 100)
788 d->mDeletedItemIds.remove(0, 50);
791 connect(deleteJob, SIGNAL(result(KJob*)),
792 d, SLOT(handleDeleteJobResult(KJob*)), Qt::QueuedConnection);
797 int IncidenceChanger::modifyIncidence(
const Item &changedItem,
798 const KCalCore::Incidence::Ptr &originalPayload,
801 if (!changedItem.isValid() || !changedItem.hasPayload<Incidence::Ptr>()) {
802 kWarning() <<
"An invalid item or payload is not allowed.";
803 d->cancelTransaction();
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();
817 changedItem.payload<Incidence::Ptr>()->setLastModified(KDateTime::currentUtcDateTime());
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);
825 if (originalPayload) {
826 Item originalItem(changedItem);
827 originalItem.setPayload<KCalCore::Incidence::Ptr>(originalPayload);
828 modificationChange->originalItems << originalItem;
831 modificationChange->newItem = changedItem;
832 d->mChangeById.insert(changeId, change);
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";
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);
849 d->adjustRecurrence(originalPayload, CalendarUtils::incidence(modificationChange->newItem));
850 d->performModification(change);
856 void IncidenceChanger::Private::performModification(Change::Ptr change)
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>());
863 const int changeId = change->id;
865 if (deleteAlreadyCalled(
id)) {
867 kDebug() <<
"Item " <<
id <<
" already deleted or being deleted, skipping";
870 emitModifyFinished(q, change->id, newItem, ResultCodeAlreadyDeleted,
871 i18n(
"That calendar item was already deleted, or currently being deleted."));
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);
885 const bool userCancelled = !handleInvitationsBeforeChange(change);
888 kDebug() <<
"User cancelled, giving up";
889 emitModifyFinished(q, changeId, newItem, ResultCodeUserCanceled, QString());
893 QHash<Akonadi::Item::Id, int> &latestRevisionByItemId =
894 ConflictPreventer::self()->mLatestRevisionByItemId;
895 if (latestRevisionByItemId.contains(
id) &&
896 latestRevisionByItemId[
id] > newItem.revision()) {
904 newItem.setRevision(latestRevisionByItemId[
id]);
907 Incidence::Ptr incidence = CalendarUtils::incidence(newItem);
909 const int revision = incidence->revision();
910 incidence->setRevision(revision + 1);
915 newItem.setRemoteRevision(QString());
917 if (mModificationsInProgress.contains(newItem.id())) {
920 queueModification(change);
923 mChangeForJob.insert(modifyJob, change);
924 mDirtyFieldsByJob.insert(modifyJob, incidence->dirtyFields());
926 if (hasAtomicOperationId) {
927 AtomicOperation *atomic = mAtomicOperations[atomicOperationId];
929 atomic->addChange(change);
932 mModificationsInProgress[newItem.id()] = change;
934 connect(modifyJob, SIGNAL(result(KJob*)),
935 SLOT(handleModifyJobResult(KJob*)), Qt::QueuedConnection);
939 void IncidenceChanger::startAtomicOperation(
const QString &operationDescription)
941 if (d->mBatchOperationInProgress) {
942 kDebug() <<
"An atomic operation is already in progress.";
946 ++d->mLatestAtomicOperationId;
947 d->mBatchOperationInProgress =
true;
949 AtomicOperation *atomicOperation =
new AtomicOperation(d, d->mLatestAtomicOperationId);
950 atomicOperation->m_description = operationDescription;
951 d->mAtomicOperations.insert(d->mLatestAtomicOperationId, atomicOperation);
954 void IncidenceChanger::endAtomicOperation()
956 if (!d->mBatchOperationInProgress) {
957 kDebug() <<
"No atomic operation is in progress.";
961 Q_ASSERT_X(d->mLatestAtomicOperationId != 0,
962 "IncidenceChanger::endAtomicOperation()",
963 "Call startAtomicOperation() first.");
965 Q_ASSERT(d->mAtomicOperations.contains(d->mLatestAtomicOperationId));
966 AtomicOperation *atomicOperation = d->mAtomicOperations[d->mLatestAtomicOperationId];
967 Q_ASSERT(atomicOperation);
968 atomicOperation->m_endCalled =
true;
970 const bool allJobsCompleted = !atomicOperation->pendingJobs();
972 if (allJobsCompleted && atomicOperation->rolledback() &&
973 atomicOperation->m_transactionCompleted) {
975 delete d->mAtomicOperations.take(d->mLatestAtomicOperationId);
976 d->mBatchOperationInProgress =
false;
983 void IncidenceChanger::setShowDialogsOnError(
bool enable)
985 d->mShowDialogsOnError = enable;
988 bool IncidenceChanger::showDialogsOnError()
const
990 return d->mShowDialogsOnError;
993 void IncidenceChanger::setRespectsCollectionRights(
bool respects)
995 d->mRespectsCollectionRights = respects;
998 bool IncidenceChanger::respectsCollectionRights()
const
1000 return d->mRespectsCollectionRights;
1003 void IncidenceChanger::setDestinationPolicy(IncidenceChanger::DestinationPolicy destinationPolicy)
1005 d->mDestinationPolicy = destinationPolicy;
1008 IncidenceChanger::DestinationPolicy IncidenceChanger::destinationPolicy()
const
1010 return d->mDestinationPolicy;
1015 d->mDefaultCollection = collection;
1018 Collection IncidenceChanger::defaultCollection()
const
1020 return d->mDefaultCollection;
1023 bool IncidenceChanger::historyEnabled()
const
1025 return d->mUseHistory;
1028 void IncidenceChanger::setHistoryEnabled(
bool enable)
1030 if (d->mUseHistory != enable) {
1031 d->mUseHistory = enable;
1032 if (enable && !d->mHistory)
1033 d->mHistory =
new History(
this);
1037 History* IncidenceChanger::history()
const
1042 bool IncidenceChanger::deletedRecently(Akonadi::Item::Id
id)
const
1044 return d->deleteAlreadyCalled(
id);
1047 void IncidenceChanger::setGroupwareCommunication(
bool enabled)
1049 d->mGroupwareCommunication = enabled;
1052 bool IncidenceChanger::groupwareCommunication()
const
1054 return d->mGroupwareCommunication;
1057 void IncidenceChanger::setAutoAdjustRecurrence(
bool enable)
1059 d->mAutoAdjustRecurrence = enable;
1062 bool IncidenceChanger::autoAdjustRecurrence()
const
1064 return d->mAutoAdjustRecurrence;
1067 void IncidenceChanger::setInvitationPolicy(IncidenceChanger::InvitationPolicy policy)
1069 d->m_invitationPolicy = policy;
1072 IncidenceChanger::InvitationPolicy IncidenceChanger::invitationPolicy()
const
1074 return d->m_invitationPolicy;
1079 return d->mLastCollectionUsed;
1082 QString IncidenceChanger::Private::showErrorDialog(IncidenceChanger::ResultCode resultCode,
1085 QString errorString;
1086 switch (resultCode) {
1087 case IncidenceChanger::ResultCodePermissions:
1088 errorString = i18n(
"Operation can not be performed due to ACL restrictions");
1090 case IncidenceChanger::ResultCodeInvalidUserCollection:
1091 errorString = i18n(
"The chosen collection is invalid");
1093 case IncidenceChanger::ResultCodeInvalidDefaultCollection:
1094 errorString = i18n(
"Default collection is invalid or doesn't have proper ACLs"
1095 " and DestinationPolicyNeverAsk was used");
1097 case IncidenceChanger::ResultCodeDuplicateId:
1098 errorString = i18n(
"Duplicate item id in a group operation");
1100 case IncidenceChanger::ResultCodeRolledback:
1101 errorString = i18n(
"One change belonging to a group of changes failed. "
1102 "All changes are being rolled back.");
1106 return QString(i18n(
"Unknown error"));
1109 if (mShowDialogsOnError) {
1110 KMessageBox::sorry(parent, errorString);
1116 void IncidenceChanger::Private::adjustRecurrence(
const KCalCore::Incidence::Ptr &originalIncidence,
1117 const KCalCore::Incidence::Ptr &incidence)
1119 if (!originalIncidence || !incidence->recurs() || incidence->hasRecurrenceId() || !mAutoAdjustRecurrence
1120 || !incidence->dirtyFields().contains(KCalCore::Incidence::FieldDtStart)) {
1124 const QDate originalDate = originalIncidence->dtStart().date();
1125 const QDate newStartDate = incidence->dtStart().date();
1127 if (!originalDate.isValid() || !newStartDate.isValid() || originalDate == newStartDate)
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;
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);
1150 const QDate recurrenceEndDate = recurrence->defaultRRule() ? recurrence->defaultRRule()->endDt().date() : QDate();
1151 if (recurrenceEndDate.isValid() && recurrenceEndDate < newStartDate) {
1152 recurrence->setEndDate(newStartDate);
1156 void IncidenceChanger::Private::cancelTransaction()
1158 if (mBatchOperationInProgress) {
1159 mAtomicOperations[mLatestAtomicOperationId]->setRolledback();
1163 void IncidenceChanger::Private::cleanupTransaction()
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;
1175 bool IncidenceChanger::Private::allowAtomicOperation(
int atomicOperationId,
1176 const Change::Ptr &change)
const
1179 if (atomicOperationId > 0) {
1180 Q_ASSERT(mAtomicOperations.contains(atomicOperationId));
1181 AtomicOperation *operation = mAtomicOperations.value(atomicOperationId);
1183 if (change->type == ChangeTypeCreate) {
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)) {
1199 kWarning() <<
"Each change belonging to a group operation"
1200 <<
"must have a different Akonadi::Item::Id";
1207 void ModificationChange::emitCompletionSignal()
1209 emitModifyFinished(changer,
id, newItem, resultCode, errorString);
1213 void CreationChange::emitCompletionSignal()
1216 emitCreateFinished(changer,
id, newItem, resultCode, errorString);
1220 void DeletionChange::emitCompletionSignal()
1222 emitDeleteFinished(changer,
id, mItemIds, resultCode, errorString);
1257 AtomicOperation::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)
1264 , m_incidenceChangerPrivate(icp)
1267 Q_ASSERT(m_id != 0);
1272 if (!m_transaction) {
1276 m_incidenceChangerPrivate->mAtomicOperationByTransaction.insert(m_transaction, m_id);
1278 QObject::connect(m_transaction, SIGNAL(result(KJob*)),
1279 m_incidenceChangerPrivate, SLOT(handleTransactionJobResult(KJob*)));
1282 return m_transaction;