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>
34 #include <KMessageBox>
35 #include <KStandardGuiItem>
37 using namespace Akonadi;
38 using namespace KCalCore;
52 return InvitationHandlerHelper::ActionDontSendMessage;
54 return InvitationHandlerHelper::ActionSendMessage;
56 return InvitationHandlerHelper::ActionAsk;
63 const QStringList &mimeTypes,
68 kDebug() <<
"selecting collections with mimeType in " << mimeTypes;
70 dlg->setMimeTypeFilter( mimeTypes );
72 if ( defCollection.
isValid() ) {
73 dlg->setDefaultCollection( defCollection );
78 dialogCode = dlg->exec();
79 if ( dialogCode == QDialog::Accepted ) {
80 collection = dlg->selectedCollection();
83 kWarning() <<
"An invalid collection was selected!";
92 static void emitCreateFinished( IncidenceChanger *changer,
95 Akonadi::IncidenceChanger::ResultCode resultCode,
96 const QString &errorString )
98 QMetaObject::invokeMethod( changer,
"createFinished", Qt::QueuedConnection,
99 Q_ARG(
int, changeId ),
101 Q_ARG( Akonadi::IncidenceChanger::ResultCode, resultCode ),
102 Q_ARG( QString, errorString ) );
106 static void emitModifyFinished( IncidenceChanger *changer,
109 IncidenceChanger::ResultCode resultCode,
110 const QString &errorString )
112 QMetaObject::invokeMethod( changer,
"modifyFinished", Qt::QueuedConnection,
113 Q_ARG(
int, changeId ),
115 Q_ARG( Akonadi::IncidenceChanger::ResultCode, resultCode ),
116 Q_ARG( QString, errorString ) );
120 static void emitDeleteFinished( IncidenceChanger *changer,
122 const QVector<Akonadi::Item::Id> &itemIdList,
123 IncidenceChanger::ResultCode resultCode,
124 const QString &errorString )
126 QMetaObject::invokeMethod( changer,
"deleteFinished", Qt::QueuedConnection,
127 Q_ARG(
int, changeId ),
128 Q_ARG( QVector<Akonadi::Item::Id>, itemIdList ),
129 Q_ARG( Akonadi::IncidenceChanger::ResultCode, resultCode ),
130 Q_ARG( QString, errorString ) );
134 class ConflictPreventerPrivate;
135 class ConflictPreventer {
136 friend class ConflictPreventerPrivate;
138 static ConflictPreventer*
self();
141 QHash<Akonadi::Item::Id, int> mLatestRevisionByItemId;
143 ConflictPreventer() {}
144 ~ConflictPreventer() {}
147 class ConflictPreventerPrivate {
149 ConflictPreventer instance;
152 K_GLOBAL_STATIC( ConflictPreventerPrivate, sConflictPreventerPrivate );
154 ConflictPreventer* ConflictPreventer::self()
156 return &sConflictPreventerPrivate->instance;
159 IncidenceChanger::Private::Private(
bool enableHistory, IncidenceChanger *qq ) : q( qq )
162 mShowDialogsOnError =
true;
163 mHistory = enableHistory ?
new History(
this ) : 0;
164 mUseHistory = enableHistory;
165 mDestinationPolicy = DestinationPolicyDefault;
166 mRespectsCollectionRights =
false;
167 mGroupwareCommunication =
false;
168 mLatestAtomicOperationId = 0;
169 mBatchOperationInProgress =
false;
171 qRegisterMetaType<QVector<Akonadi::Item::Id> >(
"QVector<Akonadi::Item::Id>" );
172 qRegisterMetaType<Akonadi::Item::Id>(
"Akonadi::Item::Id" );
174 qRegisterMetaType<Akonadi::IncidenceChanger::ResultCode>(
175 "Akonadi::IncidenceChanger::ResultCode" );
178 IncidenceChanger::Private::~Private()
180 if ( !mAtomicOperations.isEmpty() ||
181 !mQueuedModifications.isEmpty() ||
182 !mModificationsInProgress.isEmpty() ) {
183 kDebug() <<
"Normal if the application was being used. "
184 "But might indicate a memory leak if it wasn't";
188 bool IncidenceChanger::Private::atomicOperationIsValid( uint atomicOperationId )
const
191 return mAtomicOperations.contains( atomicOperationId ) &&
192 !mAtomicOperations[atomicOperationId]->endCalled;
195 bool IncidenceChanger::Private::hasRights(
const Collection &collection,
196 IncidenceChanger::ChangeType changeType )
const
199 switch( changeType ) {
200 case ChangeTypeCreate:
203 case ChangeTypeModify:
206 case ChangeTypeDelete:
210 Q_ASSERT_X(
false,
"hasRights",
"invalid type" );
213 return !collection.
isValid() || !mRespectsCollectionRights || result;
216 Akonadi::Job* IncidenceChanger::Private::parentJob(
const Change::Ptr &change )
const
218 return (mBatchOperationInProgress && !change->queuedModification) ?
219 mAtomicOperations[mLatestAtomicOperationId]->transaction : 0;
222 void IncidenceChanger::Private::queueModification( Change::Ptr change )
228 if ( mQueuedModifications.contains(
id ) ) {
229 Change::Ptr toBeDiscarded = mQueuedModifications.take(
id );
230 toBeDiscarded->resultCode = ResultCodeModificationDiscarded;
231 toBeDiscarded->completed =
true;
232 mChangeById.remove( toBeDiscarded->id );
235 change->queuedModification =
true;
236 mQueuedModifications[id] = change;
241 mModificationsInProgress.remove(
id );
243 if ( mQueuedModifications.contains(
id ) ) {
244 const Change::Ptr change = mQueuedModifications.take(
id );
245 performModification( change );
249 void IncidenceChanger::Private::handleTransactionJobResult( KJob *job )
253 Q_ASSERT( transaction );
254 Q_ASSERT( mAtomicOperationByTransaction.contains( transaction ) );
256 const uint atomicOperationId = mAtomicOperationByTransaction.take( transaction );
258 Q_ASSERT( mAtomicOperations.contains(atomicOperationId) );
259 AtomicOperation *operation = mAtomicOperations[atomicOperationId];
260 Q_ASSERT( operation );
261 Q_ASSERT( operation->id == atomicOperationId );
262 if ( job->error() ) {
263 if ( !operation->rolledback() )
264 operation->setRolledback();
265 kError() <<
"Transaction failed, everything was rolledback. "
266 << job->errorString();
268 Q_ASSERT( operation->endCalled );
269 Q_ASSERT( !operation->pendingJobs() );
272 if ( !operation->pendingJobs() && operation->endCalled ) {
273 delete mAtomicOperations.take( atomicOperationId );
274 mBatchOperationInProgress =
false;
276 operation->transactionCompleted =
true;
280 void IncidenceChanger::Private::handleCreateJobResult( KJob *job )
284 ResultCode resultCode = ResultCodeSuccess;
286 Change::Ptr change = mChangeForJob.take( job );
287 mChangeById.remove( change->id );
294 if ( change->atomicOperationId != 0 ) {
295 AtomicOperation *a = mAtomicOperations[change->atomicOperationId];
296 a->numCompletedChanges++;
297 change->completed =
true;
298 description = a->description;
302 item = change->newItem;
303 resultCode = ResultCodeJobError;
305 kError() << errorString;
306 if ( mShowDialogsOnError ) {
307 KMessageBox::sorry( change->parentWidget,
308 i18n(
"Error while trying to create calendar item. Error was: %1",
313 Q_ASSERT( item.
hasPayload<KCalCore::Incidence::Ptr>() );
314 change->newItem = item;
315 handleInvitationsAfterChange( change );
317 if ( change->recordToHistory ) {
318 mHistory->recordCreation( item, description, change->atomicOperationId );
322 change->errorString = errorString;
323 change->resultCode = resultCode;
327 void IncidenceChanger::Private::handleDeleteJobResult( KJob *job )
331 ResultCode resultCode = ResultCodeSuccess;
333 Change::Ptr change = mChangeForJob.take( job );
334 mChangeById.remove( change->id );
339 QSharedPointer<DeletionChange> deletionChange = change.staticCast<DeletionChange>();
342 deletionChange->mItemIds.append( item.
id() );
345 if ( change->atomicOperationId != 0 ) {
346 AtomicOperation *a = mAtomicOperations[change->atomicOperationId];
347 a->numCompletedChanges++;
348 change->completed =
true;
349 description = a->description;
352 resultCode = ResultCodeJobError;
354 kError() << errorString;
355 if ( mShowDialogsOnError ) {
356 KMessageBox::sorry( change->parentWidget,
357 i18n(
"Error while trying to delete calendar item. Error was: %1",
361 foreach(
const Item &item, items ) {
363 mDeletedItemIds.remove( item.
id() );
366 if ( change->recordToHistory ) {
367 Q_ASSERT( mHistory );
368 mHistory->recordDeletions( items, description, change->atomicOperationId );
371 handleInvitationsAfterChange( change );
374 change->errorString = errorString;
375 change->resultCode = resultCode;
379 void IncidenceChanger::Private::handleModifyJobResult( KJob *job )
382 ResultCode resultCode = ResultCodeSuccess;
383 Change::Ptr change = mChangeForJob.take( job );
384 mChangeById.remove( change->id );
388 Q_ASSERT( mDirtyFieldsByJob.contains( job ) );
389 item.
payload<KCalCore::Incidence::Ptr>()->setDirtyFields( mDirtyFieldsByJob.value( job ) );
390 const QSet<KCalCore::IncidenceBase::Field> dirtyFields = mDirtyFieldsByJob.value( job );
392 if ( change->atomicOperationId != 0 ) {
393 AtomicOperation *a = mAtomicOperations[change->atomicOperationId];
394 a->numCompletedChanges++;
395 change->completed =
true;
396 description = a->description;
399 if ( deleteAlreadyCalled( item.id() ) ) {
403 resultCode = ResultCodeAlreadyDeleted;
405 kWarning() <<
"Trying to change item " << item.id() <<
" while deletion is in progress.";
407 resultCode = ResultCodeJobError;
409 kError() << errorString;
411 if ( mShowDialogsOnError ) {
412 KMessageBox::sorry( change->parentWidget,
413 i18n(
"Error while trying to modify calendar item. Error was: %1",
417 ConflictPreventer::self()->mLatestRevisionByItemId[item.id()] = item.revision();
418 change->newItem = item;
419 if ( change->recordToHistory && !change->originalItems.isEmpty() ) {
420 Q_ASSERT( change->originalItems.count() == 1 );
421 mHistory->recordModification( change->originalItems.first(), item,
422 description, change->atomicOperationId );
425 handleInvitationsAfterChange( change );
428 change->errorString = errorString;
429 change->resultCode = resultCode;
432 QMetaObject::invokeMethod(
this,
"performNextModification",
433 Qt::QueuedConnection,
437 bool IncidenceChanger::Private::deleteAlreadyCalled(
Akonadi::Item::Id id )
const
439 return mDeletedItemIds.contains(
id );
442 bool IncidenceChanger::Private::handleInvitationsBeforeChange(
const Change::Ptr &change )
445 if ( mGroupwareCommunication ) {
447 if ( mInvitationStatusByAtomicOperation.contains( change->atomicOperationId ) ) {
448 handler.setDefaultAction( actionFromStatus( mInvitationStatusByAtomicOperation.value( change->atomicOperationId ) ) );
451 switch( change->type ) {
452 case IncidenceChanger::ChangeTypeCreate:
455 case IncidenceChanger::ChangeTypeDelete:
458 foreach(
const Akonadi::Item &item, change->originalItems ) {
460 Incidence::Ptr incidence = item.
payload<KCalCore::Incidence::Ptr>();
461 if ( !incidence->supportsGroupwareCommunication() )
463 status = handler.sendIncidenceDeletedMessage( KCalCore::iTIPCancel, incidence );
464 if ( change->atomicOperationId ) {
465 mInvitationStatusByAtomicOperation.insert( change->atomicOperationId, status );
467 result = status != InvitationHandlerHelper::ResultFailAbortUpdate;
472 case IncidenceChanger::ChangeTypeModify:
474 if ( !change->originalItems.isEmpty() ) {
475 Q_ASSERT( change->originalItems.count() == 1 );
476 Incidence::Ptr oldIncidence = change->originalItems.first().payload<KCalCore::Incidence::Ptr>();
477 Incidence::Ptr newIncidence = change->newItem.payload<KCalCore::Incidence::Ptr>();
479 if ( oldIncidence->supportsGroupwareCommunication() ) {
480 const bool modify = handler.handleIncidenceAboutToBeModified( newIncidence );
482 if ( newIncidence->type() == oldIncidence->type() ) {
483 IncidenceBase *i1 = newIncidence.data();
484 IncidenceBase *i2 = oldIncidence.data();
501 bool IncidenceChanger::Private::handleInvitationsAfterChange(
const Change::Ptr &change )
503 if ( mGroupwareCommunication ) {
505 switch( change->type ) {
506 case IncidenceChanger::ChangeTypeCreate:
508 Incidence::Ptr incidence = change->newItem.payload<KCalCore::Incidence::Ptr>();
509 if ( incidence->supportsGroupwareCommunication() ) {
511 handler.sendIncidenceCreatedMessage( KCalCore::iTIPRequest, incidence );
513 if ( status == InvitationHandlerHelper::ResultFailAbortUpdate ) {
514 kError() <<
"Sending invitations failed, but did not delete the incidence";
517 const uint atomicOperationId = change->atomicOperationId;
518 if ( atomicOperationId != 0 ) {
519 mInvitationStatusByAtomicOperation.insert( atomicOperationId, status );
524 case IncidenceChanger::ChangeTypeDelete:
526 foreach(
const Akonadi::Item &item, change->originalItems ) {
528 Incidence::Ptr incidence = item.
payload<KCalCore::Incidence::Ptr>();
529 Q_ASSERT( incidence );
530 if ( !incidence->supportsGroupwareCommunication() )
533 if ( !Akonadi::CalendarUtils::thatIsMe( incidence->organizer()->email() ) ) {
534 const QStringList myEmails = Akonadi::CalendarUtils::allEmails();
535 bool notifyOrganizer =
false;
536 for ( QStringList::ConstIterator it = myEmails.begin(); it != myEmails.end(); ++it ) {
537 const QString email = *it;
538 KCalCore::Attendee::Ptr me( incidence->attendeeByMail( email ) );
540 if ( me->status() == KCalCore::Attendee::Accepted ||
541 me->status() == KCalCore::Attendee::Delegated ) {
542 notifyOrganizer =
true;
544 KCalCore::Attendee::Ptr newMe(
new KCalCore::Attendee( *me ) );
545 newMe->setStatus( KCalCore::Attendee::Declined );
546 incidence->clearAttendees();
547 incidence->addAttendee( newMe );
552 if ( notifyOrganizer ) {
553 MailScheduler scheduler;
554 scheduler.performTransaction( incidence, KCalCore::iTIPReply );
560 case IncidenceChanger::ChangeTypeModify:
562 if ( !change->originalItems.isEmpty() ) {
563 Q_ASSERT( change->originalItems.count() == 1 );
564 Incidence::Ptr oldIncidence = change->originalItems.first().payload<KCalCore::Incidence::Ptr>();
565 Incidence::Ptr newIncidence = change->newItem.payload<KCalCore::Incidence::Ptr>();
566 if ( newIncidence->supportsGroupwareCommunication() ) {
567 if ( mInvitationStatusByAtomicOperation.contains( change->atomicOperationId ) ) {
568 handler.setDefaultAction( actionFromStatus( mInvitationStatusByAtomicOperation.value( change->atomicOperationId ) ) );
570 const bool attendeeStatusChanged = myAttendeeStatusChanged( newIncidence,
572 Akonadi::CalendarUtils::allEmails() );
575 attendeeStatusChanged );
577 if ( change->atomicOperationId != 0 ) {
578 mInvitationStatusByAtomicOperation.insert( change->atomicOperationId, status );
593 bool IncidenceChanger::Private::myAttendeeStatusChanged(
const Incidence::Ptr &newInc,
594 const Incidence::Ptr &oldInc,
595 const QStringList &myEmails )
599 const Attendee::Ptr oldMe = oldInc->attendeeByMails( myEmails );
600 const Attendee::Ptr newMe = newInc->attendeeByMails( myEmails );
602 return oldMe && newMe && oldMe->status() != newMe->status();
605 IncidenceChanger::IncidenceChanger( QObject *parent ) : QObject( parent )
606 , d( new Private( true, this ) )
610 IncidenceChanger::IncidenceChanger(
bool enableHistory, QObject *parent ) : QObject( parent )
611 , d( new Private( enableHistory, this ) )
615 IncidenceChanger::~IncidenceChanger()
620 int IncidenceChanger::createIncidence(
const Incidence::Ptr &incidence,
626 kWarning() <<
"An invalid payload is not allowed.";
627 d->cancelTransaction();
631 const uint atomicOperationId = d->mBatchOperationInProgress ? d->mLatestAtomicOperationId : 0;
633 const Change::Ptr change(
new CreationChange(
this, ++d->mLatestChangeId,
634 atomicOperationId, parent ) );
637 const int changeId = change->
id;
638 Q_ASSERT( !( d->mBatchOperationInProgress && !d->mAtomicOperations.contains( atomicOperationId ) ) );
639 if ( d->mBatchOperationInProgress && d->mAtomicOperations[atomicOperationId]->rolledback() ) {
640 const QString errorMessage = d->showErrorDialog( ResultCodeRolledback, parent );
641 kWarning() << errorMessage;
643 change->resultCode = ResultCodeRolledback;
644 change->errorString = errorMessage;
645 d->cleanupTransaction();
649 d->handleInvitationsBeforeChange( change );
651 if ( collection.
isValid() && d->hasRights( collection, ChangeTypeCreate ) ) {
653 collectionToUse = collection;
655 switch( d->mDestinationPolicy ) {
656 case DestinationPolicyDefault:
657 if ( d->mDefaultCollection.isValid() &&
658 d->hasRights( d->mDefaultCollection, ChangeTypeCreate ) ) {
659 collectionToUse = d->mDefaultCollection;
662 kWarning() <<
"Destination policy is to use the default collection."
663 <<
"But it's invalid or doesn't have proper ACLs."
664 <<
"isValid = " << d->mDefaultCollection.
isValid()
665 <<
"has ACLs = " << d->hasRights( d->mDefaultCollection,
668 case DestinationPolicyAsk:
671 const QStringList mimeTypes( incidence->mimeType() );
672 collectionToUse = selectCollection( parent, dialogCode , mimeTypes,
673 d->mDefaultCollection );
674 if ( dialogCode != QDialog::Accepted ) {
675 kDebug() <<
"User canceled collection choosing";
676 change->resultCode = ResultCodeUserCanceled;
677 d->cancelTransaction();
681 if ( collectionToUse.isValid() && !d->hasRights( collectionToUse, ChangeTypeCreate ) ) {
682 kWarning() <<
"No ACLs for incidence creation";
683 const QString errorMessage = d->showErrorDialog( ResultCodePermissions, parent );
684 change->resultCode = ResultCodePermissions;
685 change->errorString = errorMessage;
686 d->cancelTransaction();
691 if ( !collectionToUse.isValid() ) {
692 kError() <<
"Invalid collection selected. Can't create incidence.";
693 change->resultCode = ResultCodeInvalidUserCollection;
694 const QString errorString = d->showErrorDialog( ResultCodeInvalidUserCollection, parent );
695 change->errorString = errorString;
696 d->cancelTransaction();
701 case DestinationPolicyNeverAsk:
703 const bool hasRights = d->hasRights( d->mDefaultCollection, ChangeTypeCreate );
704 if ( d->mDefaultCollection.isValid() && hasRights ) {
705 collectionToUse = d->mDefaultCollection;
707 const QString errorString = d->showErrorDialog( ResultCodeInvalidDefaultCollection, parent );
708 kError() << errorString <<
"; rights are " << hasRights;
709 change->resultCode = hasRights ? ResultCodeInvalidDefaultCollection :
710 ResultCodePermissions;
711 change->errorString = errorString;
712 d->cancelTransaction();
719 Q_ASSERT_X(
false,
"createIncidence()",
"unknown destination policy" );
720 d->cancelTransaction();
725 d->mLastCollectionUsed = collectionToUse;
732 d->mChangeForJob.insert( createJob, change );
734 if ( d->mBatchOperationInProgress ) {
735 AtomicOperation *atomic = d->mAtomicOperations[d->mLatestAtomicOperationId];
737 atomic->addChange( change );
741 connect( createJob, SIGNAL(result(KJob*)),
742 d, SLOT(handleCreateJobResult(KJob*)), Qt::QueuedConnection );
744 d->mChangeById.insert( changeId, change );
748 int IncidenceChanger::deleteIncidence(
const Item &item, QWidget *parent )
753 return deleteIncidences( list, parent );
756 int IncidenceChanger::deleteIncidences(
const Item::List &items, QWidget *parent )
759 if ( items.isEmpty() ) {
760 kError() <<
"Delete what?";
761 d->cancelTransaction();
765 foreach(
const Item &item, items ) {
767 kError() <<
"Items must be valid!";
768 d->cancelTransaction();
773 const uint atomicOperationId = d->mBatchOperationInProgress ? d->mLatestAtomicOperationId : 0;
774 const int changeId = ++d->mLatestChangeId;
775 const Change::Ptr change(
new DeletionChange(
this, changeId, atomicOperationId, parent ) );
777 foreach(
const Item &item, items ) {
779 kWarning() <<
"Item " << item.
id() <<
" can't be deleted due to ACL restrictions";
780 const QString errorString = d->showErrorDialog( ResultCodePermissions, parent );
781 change->resultCode = ResultCodePermissions;
782 change->errorString = errorString;
783 d->cancelTransaction();
788 if ( !d->allowAtomicOperation( atomicOperationId, change ) ) {
789 const QString errorString = d->showErrorDialog( ResultCodeDuplicateId, parent );
790 change->resultCode = ResultCodeDuplicateId;
791 change->errorString = errorString;
792 kWarning() << errorString;
793 d->cancelTransaction();
798 foreach(
const Item &item, items ) {
799 if ( d->deleteAlreadyCalled( item.
id() ) ) {
801 kDebug() <<
"Item " << item.
id() <<
" already deleted or being deleted, skipping";
803 itemsToDelete.append( item );
807 if ( d->mBatchOperationInProgress && d->mAtomicOperations[atomicOperationId]->rolledback() ) {
808 const QString errorMessage = d->showErrorDialog( ResultCodeRolledback, parent );
809 change->resultCode = ResultCodeRolledback;
810 change->errorString = errorMessage;
811 kError() << errorMessage;
812 d->cleanupTransaction();
816 if ( itemsToDelete.isEmpty() ) {
817 QVector<Akonadi::Item::Id> itemIdList;
818 itemIdList.append(
Item().
id() );
819 kDebug() <<
"Items already deleted or being deleted, skipping";
820 const QString errorMessage =
821 i18n(
"That calendar item was already deleted, or currently being deleted." );
823 change->resultCode = ResultCodeAlreadyDeleted;
824 change->errorString = errorMessage;
825 d->cancelTransaction();
826 kWarning() << errorMessage;
830 d->handleInvitationsBeforeChange( change );
833 d->mChangeForJob.insert( deleteJob, change );
834 d->mChangeById.insert( changeId, change );
836 if ( d->mBatchOperationInProgress ) {
837 AtomicOperation *atomic = d->mAtomicOperations[atomicOperationId];
839 atomic->addChange( change );
842 foreach(
const Item &item, itemsToDelete ) {
843 d->mDeletedItemIds << item.
id();
847 if ( d->mDeletedItemIds.count() > 100 )
848 d->mDeletedItemIds.remove( 0, 50 );
851 connect( deleteJob, SIGNAL(result(KJob*)),
852 d, SLOT(handleDeleteJobResult(KJob*)), Qt::QueuedConnection );
857 int IncidenceChanger::modifyIncidence(
const Item &changedItem,
858 const KCalCore::Incidence::Ptr &originalPayload,
862 kWarning() <<
"An invalid item or payload is not allowed.";
863 d->cancelTransaction();
868 kWarning() <<
"Item " << changedItem.
id() <<
" can't be deleted due to ACL restrictions";
869 const int changeId = ++d->mLatestChangeId;
870 const QString errorString = d->showErrorDialog( ResultCodePermissions, parent );
871 emitModifyFinished(
this, changeId, changedItem, ResultCodePermissions, errorString );
872 d->cancelTransaction();
876 const uint atomicOperationId = d->mBatchOperationInProgress ? d->mLatestAtomicOperationId : 0;
877 const int changeId = ++d->mLatestChangeId;
878 ModificationChange *modificationChange =
new ModificationChange(
this, changeId,
879 atomicOperationId, parent );
880 Change::Ptr change( modificationChange );
882 if ( originalPayload ) {
883 Item originalItem( changedItem );
884 originalItem.setPayload<KCalCore::Incidence::Ptr>( originalPayload );
885 modificationChange->originalItems << originalItem;
888 modificationChange->newItem = changedItem;
889 d->mChangeById.insert( changeId, change );
891 if ( !d->allowAtomicOperation( atomicOperationId, change ) ) {
892 const QString errorString = d->showErrorDialog( ResultCodeDuplicateId, parent );
893 change->resultCode = ResultCodeDuplicateId;
894 change->errorString = errorString;
895 d->cancelTransaction();
896 kWarning() <<
"Atomic operation now allowed";
900 if ( d->mBatchOperationInProgress && d->mAtomicOperations[atomicOperationId]->rolledback() ) {
901 const QString errorMessage = d->showErrorDialog( ResultCodeRolledback, parent );
902 kError() << errorMessage;
903 d->cleanupTransaction();
904 emitModifyFinished(
this, changeId, changedItem, ResultCodeRolledback, errorMessage );
906 d->performModification( change );
912 void IncidenceChanger::Private::performModification( Change::Ptr change )
914 const Item::Id
id = change->newItem.id();
917 Q_ASSERT( newItem.
hasPayload<Incidence::Ptr>() );
919 const int changeId = change->id;
921 if ( deleteAlreadyCalled(
id ) ) {
923 kDebug() <<
"Item " <<
id <<
" already deleted or being deleted, skipping";
926 emitModifyFinished( q, change->id, newItem, ResultCodeAlreadyDeleted,
927 i18n(
"That calendar item was already deleted, or currently being deleted." ) );
931 const uint atomicOperationId = change->atomicOperationId;
932 const bool hasAtomicOperationId = atomicOperationId != 0;
933 if ( hasAtomicOperationId &&
934 mAtomicOperations[atomicOperationId]->rolledback() ) {
935 const QString errorMessage = showErrorDialog( ResultCodeRolledback, 0 );
936 kError() << errorMessage;
937 emitModifyFinished( q, changeId, newItem, ResultCodeRolledback, errorMessage );
941 handleInvitationsBeforeChange( change );
943 QHash<Akonadi::Item::Id, int> &latestRevisionByItemId =
944 ConflictPreventer::self()->mLatestRevisionByItemId;
945 if ( latestRevisionByItemId.contains(
id ) &&
946 latestRevisionByItemId[id] > newItem.
revision() ) {
957 Incidence::Ptr incidence = newItem.
payload<Incidence::Ptr>();
959 const int revision = incidence->revision();
960 incidence->setRevision( revision + 1 );
967 if ( mModificationsInProgress.contains( newItem.
id() ) ) {
970 queueModification( change );
973 mChangeForJob.insert( modifyJob, change );
974 mDirtyFieldsByJob.insert( modifyJob, incidence->dirtyFields() );
976 if ( hasAtomicOperationId ) {
977 AtomicOperation *atomic = mAtomicOperations[atomicOperationId];
979 atomic->addChange( change );
982 mModificationsInProgress[newItem.
id()] = change;
984 connect( modifyJob, SIGNAL(result(KJob*)),
985 SLOT(handleModifyJobResult(KJob*)), Qt::QueuedConnection );
989 void IncidenceChanger::startAtomicOperation(
const QString &operationDescription )
991 if ( d->mBatchOperationInProgress ) {
992 kDebug() <<
"An atomic operation is already in progress.";
996 ++d->mLatestAtomicOperationId;
997 d->mBatchOperationInProgress =
true;
999 AtomicOperation *atomicOperation =
new AtomicOperation( d->mLatestAtomicOperationId );
1000 atomicOperation->description = operationDescription;
1001 d->mAtomicOperations.insert( d->mLatestAtomicOperationId, atomicOperation );
1002 d->mAtomicOperationByTransaction.insert( atomicOperation->transaction, d->mLatestAtomicOperationId );
1004 d->connect( atomicOperation->transaction, SIGNAL(result(KJob*)),
1005 d, SLOT(handleTransactionJobResult(KJob*)) );
1008 void IncidenceChanger::endAtomicOperation()
1010 if ( !d->mBatchOperationInProgress ) {
1011 kWarning() <<
"No atomic operation is in progress.";
1015 Q_ASSERT_X( d->mLatestAtomicOperationId != 0,
1016 "IncidenceChanger::endAtomicOperation()",
1017 "Call startAtomicOperation() first." );
1019 Q_ASSERT( d->mAtomicOperations.contains(d->mLatestAtomicOperationId) );
1020 AtomicOperation *atomicOperation = d->mAtomicOperations[d->mLatestAtomicOperationId];
1021 Q_ASSERT( atomicOperation );
1022 atomicOperation->endCalled =
true;
1024 const bool allJobsCompleted = !atomicOperation->pendingJobs();
1026 if ( allJobsCompleted && atomicOperation->rolledback() &&
1027 atomicOperation->transactionCompleted ) {
1029 delete d->mAtomicOperations.take( d->mLatestAtomicOperationId );
1030 d->mBatchOperationInProgress =
false;
1037 void IncidenceChanger::setShowDialogsOnError(
bool enable )
1039 d->mShowDialogsOnError = enable;
1042 bool IncidenceChanger::showDialogsOnError()
const
1044 return d->mShowDialogsOnError;
1047 void IncidenceChanger::setRespectsCollectionRights(
bool respects )
1049 d->mRespectsCollectionRights = respects;
1052 bool IncidenceChanger::respectsCollectionRights()
const
1054 return d->mRespectsCollectionRights;
1057 void IncidenceChanger::setDestinationPolicy( IncidenceChanger::DestinationPolicy destinationPolicy )
1059 d->mDestinationPolicy = destinationPolicy;
1062 IncidenceChanger::DestinationPolicy IncidenceChanger::destinationPolicy()
const
1064 return d->mDestinationPolicy;
1069 d->mDefaultCollection = collection;
1072 Collection IncidenceChanger::defaultCollection()
const
1074 return d->mDefaultCollection;
1077 bool IncidenceChanger::historyEnabled()
const
1079 return d->mUseHistory;
1082 void IncidenceChanger::setHistoryEnabled(
bool enable )
1084 if ( d->mUseHistory != enable ) {
1085 d->mUseHistory = enable;
1086 if ( enable && !d->mHistory )
1087 d->mHistory =
new History(
this );
1091 History* IncidenceChanger::history()
const
1098 return d->deleteAlreadyCalled(
id );
1101 void IncidenceChanger::setGroupwareCommunication(
bool enabled )
1103 d->mGroupwareCommunication = enabled;
1106 bool IncidenceChanger::groupwareCommunication()
const
1108 return d->mGroupwareCommunication;
1113 return d->mLastCollectionUsed;
1116 QString IncidenceChanger::Private::showErrorDialog( IncidenceChanger::ResultCode resultCode,
1119 QString errorString;
1120 switch( resultCode ) {
1121 case IncidenceChanger::ResultCodePermissions:
1122 errorString = i18n(
"Operation can not be performed due to ACL restrictions" );
1124 case IncidenceChanger::ResultCodeInvalidUserCollection:
1125 errorString = i18n(
"The chosen collection is invalid" );
1127 case IncidenceChanger::ResultCodeInvalidDefaultCollection:
1128 errorString = i18n(
"Default collection is invalid or doesn't have proper ACLs"
1129 " and DestinationPolicyNeverAsk was used" );
1131 case IncidenceChanger::ResultCodeDuplicateId:
1132 errorString = i18n(
"Duplicate item id in a group operation");
1134 case IncidenceChanger::ResultCodeRolledback:
1135 errorString = i18n(
"One change belonging to a group of changes failed. "
1136 "All changes are being rolled back." );
1140 return QString( i18n(
"Unknown error" ) );
1143 if ( mShowDialogsOnError ) {
1144 KMessageBox::sorry( parent, errorString );
1150 void IncidenceChanger::Private::cancelTransaction()
1152 if ( mBatchOperationInProgress ) {
1153 mAtomicOperations[mLatestAtomicOperationId]->setRolledback();
1157 void IncidenceChanger::Private::cleanupTransaction()
1159 Q_ASSERT( mAtomicOperations.contains(mLatestAtomicOperationId) );
1160 AtomicOperation *operation = mAtomicOperations[mLatestAtomicOperationId];
1161 Q_ASSERT( operation );
1162 Q_ASSERT( operation->rolledback() );
1163 if ( !operation->pendingJobs() && operation->endCalled && operation->transactionCompleted ) {
1164 delete mAtomicOperations.take(mLatestAtomicOperationId);
1165 mBatchOperationInProgress =
false;
1169 bool IncidenceChanger::Private::allowAtomicOperation(
int atomicOperationId,
1170 const Change::Ptr &change )
const
1173 if ( atomicOperationId > 0 ) {
1174 Q_ASSERT( mAtomicOperations.contains( atomicOperationId ) );
1175 AtomicOperation *operation = mAtomicOperations.value( atomicOperationId );
1177 if ( change->type == ChangeTypeCreate ) {
1179 }
else if ( change->type == ChangeTypeModify ) {
1180 allow = !operation->mItemIdsInOperation.contains( change->newItem.id() );
1181 }
else if ( change->type == ChangeTypeDelete ) {
1182 DeletionChange::Ptr deletion = change.staticCast<DeletionChange>();
1184 if ( operation->mItemIdsInOperation.contains(
id ) ) {
1193 kWarning() <<
"Each change belonging to a group operation"
1194 <<
"must have a different Akonadi::Item::Id";
1201 void ModificationChange::emitCompletionSignal()
1203 emitModifyFinished( changer,
id, newItem, resultCode, errorString );
1207 void CreationChange::emitCompletionSignal()
1210 emitCreateFinished( changer,
id, newItem, resultCode, errorString );
1214 void DeletionChange::emitCompletionSignal()
1216 emitDeleteFinished( changer,
id, mItemIds, resultCode, errorString );