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,
94 const Akonadi::Item &item,
95 Akonadi::IncidenceChanger::ResultCode resultCode,
96 const QString &errorString )
98 QMetaObject::invokeMethod( changer,
"createFinished", Qt::QueuedConnection,
99 Q_ARG(
int, changeId ),
100 Q_ARG( Akonadi::Item, item ),
101 Q_ARG( Akonadi::IncidenceChanger::ResultCode, resultCode ),
102 Q_ARG( QString, errorString ) );
106 static void emitModifyFinished( IncidenceChanger *changer,
108 const Akonadi::Item &item,
109 IncidenceChanger::ResultCode resultCode,
110 const QString &errorString )
112 QMetaObject::invokeMethod( changer,
"modifyFinished", Qt::QueuedConnection,
113 Q_ARG(
int, changeId ),
114 Q_ARG( Akonadi::Item, item ),
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 )
227 const Akonadi::Item::Id
id = change->newItem.id();
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;
239 void IncidenceChanger::Private::performNextModification( Akonadi::Item::Id
id )
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 );
291 Akonadi::Item item = j->
item();
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",
312 Q_ASSERT( item.isValid() );
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>();
341 foreach(
const Akonadi::Item &item, items ) {
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 );
387 const Item item = j->
item();
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,
434 Q_ARG( Akonadi::Item::Id, item.id() ) );
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 ) {
459 Q_ASSERT( item.hasPayload() );
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 ) {
527 Q_ASSERT( item.hasPayload() );
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;
728 item.setPayload<Incidence::Ptr>( incidence );
729 item.setMimeType( incidence->mimeType() );
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 ) {
766 if ( !item.isValid() ) {
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 ) {
778 if ( !d->hasRights( item.parentCollection(), ChangeTypeDelete ) ) {
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();
797 Item::List itemsToDelete;
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,
861 if ( !changedItem.isValid() || !changedItem.hasPayload<Incidence::Ptr>() ) {
862 kWarning() <<
"An invalid item or payload is not allowed.";
863 d->cancelTransaction();
867 if ( !d->hasRights( changedItem.parentCollection(), ChangeTypeModify ) ) {
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();
915 Akonadi::Item &newItem = change->newItem;
916 Q_ASSERT( newItem.isValid() );
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() ) {
954 newItem.setRevision( latestRevisionByItemId[
id] );
957 Incidence::Ptr incidence = newItem.payload<Incidence::Ptr>();
959 const int revision = incidence->revision();
960 incidence->setRevision( revision + 1 );
965 newItem.setRemoteRevision( QString() );
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
1096 bool IncidenceChanger::deletedRecently( Akonadi::Item::Id
id )
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>();
1183 foreach( Akonadi::Item::Id
id, deletion->mItemIds ) {
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 );
Item item() const
Returns the created item with the new unique id, or an invalid item if the job failed.
A collection selection dialog.
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.
Job that deletes items from the Akonadi storage.
Can delete items in this collection.
History class for implementing undo/redo of calendar operations.
Can create new items in this collection.
The invitation was sent to all attendees.
Job that creates a new item in the Akonadi storage.
Id id() const
Returns the unique identifier of the entity.
Rights rights() const
Returns the rights the user has on the collection.
Base class for jobs that need to run a sequence of sub-jobs in a transaction.
This class handles sending of invitations to attendees when Incidences (e.g.
Job that modifies an existing item in the Akonadi storage.
Sending was canceled by the user, meaning there are local changes of which other attendees are not aw...
virtual QString errorString() const
Returns the error string, if there has been an error, an empty string otherwise.
bool isValid() const
Returns whether the entity is valid.
Item::List deletedItems() const
Returns the items passed on in the constructor.