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>
29 #include <akonadi/collectiondialog.h>
32 #include <KLocalizedString>
34 #include <KMessageBox>
35 #include <KStandardGuiItem>
37 using namespace Akonadi;
38 using namespace KCalCore;
52 return ITIPHandlerHelper::ActionDontSendMessage;
54 return ITIPHandlerHelper::ActionSendMessage;
56 return ITIPHandlerHelper::ActionAsk;
63 const QStringList &mimeTypes,
68 kDebug() <<
"selecting collections with mimeType in " << mimeTypes;
70 dlg->changeCollectionDialogOptions(Akonadi::CollectionDialog::KeepTreeExpanded);
71 dlg->setMimeTypeFilter(mimeTypes);
74 dlg->setDefaultCollection(defCollection);
79 dialogCode = dlg->exec();
80 if (dialogCode == QDialog::Accepted) {
81 collection = dlg->selectedCollection();
84 kWarning() <<
"An invalid collection was selected!";
93 static void emitCreateFinished(IncidenceChanger *changer,
96 Akonadi::IncidenceChanger::ResultCode resultCode,
97 const QString &errorString)
99 QMetaObject::invokeMethod(changer,
"createFinished", Qt::QueuedConnection,
100 Q_ARG(
int, changeId),
102 Q_ARG(Akonadi::IncidenceChanger::ResultCode, resultCode),
103 Q_ARG(QString, errorString));
107 static void emitModifyFinished(IncidenceChanger *changer,
110 IncidenceChanger::ResultCode resultCode,
111 const QString &errorString)
113 QMetaObject::invokeMethod(changer,
"modifyFinished", Qt::QueuedConnection,
114 Q_ARG(
int, changeId),
116 Q_ARG(Akonadi::IncidenceChanger::ResultCode, resultCode),
117 Q_ARG(QString, errorString));
121 static void emitDeleteFinished(IncidenceChanger *changer,
123 const QVector<Akonadi::Item::Id> &itemIdList,
124 IncidenceChanger::ResultCode resultCode,
125 const QString &errorString)
127 QMetaObject::invokeMethod(changer,
"deleteFinished", Qt::QueuedConnection,
128 Q_ARG(
int, changeId),
129 Q_ARG(QVector<Akonadi::Item::Id>, itemIdList),
130 Q_ARG(Akonadi::IncidenceChanger::ResultCode, resultCode),
131 Q_ARG(QString, errorString));
135 class ConflictPreventerPrivate;
136 class ConflictPreventer {
137 friend class ConflictPreventerPrivate;
139 static ConflictPreventer*
self();
142 QHash<Akonadi::Item::Id, int> mLatestRevisionByItemId;
144 ConflictPreventer() {}
145 ~ConflictPreventer() {}
148 class ConflictPreventerPrivate {
150 ConflictPreventer instance;
153 K_GLOBAL_STATIC(ConflictPreventerPrivate, sConflictPreventerPrivate)
155 ConflictPreventer* ConflictPreventer::self()
157 return &sConflictPreventerPrivate->instance;
160 IncidenceChanger::Private::Private(
bool enableHistory, IncidenceChanger *qq) : q(qq)
163 mShowDialogsOnError =
true;
164 mHistory = enableHistory ?
new History(
this) : 0;
165 mUseHistory = enableHistory;
166 mDestinationPolicy = DestinationPolicyDefault;
167 mRespectsCollectionRights =
false;
168 mGroupwareCommunication =
false;
169 mLatestAtomicOperationId = 0;
170 mBatchOperationInProgress =
false;
172 qRegisterMetaType<QVector<Akonadi::Item::Id> >(
"QVector<Akonadi::Item::Id>");
173 qRegisterMetaType<Akonadi::Item::Id>(
"Akonadi::Item::Id");
174 qRegisterMetaType<Akonadi::Item>(
"Akonadi::Item");
175 qRegisterMetaType<Akonadi::IncidenceChanger::ResultCode>(
176 "Akonadi::IncidenceChanger::ResultCode");
179 IncidenceChanger::Private::~Private()
181 if (!mAtomicOperations.isEmpty() ||
182 !mQueuedModifications.isEmpty() ||
183 !mModificationsInProgress.isEmpty()) {
184 kDebug() <<
"Normal if the application was being used. "
185 "But might indicate a memory leak if it wasn't";
189 bool IncidenceChanger::Private::atomicOperationIsValid(uint atomicOperationId)
const
192 return mAtomicOperations.contains(atomicOperationId) &&
193 !mAtomicOperations[atomicOperationId]->m_endCalled;
196 bool IncidenceChanger::Private::hasRights(
const Collection &collection,
197 IncidenceChanger::ChangeType changeType)
const
200 switch (changeType) {
201 case ChangeTypeCreate:
204 case ChangeTypeModify:
207 case ChangeTypeDelete:
211 Q_ASSERT_X(
false,
"hasRights",
"invalid type");
214 return !collection.
isValid() || !mRespectsCollectionRights || result;
217 Akonadi::Job* IncidenceChanger::Private::parentJob(
const Change::Ptr &change)
const
219 return (mBatchOperationInProgress && !change->queuedModification) ?
220 mAtomicOperations[mLatestAtomicOperationId]->transaction() : 0;
223 void IncidenceChanger::Private::queueModification(Change::Ptr change)
229 if (mQueuedModifications.contains(
id)) {
230 Change::Ptr toBeDiscarded = mQueuedModifications.take(
id);
231 toBeDiscarded->resultCode = ResultCodeModificationDiscarded;
232 toBeDiscarded->completed =
true;
233 mChangeById.remove(toBeDiscarded->id);
236 change->queuedModification =
true;
237 mQueuedModifications[id] = change;
242 mModificationsInProgress.remove(
id);
244 if (mQueuedModifications.contains(
id)) {
245 const Change::Ptr change = mQueuedModifications.take(
id);
246 performModification(change);
250 void IncidenceChanger::Private::handleTransactionJobResult(KJob *job)
254 Q_ASSERT(transaction);
255 Q_ASSERT(mAtomicOperationByTransaction.contains(transaction));
257 const uint atomicOperationId = mAtomicOperationByTransaction.take(transaction);
259 Q_ASSERT(mAtomicOperations.contains(atomicOperationId));
260 AtomicOperation *operation = mAtomicOperations[atomicOperationId];
262 Q_ASSERT(operation->m_id == atomicOperationId);
264 if (!operation->rolledback())
265 operation->setRolledback();
266 kError() <<
"Transaction failed, everything was rolledback. "
267 << job->errorString();
269 Q_ASSERT(operation->m_endCalled);
270 Q_ASSERT(!operation->pendingJobs());
273 if (!operation->pendingJobs() && operation->m_endCalled) {
274 delete mAtomicOperations.take(atomicOperationId);
275 mBatchOperationInProgress =
false;
277 operation->m_transactionCompleted =
true;
281 void IncidenceChanger::Private::handleCreateJobResult(KJob *job)
285 ResultCode resultCode = ResultCodeSuccess;
287 Change::Ptr change = mChangeForJob.take(job);
288 mChangeById.remove(change->id);
295 if (change->atomicOperationId != 0) {
296 AtomicOperation *a = mAtomicOperations[change->atomicOperationId];
297 a->m_numCompletedChanges++;
298 change->completed =
true;
299 description = a->m_description;
303 item = change->newItem;
304 resultCode = ResultCodeJobError;
306 kError() << errorString;
307 if (mShowDialogsOnError) {
308 KMessageBox::sorry(change->parentWidget,
309 i18n(
"Error while trying to create calendar item. Error was: %1",
314 Q_ASSERT(item.
hasPayload<KCalCore::Incidence::Ptr>());
315 change->newItem = item;
316 handleInvitationsAfterChange(change);
318 if (change->recordToHistory) {
319 mHistory->recordCreation(item, description, change->atomicOperationId);
323 change->errorString = errorString;
324 change->resultCode = resultCode;
328 void IncidenceChanger::Private::handleDeleteJobResult(KJob *job)
332 ResultCode resultCode = ResultCodeSuccess;
334 Change::Ptr change = mChangeForJob.take(job);
335 mChangeById.remove(change->id);
340 QSharedPointer<DeletionChange> deletionChange = change.staticCast<DeletionChange>();
343 deletionChange->mItemIds.append(item.
id());
346 if (change->atomicOperationId != 0) {
347 AtomicOperation *a = mAtomicOperations[change->atomicOperationId];
348 a->m_numCompletedChanges++;
349 change->completed =
true;
350 description = a->m_description;
353 resultCode = ResultCodeJobError;
355 kError() << errorString;
356 if (mShowDialogsOnError) {
357 KMessageBox::sorry(change->parentWidget,
358 i18n(
"Error while trying to delete calendar item. Error was: %1",
362 foreach(
const Item &item, items) {
364 mDeletedItemIds.remove(mDeletedItemIds.indexOf(item.
id()));
367 if (change->recordToHistory) {
369 mHistory->recordDeletions(items, description, change->atomicOperationId);
372 handleInvitationsAfterChange(change);
375 change->errorString = errorString;
376 change->resultCode = resultCode;
380 void IncidenceChanger::Private::handleModifyJobResult(KJob *job)
383 ResultCode resultCode = ResultCodeSuccess;
384 Change::Ptr change = mChangeForJob.take(job);
385 mChangeById.remove(change->id);
389 Q_ASSERT(mDirtyFieldsByJob.contains(job));
390 Q_ASSERT(item.hasPayload<KCalCore::Incidence::Ptr>());
391 item.payload<KCalCore::Incidence::Ptr>()->setDirtyFields(mDirtyFieldsByJob.value(job));
392 const QSet<KCalCore::IncidenceBase::Field> dirtyFields = mDirtyFieldsByJob.value(job);
394 if (change->atomicOperationId != 0) {
395 AtomicOperation *a = mAtomicOperations[change->atomicOperationId];
396 a->m_numCompletedChanges++;
397 change->completed =
true;
398 description = a->m_description;
401 if (deleteAlreadyCalled(item.id())) {
405 resultCode = ResultCodeAlreadyDeleted;
407 kWarning() <<
"Trying to change item " << item.id() <<
" while deletion is in progress.";
409 resultCode = ResultCodeJobError;
411 kError() << errorString;
413 if (mShowDialogsOnError) {
414 KMessageBox::sorry(change->parentWidget,
415 i18n(
"Error while trying to modify calendar item. Error was: %1",
419 ConflictPreventer::self()->mLatestRevisionByItemId[item.id()] = item.revision();
420 change->newItem = item;
421 if (change->recordToHistory && !change->originalItems.isEmpty()) {
422 Q_ASSERT(change->originalItems.count() == 1);
423 mHistory->recordModification(change->originalItems.first(), item,
424 description, change->atomicOperationId);
427 handleInvitationsAfterChange(change);
430 change->errorString = errorString;
431 change->resultCode = resultCode;
434 QMetaObject::invokeMethod(
this,
"performNextModification",
435 Qt::QueuedConnection,
441 return mDeletedItemIds.contains(
id);
444 bool IncidenceChanger::Private::handleInvitationsBeforeChange(
const Change::Ptr &change)
447 if (mGroupwareCommunication) {
449 if (mInvitationStatusByAtomicOperation.contains(change->atomicOperationId)) {
450 handler.setDefaultAction(actionFromStatus(mInvitationStatusByAtomicOperation.value(change->atomicOperationId)));
453 switch (change->type) {
454 case IncidenceChanger::ChangeTypeCreate:
457 case IncidenceChanger::ChangeTypeDelete:
460 Q_ASSERT(!change->originalItems.isEmpty());
462 Q_ASSERT(item.
hasPayload<KCalCore::Incidence::Ptr>());
463 Incidence::Ptr incidence = CalendarUtils::incidence(item);
464 if (!incidence->supportsGroupwareCommunication()) {
469 if (Akonadi::CalendarUtils::thatIsMe(incidence->organizer()->email())) {
470 status = handler.sendIncidenceDeletedMessage(KCalCore::iTIPCancel, incidence);
471 if (change->atomicOperationId) {
472 mInvitationStatusByAtomicOperation.insert(change->atomicOperationId, status);
474 result = status != ITIPHandlerHelper::ResultFailAbortUpdate;
480 case IncidenceChanger::ChangeTypeModify:
482 if (change->originalItems.isEmpty()) {
486 Q_ASSERT(change->originalItems.count() == 1);
487 Incidence::Ptr oldIncidence = change->originalItems.first().payload<KCalCore::Incidence::Ptr>();
488 Incidence::Ptr newIncidence = change->newItem.payload<KCalCore::Incidence::Ptr>();
490 if (!oldIncidence->supportsGroupwareCommunication()) {
494 const bool modify = handler.handleIncidenceAboutToBeModified(newIncidence);
499 if (newIncidence->type() == oldIncidence->type()) {
500 IncidenceBase *i1 = newIncidence.data();
501 IncidenceBase *i2 = oldIncidence.data();
515 bool IncidenceChanger::Private::handleInvitationsAfterChange(
const Change::Ptr &change)
517 if (change->useGroupwareCommunication) {
519 switch (change->type) {
520 case IncidenceChanger::ChangeTypeCreate:
522 Incidence::Ptr incidence = change->newItem.payload<KCalCore::Incidence::Ptr>();
523 if (incidence->supportsGroupwareCommunication()) {
525 handler.sendIncidenceCreatedMessage(KCalCore::iTIPRequest, incidence);
527 if (status == ITIPHandlerHelper::ResultFailAbortUpdate) {
528 kError() <<
"Sending invitations failed, but did not delete the incidence";
531 const uint atomicOperationId = change->atomicOperationId;
532 if (atomicOperationId != 0) {
533 mInvitationStatusByAtomicOperation.insert(atomicOperationId, status);
538 case IncidenceChanger::ChangeTypeDelete:
540 Q_ASSERT(!change->originalItems.isEmpty());
543 Incidence::Ptr incidence = item.
payload<KCalCore::Incidence::Ptr>();
545 if (!incidence->supportsGroupwareCommunication())
548 if (!Akonadi::CalendarUtils::thatIsMe(incidence->organizer()->email())) {
549 const QStringList myEmails = Akonadi::CalendarUtils::allEmails();
550 bool notifyOrganizer =
false;
551 for (QStringList::ConstIterator it = myEmails.begin(); it != myEmails.end(); ++it) {
552 const QString email = *it;
553 KCalCore::Attendee::Ptr me(incidence->attendeeByMail(email));
555 if (me->status() == KCalCore::Attendee::Accepted ||
556 me->status() == KCalCore::Attendee::Delegated) {
557 notifyOrganizer =
true;
559 KCalCore::Attendee::Ptr newMe(
new KCalCore::Attendee(*me));
560 newMe->setStatus(KCalCore::Attendee::Declined);
561 incidence->clearAttendees();
562 incidence->addAttendee(newMe);
567 if (notifyOrganizer) {
568 MailScheduler scheduler;
569 scheduler.performTransaction(incidence, KCalCore::iTIPReply);
575 case IncidenceChanger::ChangeTypeModify:
577 if (change->originalItems.isEmpty()) {
581 Q_ASSERT(change->originalItems.count() == 1);
582 Incidence::Ptr oldIncidence = CalendarUtils::incidence(change->originalItems.first());
583 Incidence::Ptr newIncidence = CalendarUtils::incidence(change->newItem);
585 if (!newIncidence->supportsGroupwareCommunication() ||
586 !Akonadi::CalendarUtils::thatIsMe(newIncidence->organizer()->email())) {
591 if (mInvitationStatusByAtomicOperation.contains(change->atomicOperationId)) {
592 handler.setDefaultAction(actionFromStatus(mInvitationStatusByAtomicOperation.value(change->atomicOperationId)));
595 const bool attendeeStatusChanged = myAttendeeStatusChanged(newIncidence,
597 Akonadi::CalendarUtils::allEmails());
601 attendeeStatusChanged);
603 if (change->atomicOperationId != 0) {
604 mInvitationStatusByAtomicOperation.insert(change->atomicOperationId, status);
617 bool IncidenceChanger::Private::myAttendeeStatusChanged(
const Incidence::Ptr &newInc,
618 const Incidence::Ptr &oldInc,
619 const QStringList &myEmails)
623 const Attendee::Ptr oldMe = oldInc->attendeeByMails(myEmails);
624 const Attendee::Ptr newMe = newInc->attendeeByMails(myEmails);
626 return oldMe && newMe && oldMe->status() != newMe->status();
629 IncidenceChanger::IncidenceChanger(QObject *parent) : QObject(parent)
630 , d(new Private(true, this))
634 IncidenceChanger::IncidenceChanger(
bool enableHistory, QObject *parent) : QObject(parent)
635 , d(new Private(enableHistory, this))
639 IncidenceChanger::~IncidenceChanger()
644 int IncidenceChanger::createIncidence(
const Incidence::Ptr &incidence,
650 kWarning() <<
"An invalid payload is not allowed.";
651 d->cancelTransaction();
655 const uint atomicOperationId = d->mBatchOperationInProgress ? d->mLatestAtomicOperationId : 0;
657 const Change::Ptr change(
new CreationChange(
this, ++d->mLatestChangeId,
658 atomicOperationId, parent));
661 const int changeId = change->
id;
662 Q_ASSERT(!(d->mBatchOperationInProgress && !d->mAtomicOperations.contains(atomicOperationId)));
663 if (d->mBatchOperationInProgress && d->mAtomicOperations[atomicOperationId]->rolledback()) {
664 const QString errorMessage = d->showErrorDialog(ResultCodeRolledback, parent);
665 kWarning() << errorMessage;
667 change->resultCode = ResultCodeRolledback;
668 change->errorString = errorMessage;
669 d->cleanupTransaction();
673 d->handleInvitationsBeforeChange(change);
675 if (collection.
isValid() && d->hasRights(collection, ChangeTypeCreate)) {
677 collectionToUse = collection;
679 switch (d->mDestinationPolicy) {
680 case DestinationPolicyDefault:
681 if (d->mDefaultCollection.isValid() &&
682 d->hasRights(d->mDefaultCollection, ChangeTypeCreate)) {
683 collectionToUse = d->mDefaultCollection;
686 kWarning() <<
"Destination policy is to use the default collection."
687 <<
"But it's invalid or doesn't have proper ACLs."
688 <<
"isValid = " << d->mDefaultCollection.
isValid()
689 <<
"has ACLs = " << d->hasRights(d->mDefaultCollection,
692 case DestinationPolicyAsk:
695 const QStringList mimeTypes(incidence->mimeType());
696 collectionToUse = selectCollection(parent, dialogCode , mimeTypes,
697 d->mDefaultCollection);
698 if (dialogCode != QDialog::Accepted) {
699 kDebug() <<
"User canceled collection choosing";
700 change->resultCode = ResultCodeUserCanceled;
701 d->cancelTransaction();
705 if (collectionToUse.isValid() && !d->hasRights(collectionToUse, ChangeTypeCreate)) {
706 kWarning() <<
"No ACLs for incidence creation";
707 const QString errorMessage = d->showErrorDialog(ResultCodePermissions, parent);
708 change->resultCode = ResultCodePermissions;
709 change->errorString = errorMessage;
710 d->cancelTransaction();
715 if (!collectionToUse.isValid()) {
716 kError() <<
"Invalid collection selected. Can't create incidence.";
717 change->resultCode = ResultCodeInvalidUserCollection;
718 const QString errorString = d->showErrorDialog(ResultCodeInvalidUserCollection, parent);
719 change->errorString = errorString;
720 d->cancelTransaction();
725 case DestinationPolicyNeverAsk:
727 const bool hasRights = d->hasRights(d->mDefaultCollection, ChangeTypeCreate);
728 if (d->mDefaultCollection.isValid() && hasRights) {
729 collectionToUse = d->mDefaultCollection;
731 const QString errorString = d->showErrorDialog(ResultCodeInvalidDefaultCollection, parent);
732 kError() << errorString <<
"; rights are " << hasRights;
733 change->resultCode = hasRights ? ResultCodeInvalidDefaultCollection :
734 ResultCodePermissions;
735 change->errorString = errorString;
736 d->cancelTransaction();
743 Q_ASSERT_X(
false,
"createIncidence()",
"unknown destination policy");
744 d->cancelTransaction();
749 d->mLastCollectionUsed = collectionToUse;
756 d->mChangeForJob.insert(createJob, change);
758 if (d->mBatchOperationInProgress) {
759 AtomicOperation *atomic = d->mAtomicOperations[d->mLatestAtomicOperationId];
761 atomic->addChange(change);
765 connect(createJob, SIGNAL(result(KJob*)),
766 d, SLOT(handleCreateJobResult(KJob*)), Qt::QueuedConnection);
768 d->mChangeById.insert(changeId, change);
772 int IncidenceChanger::deleteIncidence(
const Item &item, QWidget *parent)
777 return deleteIncidences(list, parent);
780 int IncidenceChanger::deleteIncidences(
const Item::List &items, QWidget *parent)
783 if (items.isEmpty()) {
784 kError() <<
"Delete what?";
785 d->cancelTransaction();
789 foreach(
const Item &item, items) {
791 kError() <<
"Items must be valid!";
792 d->cancelTransaction();
797 const uint atomicOperationId = d->mBatchOperationInProgress ? d->mLatestAtomicOperationId : 0;
798 const int changeId = ++d->mLatestChangeId;
799 const Change::Ptr change(
new DeletionChange(
this, changeId, atomicOperationId, parent));
801 foreach(
const Item &item, items) {
803 kWarning() <<
"Item " << item.
id() <<
" can't be deleted due to ACL restrictions";
804 const QString errorString = d->showErrorDialog(ResultCodePermissions, parent);
805 change->resultCode = ResultCodePermissions;
806 change->errorString = errorString;
807 d->cancelTransaction();
812 if (!d->allowAtomicOperation(atomicOperationId, change)) {
813 const QString errorString = d->showErrorDialog(ResultCodeDuplicateId, parent);
814 change->resultCode = ResultCodeDuplicateId;
815 change->errorString = errorString;
816 kWarning() << errorString;
817 d->cancelTransaction();
822 foreach(
const Item &item, items) {
823 if (d->deleteAlreadyCalled(item.
id())) {
825 kDebug() <<
"Item " << item.
id() <<
" already deleted or being deleted, skipping";
827 itemsToDelete.append(item);
831 if (d->mBatchOperationInProgress && d->mAtomicOperations[atomicOperationId]->rolledback()) {
832 const QString errorMessage = d->showErrorDialog(ResultCodeRolledback, parent);
833 change->resultCode = ResultCodeRolledback;
834 change->errorString = errorMessage;
835 kError() << errorMessage;
836 d->cleanupTransaction();
840 if (itemsToDelete.isEmpty()) {
841 QVector<Akonadi::Item::Id> itemIdList;
842 itemIdList.append(
Item().
id());
843 kDebug() <<
"Items already deleted or being deleted, skipping";
844 const QString errorMessage =
845 i18n(
"That calendar item was already deleted, or currently being deleted.");
847 change->resultCode = ResultCodeAlreadyDeleted;
848 change->errorString = errorMessage;
849 d->cancelTransaction();
850 kWarning() << errorMessage;
853 change->originalItems = itemsToDelete;
854 d->handleInvitationsBeforeChange(change);
857 d->mChangeForJob.insert(deleteJob, change);
858 d->mChangeById.insert(changeId, change);
860 if (d->mBatchOperationInProgress) {
861 AtomicOperation *atomic = d->mAtomicOperations[atomicOperationId];
863 atomic->addChange(change);
866 foreach(
const Item &item, itemsToDelete) {
867 d->mDeletedItemIds << item.
id();
871 if (d->mDeletedItemIds.count() > 100)
872 d->mDeletedItemIds.remove(0, 50);
875 connect(deleteJob, SIGNAL(result(KJob*)),
876 d, SLOT(handleDeleteJobResult(KJob*)), Qt::QueuedConnection);
881 int IncidenceChanger::modifyIncidence(
const Item &changedItem,
882 const KCalCore::Incidence::Ptr &originalPayload,
886 kWarning() <<
"An invalid item or payload is not allowed.";
887 d->cancelTransaction();
892 kWarning() <<
"Item " << changedItem.
id() <<
" can't be deleted due to ACL restrictions";
893 const int changeId = ++d->mLatestChangeId;
894 const QString errorString = d->showErrorDialog(ResultCodePermissions, parent);
895 emitModifyFinished(
this, changeId, changedItem, ResultCodePermissions, errorString);
896 d->cancelTransaction();
901 changedItem.
payload<Incidence::Ptr>()->setLastModified(KDateTime::currentUtcDateTime());
903 const uint atomicOperationId = d->mBatchOperationInProgress ? d->mLatestAtomicOperationId : 0;
904 const int changeId = ++d->mLatestChangeId;
905 ModificationChange *modificationChange =
new ModificationChange(
this, changeId,
906 atomicOperationId, parent);
907 Change::Ptr change(modificationChange);
909 if (originalPayload) {
910 Item originalItem(changedItem);
911 originalItem.setPayload<KCalCore::Incidence::Ptr>(originalPayload);
912 modificationChange->originalItems << originalItem;
915 modificationChange->newItem = changedItem;
916 d->mChangeById.insert(changeId, change);
918 if (!d->allowAtomicOperation(atomicOperationId, change)) {
919 const QString errorString = d->showErrorDialog(ResultCodeDuplicateId, parent);
920 change->resultCode = ResultCodeDuplicateId;
921 change->errorString = errorString;
922 d->cancelTransaction();
923 kWarning() <<
"Atomic operation now allowed";
927 if (d->mBatchOperationInProgress && d->mAtomicOperations[atomicOperationId]->rolledback()) {
928 const QString errorMessage = d->showErrorDialog(ResultCodeRolledback, parent);
929 kError() << errorMessage;
930 d->cleanupTransaction();
931 emitModifyFinished(
this, changeId, changedItem, ResultCodeRolledback, errorMessage);
933 d->performModification(change);
939 void IncidenceChanger::Private::performModification(Change::Ptr change)
941 const Item::Id
id = change->newItem.id();
944 Q_ASSERT(newItem.
hasPayload<Incidence::Ptr>());
946 const int changeId = change->id;
948 if (deleteAlreadyCalled(
id)) {
950 kDebug() <<
"Item " <<
id <<
" already deleted or being deleted, skipping";
953 emitModifyFinished(q, change->id, newItem, ResultCodeAlreadyDeleted,
954 i18n(
"That calendar item was already deleted, or currently being deleted."));
958 const uint atomicOperationId = change->atomicOperationId;
959 const bool hasAtomicOperationId = atomicOperationId != 0;
960 if (hasAtomicOperationId &&
961 mAtomicOperations[atomicOperationId]->rolledback()) {
962 const QString errorMessage = showErrorDialog(ResultCodeRolledback, 0);
963 kError() << errorMessage;
964 emitModifyFinished(q, changeId, newItem, ResultCodeRolledback, errorMessage);
968 const bool userCancelled = !handleInvitationsBeforeChange(change);
971 kDebug() <<
"User cancelled, giving up";
972 emitModifyFinished(q, changeId, newItem, ResultCodeUserCanceled, QString());
976 QHash<Akonadi::Item::Id, int> &latestRevisionByItemId =
977 ConflictPreventer::self()->mLatestRevisionByItemId;
978 if (latestRevisionByItemId.contains(
id) &&
979 latestRevisionByItemId[id] > newItem.
revision()) {
990 Incidence::Ptr incidence = newItem.
payload<Incidence::Ptr>();
992 const int revision = incidence->revision();
993 incidence->setRevision(revision + 1);
1000 if (mModificationsInProgress.contains(newItem.
id())) {
1003 queueModification(change);
1006 mChangeForJob.insert(modifyJob, change);
1007 mDirtyFieldsByJob.insert(modifyJob, incidence->dirtyFields());
1009 if (hasAtomicOperationId) {
1010 AtomicOperation *atomic = mAtomicOperations[atomicOperationId];
1012 atomic->addChange(change);
1015 mModificationsInProgress[newItem.
id()] = change;
1017 connect(modifyJob, SIGNAL(result(KJob*)),
1018 SLOT(handleModifyJobResult(KJob*)), Qt::QueuedConnection);
1022 void IncidenceChanger::startAtomicOperation(
const QString &operationDescription)
1024 if (d->mBatchOperationInProgress) {
1025 kDebug() <<
"An atomic operation is already in progress.";
1029 ++d->mLatestAtomicOperationId;
1030 d->mBatchOperationInProgress =
true;
1032 AtomicOperation *atomicOperation =
new AtomicOperation(d, d->mLatestAtomicOperationId);
1033 atomicOperation->m_description = operationDescription;
1034 d->mAtomicOperations.insert(d->mLatestAtomicOperationId, atomicOperation);
1037 void IncidenceChanger::endAtomicOperation()
1039 if (!d->mBatchOperationInProgress) {
1040 kDebug() <<
"No atomic operation is in progress.";
1044 Q_ASSERT_X(d->mLatestAtomicOperationId != 0,
1045 "IncidenceChanger::endAtomicOperation()",
1046 "Call startAtomicOperation() first.");
1048 Q_ASSERT(d->mAtomicOperations.contains(d->mLatestAtomicOperationId));
1049 AtomicOperation *atomicOperation = d->mAtomicOperations[d->mLatestAtomicOperationId];
1050 Q_ASSERT(atomicOperation);
1051 atomicOperation->m_endCalled =
true;
1053 const bool allJobsCompleted = !atomicOperation->pendingJobs();
1055 if (allJobsCompleted && atomicOperation->rolledback() &&
1056 atomicOperation->m_transactionCompleted) {
1058 delete d->mAtomicOperations.take(d->mLatestAtomicOperationId);
1059 d->mBatchOperationInProgress =
false;
1066 void IncidenceChanger::setShowDialogsOnError(
bool enable)
1068 d->mShowDialogsOnError = enable;
1071 bool IncidenceChanger::showDialogsOnError()
const
1073 return d->mShowDialogsOnError;
1076 void IncidenceChanger::setRespectsCollectionRights(
bool respects)
1078 d->mRespectsCollectionRights = respects;
1081 bool IncidenceChanger::respectsCollectionRights()
const
1083 return d->mRespectsCollectionRights;
1086 void IncidenceChanger::setDestinationPolicy(IncidenceChanger::DestinationPolicy destinationPolicy)
1088 d->mDestinationPolicy = destinationPolicy;
1091 IncidenceChanger::DestinationPolicy IncidenceChanger::destinationPolicy()
const
1093 return d->mDestinationPolicy;
1098 d->mDefaultCollection = collection;
1101 Collection IncidenceChanger::defaultCollection()
const
1103 return d->mDefaultCollection;
1106 bool IncidenceChanger::historyEnabled()
const
1108 return d->mUseHistory;
1111 void IncidenceChanger::setHistoryEnabled(
bool enable)
1113 if (d->mUseHistory != enable) {
1114 d->mUseHistory = enable;
1115 if (enable && !d->mHistory)
1116 d->mHistory =
new History(
this);
1120 History* IncidenceChanger::history()
const
1127 return d->deleteAlreadyCalled(
id);
1130 void IncidenceChanger::setGroupwareCommunication(
bool enabled)
1132 d->mGroupwareCommunication = enabled;
1135 bool IncidenceChanger::groupwareCommunication()
const
1137 return d->mGroupwareCommunication;
1142 return d->mLastCollectionUsed;
1145 QString IncidenceChanger::Private::showErrorDialog(IncidenceChanger::ResultCode resultCode,
1148 QString errorString;
1149 switch (resultCode) {
1150 case IncidenceChanger::ResultCodePermissions:
1151 errorString = i18n(
"Operation can not be performed due to ACL restrictions");
1153 case IncidenceChanger::ResultCodeInvalidUserCollection:
1154 errorString = i18n(
"The chosen collection is invalid");
1156 case IncidenceChanger::ResultCodeInvalidDefaultCollection:
1157 errorString = i18n(
"Default collection is invalid or doesn't have proper ACLs"
1158 " and DestinationPolicyNeverAsk was used");
1160 case IncidenceChanger::ResultCodeDuplicateId:
1161 errorString = i18n(
"Duplicate item id in a group operation");
1163 case IncidenceChanger::ResultCodeRolledback:
1164 errorString = i18n(
"One change belonging to a group of changes failed. "
1165 "All changes are being rolled back.");
1169 return QString(i18n(
"Unknown error"));
1172 if (mShowDialogsOnError) {
1173 KMessageBox::sorry(parent, errorString);
1179 void IncidenceChanger::Private::cancelTransaction()
1181 if (mBatchOperationInProgress) {
1182 mAtomicOperations[mLatestAtomicOperationId]->setRolledback();
1186 void IncidenceChanger::Private::cleanupTransaction()
1188 Q_ASSERT(mAtomicOperations.contains(mLatestAtomicOperationId));
1189 AtomicOperation *operation = mAtomicOperations[mLatestAtomicOperationId];
1190 Q_ASSERT(operation);
1191 Q_ASSERT(operation->rolledback());
1192 if (!operation->pendingJobs() && operation->m_endCalled && operation->m_transactionCompleted) {
1193 delete mAtomicOperations.take(mLatestAtomicOperationId);
1194 mBatchOperationInProgress =
false;
1198 bool IncidenceChanger::Private::allowAtomicOperation(
int atomicOperationId,
1199 const Change::Ptr &change)
const
1202 if (atomicOperationId > 0) {
1203 Q_ASSERT(mAtomicOperations.contains(atomicOperationId));
1204 AtomicOperation *operation = mAtomicOperations.value(atomicOperationId);
1206 if (change->type == ChangeTypeCreate) {
1208 }
else if (change->type == ChangeTypeModify) {
1209 allow = !operation->m_itemIdsInOperation.contains(change->newItem.id());
1210 }
else if (change->type == ChangeTypeDelete) {
1211 DeletionChange::Ptr deletion = change.staticCast<DeletionChange>();
1213 if (operation->m_itemIdsInOperation.contains(
id)) {
1222 kWarning() <<
"Each change belonging to a group operation"
1223 <<
"must have a different Akonadi::Item::Id";
1230 void ModificationChange::emitCompletionSignal()
1232 emitModifyFinished(changer,
id, newItem, resultCode, errorString);
1236 void CreationChange::emitCompletionSignal()
1239 emitCreateFinished(changer,
id, newItem, resultCode, errorString);
1243 void DeletionChange::emitCompletionSignal()
1245 emitDeleteFinished(changer,
id, mItemIds, resultCode, errorString);
1280 AtomicOperation::AtomicOperation(IncidenceChanger::Private *icp,
1281 uint ident) : m_id(ident)
1282 , m_endCalled(false)
1283 , m_numCompletedChanges(0)
1284 , m_transactionCompleted(false)
1285 , m_wasRolledback(false)
1287 , m_incidenceChangerPrivate(icp)
1290 Q_ASSERT(m_id != 0);
1295 if (!m_transaction) {
1299 m_incidenceChangerPrivate->mAtomicOperationByTransaction.insert(m_transaction, m_id);
1301 QObject::connect(m_transaction, SIGNAL(result(KJob*)),
1302 m_incidenceChangerPrivate, SLOT(handleTransactionJobResult(KJob*)));
1305 return m_transaction;