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>
37 using namespace Akonadi;
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,
76 Q_ARG(Akonadi::Item, item),
77 Q_ARG(Akonadi::IncidenceChanger::ResultCode, resultCode),
82 static void emitModifyFinished(IncidenceChanger *changer,
84 const Akonadi::Item &item,
85 IncidenceChanger::ResultCode resultCode,
90 Q_ARG(Akonadi::Item, item),
91 Q_ARG(Akonadi::IncidenceChanger::ResultCode, resultCode),
96 static void emitDeleteFinished(IncidenceChanger *changer,
99 IncidenceChanger::ResultCode resultCode,
103 Q_ARG(
int, changeId),
105 Q_ARG(Akonadi::IncidenceChanger::ResultCode, resultCode),
110 class ConflictPreventerPrivate;
111 class ConflictPreventer {
112 friend class ConflictPreventerPrivate;
114 static ConflictPreventer*
self();
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;
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;
266 mChangeById.remove(change->id);
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;
313 mChangeById.remove(change->id);
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;
363 mChangeById.remove(change->id);
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));
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;
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;
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,
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()) {
758 itemIdList.
append(Item().
id());
759 kDebug() <<
"Items already deleted or being deleted, skipping";
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);
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());
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,
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: {
1134 const int oldIndex = originalDate.
dayOfWeek()-1;
1135 const int newIndex = newStartDate.
dayOfWeek()-1;
1136 if (oldIndex != 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,
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) {
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);
1279 m_incidenceChangerPrivate, SLOT(handleTransactionJobResult(KJob*)));
1282 return m_transaction;
QString & append(QChar ch)
Item item() const
Returns the created item with the new unique id, or an invalid item if the job failed.
void append(const T &value)
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.
QString & insert(int position, QChar ch)
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.
bool contains(const Key &key) const
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.
bool connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
QSharedPointer< X > staticCast() const
Item::List deletedItems() const
Returns the items passed on in the constructor.