• Skip to content
  • Skip to link menu
  • KDE API Reference
  • kdepimlibs-4.11.3 API Reference
  • KDE Home
  • Contact Us
 

akonadi

  • akonadi
  • calendar
incidencechanger.cpp
1 /*
2  Copyright (C) 2004 Reinhold Kainhofer <reinhold@kainhofer.com>
3  Copyright (C) 2010-2012 Sérgio Martins <iamsergio@gmail.com>
4 
5  This library is free software; you can redistribute it and/or modify it
6  under the terms of the GNU Library General Public License as published by
7  the Free Software Foundation; either version 2 of the License, or (at your
8  option) any later version.
9 
10  This library is distributed in the hope that it will be useful, but WITHOUT
11  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
12  FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
13  License for more details.
14 
15  You should have received a copy of the GNU Library General Public License
16  along with this library; see the file COPYING.LIB. If not, write to the
17  Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
18  02110-1301, USA.
19 */
20 #include "incidencechanger.h"
21 #include "incidencechanger_p.h"
22 #include "mailscheduler_p.h"
23 #include "utils_p.h"
24 
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>
30 
31 #include <KJob>
32 #include <KLocalizedString>
33 #include <KGuiItem>
34 #include <KMessageBox>
35 #include <KStandardGuiItem>
36 
37 using namespace Akonadi;
38 using namespace KCalCore;
39 
40 ITIPHandlerHelper::Action actionFromStatus( ITIPHandlerHelper::SendResult result )
41 {
42  //enum SendResult {
43  // Canceled, /**< Sending was canceled by the user, meaning there are
44  // local changes of which other attendees are not aware. */
45  // FailKeepUpdate, /**< Sending failed, the changes to the incidence must be kept. */
46  // FailAbortUpdate, /**< Sending failed, the changes to the incidence must be undone. */
47  // NoSendingNeeded, /**< In some cases it is not needed to send an invitation
48  // (e.g. when we are the only attendee) */
49  // Success
50  switch ( result ) {
51  case ITIPHandlerHelper::ResultCanceled:
52  return ITIPHandlerHelper::ActionDontSendMessage;
53  case ITIPHandlerHelper::ResultSuccess:
54  return ITIPHandlerHelper::ActionSendMessage;
55  default:
56  return ITIPHandlerHelper::ActionAsk;
57  }
58 }
59 
60 namespace Akonadi {
61  static Akonadi::Collection selectCollection( QWidget *parent,
62  int &dialogCode,
63  const QStringList &mimeTypes,
64  const Akonadi::Collection &defCollection )
65  {
66  QPointer<Akonadi::CollectionDialog> dlg( new Akonadi::CollectionDialog( parent ) );
67 
68  kDebug() << "selecting collections with mimeType in " << mimeTypes;
69 
70  dlg->changeCollectionDialogOptions( Akonadi::CollectionDialog::KeepTreeExpanded );
71  dlg->setMimeTypeFilter( mimeTypes );
72  dlg->setAccessRightsFilter( Akonadi::Collection::CanCreateItem );
73  if ( defCollection.isValid() ) {
74  dlg->setDefaultCollection( defCollection );
75  }
76  Akonadi::Collection collection;
77 
78  // FIXME: don't use exec.
79  dialogCode = dlg->exec();
80  if ( dialogCode == QDialog::Accepted ) {
81  collection = dlg->selectedCollection();
82 
83  if ( !collection.isValid() ) {
84  kWarning() <<"An invalid collection was selected!";
85  }
86  }
87  delete dlg;
88 
89  return collection;
90  }
91 
92  // Does a queued emit, with QMetaObject::invokeMethod
93  static void emitCreateFinished( IncidenceChanger *changer,
94  int changeId,
95  const Akonadi::Item &item,
96  Akonadi::IncidenceChanger::ResultCode resultCode,
97  const QString &errorString )
98  {
99  QMetaObject::invokeMethod( changer, "createFinished", Qt::QueuedConnection,
100  Q_ARG( int, changeId ),
101  Q_ARG( Akonadi::Item, item ),
102  Q_ARG( Akonadi::IncidenceChanger::ResultCode, resultCode ),
103  Q_ARG( QString, errorString ) );
104  }
105 
106  // Does a queued emit, with QMetaObject::invokeMethod
107  static void emitModifyFinished( IncidenceChanger *changer,
108  int changeId,
109  const Akonadi::Item &item,
110  IncidenceChanger::ResultCode resultCode,
111  const QString &errorString )
112  {
113  QMetaObject::invokeMethod( changer, "modifyFinished", Qt::QueuedConnection,
114  Q_ARG( int, changeId ),
115  Q_ARG( Akonadi::Item, item ),
116  Q_ARG( Akonadi::IncidenceChanger::ResultCode, resultCode ),
117  Q_ARG( QString, errorString ) );
118  }
119 
120  // Does a queued emit, with QMetaObject::invokeMethod
121  static void emitDeleteFinished( IncidenceChanger *changer,
122  int changeId,
123  const QVector<Akonadi::Item::Id> &itemIdList,
124  IncidenceChanger::ResultCode resultCode,
125  const QString &errorString )
126  {
127  QMetaObject::invokeMethod( changer, "deleteFinished", Qt::QueuedConnection,
128  Q_ARG( int, changeId ),
129  Q_ARG( QVector<Akonadi::Item::Id>, itemIdList ),
130  Q_ARG( Akonadi::IncidenceChanger::ResultCode, resultCode ),
131  Q_ARG( QString, errorString ) );
132  }
133 }
134 
135 class ConflictPreventerPrivate;
136 class ConflictPreventer {
137  friend class ConflictPreventerPrivate;
138 public:
139  static ConflictPreventer* self();
140 
141  // To avoid conflicts when the two modifications came from within the same application
142  QHash<Akonadi::Item::Id, int> mLatestRevisionByItemId;
143 private:
144  ConflictPreventer() {}
145  ~ConflictPreventer() {}
146 };
147 
148 class ConflictPreventerPrivate {
149 public:
150  ConflictPreventer instance;
151 };
152 
153 K_GLOBAL_STATIC( ConflictPreventerPrivate, sConflictPreventerPrivate )
154 
155 ConflictPreventer* ConflictPreventer::self()
156 {
157  return &sConflictPreventerPrivate->instance;
158 }
159 
160 IncidenceChanger::Private::Private( bool enableHistory, IncidenceChanger *qq ) : q( qq )
161 {
162  mLatestChangeId = 0;
163  mShowDialogsOnError = true;
164  mHistory = enableHistory ? new History( this ) : 0;
165  mUseHistory = enableHistory;
166  mDestinationPolicy = DestinationPolicyDefault;
167  mRespectsCollectionRights = false;
168  mGroupwareCommunication = false;
169  mLatestAtomicOperationId = 0;
170  mBatchOperationInProgress = false;
171 
172  qRegisterMetaType<QVector<Akonadi::Item::Id> >( "QVector<Akonadi::Item::Id>" );
173  qRegisterMetaType<Akonadi::Item::Id>( "Akonadi::Item::Id" );
174  qRegisterMetaType<Akonadi::Item>("Akonadi::Item");
175  qRegisterMetaType<Akonadi::IncidenceChanger::ResultCode>(
176  "Akonadi::IncidenceChanger::ResultCode" );
177 }
178 
179 IncidenceChanger::Private::~Private()
180 {
181  if ( !mAtomicOperations.isEmpty() ||
182  !mQueuedModifications.isEmpty() ||
183  !mModificationsInProgress.isEmpty() ) {
184  kDebug() << "Normal if the application was being used. "
185  "But might indicate a memory leak if it wasn't";
186  }
187 }
188 
189 bool IncidenceChanger::Private::atomicOperationIsValid( uint atomicOperationId ) const
190 {
191  // Changes must be done between startAtomicOperation() and endAtomicOperation()
192  return mAtomicOperations.contains( atomicOperationId ) &&
193  !mAtomicOperations[atomicOperationId]->m_endCalled;
194 }
195 
196 bool IncidenceChanger::Private::hasRights( const Collection &collection,
197  IncidenceChanger::ChangeType changeType ) const
198 {
199  bool result = false;
200  switch( changeType ) {
201  case ChangeTypeCreate:
202  result = collection.rights() & Akonadi::Collection::CanCreateItem;
203  break;
204  case ChangeTypeModify:
205  result = collection.rights() & Akonadi::Collection::CanChangeItem;
206  break;
207  case ChangeTypeDelete:
208  result = collection.rights() & Akonadi::Collection::CanDeleteItem;
209  break;
210  default:
211  Q_ASSERT_X( false, "hasRights", "invalid type" );
212  }
213 
214  return !collection.isValid() || !mRespectsCollectionRights || result;
215 }
216 
217 Akonadi::Job* IncidenceChanger::Private::parentJob( const Change::Ptr &change ) const
218 {
219  return (mBatchOperationInProgress && !change->queuedModification) ?
220  mAtomicOperations[mLatestAtomicOperationId]->transaction() : 0;
221 }
222 
223 void IncidenceChanger::Private::queueModification( Change::Ptr change )
224 {
225  // If there's already a change queued we just discard it
226  // and send the newer change, which already includes
227  // previous modifications
228  const Akonadi::Item::Id id = change->newItem.id();
229  if ( mQueuedModifications.contains( id ) ) {
230  Change::Ptr toBeDiscarded = mQueuedModifications.take( id );
231  toBeDiscarded->resultCode = ResultCodeModificationDiscarded;
232  toBeDiscarded->completed = true;
233  mChangeById.remove( toBeDiscarded->id );
234  }
235 
236  change->queuedModification = true;
237  mQueuedModifications[id] = change;
238 }
239 
240 void IncidenceChanger::Private::performNextModification( Akonadi::Item::Id id )
241 {
242  mModificationsInProgress.remove( id );
243 
244  if ( mQueuedModifications.contains( id ) ) {
245  const Change::Ptr change = mQueuedModifications.take( id );
246  performModification( change );
247  }
248 }
249 
250 void IncidenceChanger::Private::handleTransactionJobResult( KJob *job )
251 {
252  //kDebug();
253  TransactionSequence *transaction = qobject_cast<TransactionSequence*>( job );
254  Q_ASSERT( transaction );
255  Q_ASSERT( mAtomicOperationByTransaction.contains( transaction ) );
256 
257  const uint atomicOperationId = mAtomicOperationByTransaction.take( transaction );
258 
259  Q_ASSERT( mAtomicOperations.contains(atomicOperationId) );
260  AtomicOperation *operation = mAtomicOperations[atomicOperationId];
261  Q_ASSERT( operation );
262  Q_ASSERT( operation->m_id == atomicOperationId );
263  if ( job->error() ) {
264  if ( !operation->rolledback() )
265  operation->setRolledback();
266  kError() << "Transaction failed, everything was rolledback. "
267  << job->errorString();
268  } else {
269  Q_ASSERT( operation->m_endCalled );
270  Q_ASSERT( !operation->pendingJobs() );
271  }
272 
273  if ( !operation->pendingJobs() && operation->m_endCalled ) {
274  delete mAtomicOperations.take( atomicOperationId );
275  mBatchOperationInProgress = false;
276  } else {
277  operation->m_transactionCompleted = true;
278  }
279 }
280 
281 void IncidenceChanger::Private::handleCreateJobResult( KJob *job )
282 {
283  //kDebug();
284  QString errorString;
285  ResultCode resultCode = ResultCodeSuccess;
286 
287  Change::Ptr change = mChangeForJob.take( job );
288  mChangeById.remove( change->id );
289 
290  const ItemCreateJob *j = qobject_cast<const ItemCreateJob*>( job );
291  Q_ASSERT( j );
292  Akonadi::Item item = j->item();
293 
294  QString description;
295  if ( change->atomicOperationId != 0 ) {
296  AtomicOperation *a = mAtomicOperations[change->atomicOperationId];
297  a->m_numCompletedChanges++;
298  change->completed = true;
299  description = a->m_description;
300  }
301 
302  if ( j->error() ) {
303  item = change->newItem;
304  resultCode = ResultCodeJobError;
305  errorString = j->errorString();
306  kError() << errorString;
307  if ( mShowDialogsOnError ) {
308  KMessageBox::sorry( change->parentWidget,
309  i18n( "Error while trying to create calendar item. Error was: %1",
310  errorString ) );
311  }
312  } else {
313  Q_ASSERT( item.isValid() );
314  Q_ASSERT( item.hasPayload<KCalCore::Incidence::Ptr>() );
315  change->newItem = item;
316  handleInvitationsAfterChange( change );
317  // for user undo/redo
318  if ( change->recordToHistory ) {
319  mHistory->recordCreation( item, description, change->atomicOperationId );
320  }
321  }
322 
323  change->errorString = errorString;
324  change->resultCode = resultCode;
325  // puff, change finally goes out of scope, and emits the incidenceCreated signal.
326 }
327 
328 void IncidenceChanger::Private::handleDeleteJobResult( KJob *job )
329 {
330  //kDebug();
331  QString errorString;
332  ResultCode resultCode = ResultCodeSuccess;
333 
334  Change::Ptr change = mChangeForJob.take( job );
335  mChangeById.remove( change->id );
336 
337  const ItemDeleteJob *j = qobject_cast<const ItemDeleteJob*>( job );
338  const Item::List items = j->deletedItems();
339 
340  QSharedPointer<DeletionChange> deletionChange = change.staticCast<DeletionChange>();
341 
342  foreach( const Akonadi::Item &item, items ) {
343  deletionChange->mItemIds.append( item.id() );
344  }
345  QString description;
346  if ( change->atomicOperationId != 0 ) {
347  AtomicOperation *a = mAtomicOperations[change->atomicOperationId];
348  a->m_numCompletedChanges++;
349  change->completed = true;
350  description = a->m_description;
351  }
352  if ( j->error() ) {
353  resultCode = ResultCodeJobError;
354  errorString = j->errorString();
355  kError() << errorString;
356  if ( mShowDialogsOnError ) {
357  KMessageBox::sorry( change->parentWidget,
358  i18n( "Error while trying to delete calendar item. Error was: %1",
359  errorString ) );
360  }
361 
362  foreach( const Item &item, items ) {
363  // Werent deleted due to error
364  mDeletedItemIds.remove( mDeletedItemIds.indexOf( item.id() ) );
365  }
366  } else { // success
367  if ( change->recordToHistory ) {
368  Q_ASSERT( mHistory );
369  mHistory->recordDeletions( items, description, change->atomicOperationId );
370  }
371 
372  handleInvitationsAfterChange( change );
373  }
374 
375  change->errorString = errorString;
376  change->resultCode = resultCode;
377  // puff, change finally goes out of scope, and emits the incidenceDeleted signal.
378 }
379 
380 void IncidenceChanger::Private::handleModifyJobResult( KJob *job )
381 {
382  QString errorString;
383  ResultCode resultCode = ResultCodeSuccess;
384  Change::Ptr change = mChangeForJob.take( job );
385  mChangeById.remove( change->id );
386 
387  const ItemModifyJob *j = qobject_cast<const ItemModifyJob*>( job );
388  const Item item = j->item();
389  Q_ASSERT( mDirtyFieldsByJob.contains( job ) );
390  Q_ASSERT( item.hasPayload<KCalCore::Incidence::Ptr>() );
391  item.payload<KCalCore::Incidence::Ptr>()->setDirtyFields( mDirtyFieldsByJob.value( job ) );
392  const QSet<KCalCore::IncidenceBase::Field> dirtyFields = mDirtyFieldsByJob.value( job );
393  QString description;
394  if ( change->atomicOperationId != 0 ) {
395  AtomicOperation *a = mAtomicOperations[change->atomicOperationId];
396  a->m_numCompletedChanges++;
397  change->completed = true;
398  description = a->m_description;
399  }
400  if ( j->error() ) {
401  if ( deleteAlreadyCalled( item.id() ) ) {
402  // User deleted the item almost at the same time he changed it. We could just return success
403  // but the delete is probably already recorded to History, and that would make undo not work
404  // in the proper order.
405  resultCode = ResultCodeAlreadyDeleted;
406  errorString = j->errorString();
407  kWarning() << "Trying to change item " << item.id() << " while deletion is in progress.";
408  } else {
409  resultCode = ResultCodeJobError;
410  errorString = j->errorString();
411  kError() << errorString;
412  }
413  if ( mShowDialogsOnError ) {
414  KMessageBox::sorry( change->parentWidget,
415  i18n( "Error while trying to modify calendar item. Error was: %1",
416  errorString ) );
417  }
418  } else { // success
419  ConflictPreventer::self()->mLatestRevisionByItemId[item.id()] = item.revision();
420  change->newItem = item;
421  if ( change->recordToHistory && !change->originalItems.isEmpty() ) {
422  Q_ASSERT( change->originalItems.count() == 1 );
423  mHistory->recordModification( change->originalItems.first(), item,
424  description, change->atomicOperationId );
425  }
426 
427  handleInvitationsAfterChange( change );
428  }
429 
430  change->errorString = errorString;
431  change->resultCode = resultCode;
432  // puff, change finally goes out of scope, and emits the incidenceModified signal.
433 
434  QMetaObject::invokeMethod( this, "performNextModification",
435  Qt::QueuedConnection,
436  Q_ARG( Akonadi::Item::Id, item.id() ) );
437 }
438 
439 bool IncidenceChanger::Private::deleteAlreadyCalled( Akonadi::Item::Id id ) const
440 {
441  return mDeletedItemIds.contains( id );
442 }
443 
444 bool IncidenceChanger::Private::handleInvitationsBeforeChange( const Change::Ptr &change )
445 {
446  bool result = true;
447  if ( mGroupwareCommunication ) {
448  ITIPHandlerHelper handler( change->parentWidget ); // TODO make async
449  if ( mInvitationStatusByAtomicOperation.contains( change->atomicOperationId ) ) {
450  handler.setDefaultAction( actionFromStatus( mInvitationStatusByAtomicOperation.value( change->atomicOperationId ) ) );
451  }
452 
453  switch( change->type ) {
454  case IncidenceChanger::ChangeTypeCreate:
455  // nothing needs to be done
456  break;
457  case IncidenceChanger::ChangeTypeDelete:
458  {
459  ITIPHandlerHelper::SendResult status;
460  Q_ASSERT( !change->originalItems.isEmpty() );
461  foreach( const Akonadi::Item &item, change->originalItems ) {
462  Q_ASSERT( item.hasPayload<KCalCore::Incidence::Ptr>() );
463  Incidence::Ptr incidence = CalendarUtils::incidence( item );
464  if ( !incidence->supportsGroupwareCommunication() ) {
465  continue;
466  }
467  // We only send CANCEL if we're the organizer.
468  // If we're not, then we send REPLY with PartStat=Declined in handleInvitationsAfterChange()
469  if ( Akonadi::CalendarUtils::thatIsMe( incidence->organizer()->email() ) ) {
470  status = handler.sendIncidenceDeletedMessage( KCalCore::iTIPCancel, incidence );
471  if ( change->atomicOperationId ) {
472  mInvitationStatusByAtomicOperation.insert( change->atomicOperationId, status );
473  }
474  result = status != ITIPHandlerHelper::ResultFailAbortUpdate;
475  //TODO: with some status we want to break immediately
476  }
477  }
478  }
479  break;
480  case IncidenceChanger::ChangeTypeModify:
481  {
482  if ( change->originalItems.isEmpty() ) {
483  break;
484  }
485 
486  Q_ASSERT( change->originalItems.count() == 1 );
487  Incidence::Ptr oldIncidence = change->originalItems.first().payload<KCalCore::Incidence::Ptr>();
488  Incidence::Ptr newIncidence = change->newItem.payload<KCalCore::Incidence::Ptr>();
489 
490  if ( !oldIncidence->supportsGroupwareCommunication() ) {
491  break;
492  }
493 
494  const bool modify = handler.handleIncidenceAboutToBeModified( newIncidence );
495  if ( modify ) {
496  break;
497  }
498 
499  if ( newIncidence->type() == oldIncidence->type() ) {
500  IncidenceBase *i1 = newIncidence.data();
501  IncidenceBase *i2 = oldIncidence.data();
502  *i1 = *i2;
503  }
504  result = false;
505  }
506  break;
507  default:
508  Q_ASSERT( false );
509  result = false;
510  }
511  }
512  return result;
513 }
514 
515 bool IncidenceChanger::Private::handleInvitationsAfterChange( const Change::Ptr &change )
516 {
517  if ( change->useGroupwareCommunication ) {
518  ITIPHandlerHelper handler( change->parentWidget ); // TODO make async
519  switch( change->type ) {
520  case IncidenceChanger::ChangeTypeCreate:
521  {
522  Incidence::Ptr incidence = change->newItem.payload<KCalCore::Incidence::Ptr>();
523  if ( incidence->supportsGroupwareCommunication() ) {
524  const ITIPHandlerHelper::SendResult status =
525  handler.sendIncidenceCreatedMessage( KCalCore::iTIPRequest, incidence );
526 
527  if ( status == ITIPHandlerHelper::ResultFailAbortUpdate ) {
528  kError() << "Sending invitations failed, but did not delete the incidence";
529  }
530 
531  const uint atomicOperationId = change->atomicOperationId;
532  if ( atomicOperationId != 0 ) {
533  mInvitationStatusByAtomicOperation.insert( atomicOperationId, status );
534  }
535  }
536  }
537  break;
538  case IncidenceChanger::ChangeTypeDelete:
539  {
540  Q_ASSERT( !change->originalItems.isEmpty() );
541  foreach( const Akonadi::Item &item, change->originalItems ) {
542  Q_ASSERT( item.hasPayload() );
543  Incidence::Ptr incidence = item.payload<KCalCore::Incidence::Ptr>();
544  Q_ASSERT( incidence );
545  if ( !incidence->supportsGroupwareCommunication() )
546  continue;
547 
548  if ( !Akonadi::CalendarUtils::thatIsMe( incidence->organizer()->email() ) ) {
549  const QStringList myEmails = Akonadi::CalendarUtils::allEmails();
550  bool notifyOrganizer = false;
551  for ( QStringList::ConstIterator it = myEmails.begin(); it != myEmails.end(); ++it ) {
552  const QString email = *it;
553  KCalCore::Attendee::Ptr me( incidence->attendeeByMail( email ) );
554  if ( me ) {
555  if ( me->status() == KCalCore::Attendee::Accepted ||
556  me->status() == KCalCore::Attendee::Delegated ) {
557  notifyOrganizer = true;
558  }
559  KCalCore::Attendee::Ptr newMe( new KCalCore::Attendee( *me ) );
560  newMe->setStatus( KCalCore::Attendee::Declined );
561  incidence->clearAttendees();
562  incidence->addAttendee( newMe );
563  break;
564  }
565  }
566 
567  if ( notifyOrganizer ) {
568  MailScheduler scheduler; // TODO make async
569  scheduler.performTransaction( incidence, KCalCore::iTIPReply );
570  }
571  }
572  }
573  }
574  break;
575  case IncidenceChanger::ChangeTypeModify:
576  {
577  if ( change->originalItems.isEmpty() ) {
578  break;
579  }
580 
581  Q_ASSERT( change->originalItems.count() == 1 );
582  Incidence::Ptr oldIncidence = CalendarUtils::incidence( change->originalItems.first() );
583  Incidence::Ptr newIncidence = CalendarUtils::incidence( change->newItem );
584 
585  if ( !newIncidence->supportsGroupwareCommunication() ||
586  !Akonadi::CalendarUtils::thatIsMe( newIncidence->organizer()->email() ) ) {
587  // If we're not the organizer, the user already saw the "Do you really want to do this, incidence will become out of sync"
588  break;
589  }
590 
591  if ( mInvitationStatusByAtomicOperation.contains( change->atomicOperationId ) ) {
592  handler.setDefaultAction( actionFromStatus( mInvitationStatusByAtomicOperation.value( change->atomicOperationId ) ) );
593  }
594 
595  const bool attendeeStatusChanged = myAttendeeStatusChanged( newIncidence,
596  oldIncidence,
597  Akonadi::CalendarUtils::allEmails() );
598 
599  ITIPHandlerHelper::SendResult status = handler.sendIncidenceModifiedMessage( KCalCore::iTIPRequest,
600  newIncidence,
601  attendeeStatusChanged );
602 
603  if ( change->atomicOperationId != 0 ) {
604  mInvitationStatusByAtomicOperation.insert( change->atomicOperationId, status );
605  }
606  }
607  break;
608  default:
609  Q_ASSERT( false );
610  return false;
611  }
612  }
613  return true;
614 }
615 
617 bool IncidenceChanger::Private::myAttendeeStatusChanged( const Incidence::Ptr &newInc,
618  const Incidence::Ptr &oldInc,
619  const QStringList &myEmails )
620 {
621  Q_ASSERT( newInc );
622  Q_ASSERT( oldInc );
623  const Attendee::Ptr oldMe = oldInc->attendeeByMails( myEmails );
624  const Attendee::Ptr newMe = newInc->attendeeByMails( myEmails );
625 
626  return oldMe && newMe && oldMe->status() != newMe->status();
627 }
628 
629 IncidenceChanger::IncidenceChanger( QObject *parent ) : QObject( parent )
630  , d( new Private( true, this ) )
631 {
632 }
633 
634 IncidenceChanger::IncidenceChanger( bool enableHistory, QObject *parent ) : QObject( parent )
635  , d( new Private( enableHistory, this ) )
636 {
637 }
638 
639 IncidenceChanger::~IncidenceChanger()
640 {
641  delete d;
642 }
643 
644 int IncidenceChanger::createIncidence( const Incidence::Ptr &incidence,
645  const Collection &collection,
646  QWidget *parent )
647 {
648  //kDebug();
649  if ( !incidence ) {
650  kWarning() << "An invalid payload is not allowed.";
651  d->cancelTransaction();
652  return -1;
653  }
654 
655  const uint atomicOperationId = d->mBatchOperationInProgress ? d->mLatestAtomicOperationId : 0;
656 
657  const Change::Ptr change( new CreationChange( this, ++d->mLatestChangeId,
658  atomicOperationId, parent ) );
659  Collection collectionToUse;
660 
661  const int changeId = change->id;
662  Q_ASSERT( !( d->mBatchOperationInProgress && !d->mAtomicOperations.contains( atomicOperationId ) ) );
663  if ( d->mBatchOperationInProgress && d->mAtomicOperations[atomicOperationId]->rolledback() ) {
664  const QString errorMessage = d->showErrorDialog( ResultCodeRolledback, parent );
665  kWarning() << errorMessage;
666 
667  change->resultCode = ResultCodeRolledback;
668  change->errorString = errorMessage;
669  d->cleanupTransaction();
670  return changeId;
671  }
672 
673  d->handleInvitationsBeforeChange( change );
674 
675  if ( collection.isValid() && d->hasRights( collection, ChangeTypeCreate ) ) {
676  // The collection passed always has priority
677  collectionToUse = collection;
678  } else {
679  switch( d->mDestinationPolicy ) {
680  case DestinationPolicyDefault:
681  if ( d->mDefaultCollection.isValid() &&
682  d->hasRights( d->mDefaultCollection, ChangeTypeCreate ) ) {
683  collectionToUse = d->mDefaultCollection;
684  break;
685  }
686  kWarning() << "Destination policy is to use the default collection."
687  << "But it's invalid or doesn't have proper ACLs."
688  << "isValid = " << d->mDefaultCollection.isValid()
689  << "has ACLs = " << d->hasRights( d->mDefaultCollection,
690  ChangeTypeCreate );
691  // else fallthrough, and ask the user.
692  case DestinationPolicyAsk:
693  {
694  int dialogCode;
695  const QStringList mimeTypes( incidence->mimeType() );
696  collectionToUse = selectCollection( parent, dialogCode /*by-ref*/, mimeTypes,
697  d->mDefaultCollection );
698  if ( dialogCode != QDialog::Accepted ) {
699  kDebug() << "User canceled collection choosing";
700  change->resultCode = ResultCodeUserCanceled;
701  d->cancelTransaction();
702  return changeId;
703  }
704 
705  if ( collectionToUse.isValid() && !d->hasRights( collectionToUse, ChangeTypeCreate ) ) {
706  kWarning() << "No ACLs for incidence creation";
707  const QString errorMessage = d->showErrorDialog( ResultCodePermissions, parent );
708  change->resultCode = ResultCodePermissions;
709  change->errorString = errorMessage;
710  d->cancelTransaction();
711  return changeId;
712  }
713 
714  // TODO: add unit test for these two situations after reviewing API
715  if ( !collectionToUse.isValid() ) {
716  kError() << "Invalid collection selected. Can't create incidence.";
717  change->resultCode = ResultCodeInvalidUserCollection;
718  const QString errorString = d->showErrorDialog( ResultCodeInvalidUserCollection, parent );
719  change->errorString = errorString;
720  d->cancelTransaction();
721  return changeId;
722  }
723  }
724  break;
725  case DestinationPolicyNeverAsk:
726  {
727  const bool hasRights = d->hasRights( d->mDefaultCollection, ChangeTypeCreate );
728  if ( d->mDefaultCollection.isValid() && hasRights ) {
729  collectionToUse = d->mDefaultCollection;
730  } else {
731  const QString errorString = d->showErrorDialog( ResultCodeInvalidDefaultCollection, parent );
732  kError() << errorString << "; rights are " << hasRights;
733  change->resultCode = hasRights ? ResultCodeInvalidDefaultCollection :
734  ResultCodePermissions;
735  change->errorString = errorString;
736  d->cancelTransaction();
737  return changeId;
738  }
739  }
740  break;
741  default:
742  // Never happens
743  Q_ASSERT_X( false, "createIncidence()", "unknown destination policy" );
744  d->cancelTransaction();
745  return -1;
746  }
747  }
748 
749  d->mLastCollectionUsed = collectionToUse;
750 
751  Item item;
752  item.setPayload<Incidence::Ptr>( incidence );
753  item.setMimeType( incidence->mimeType() );
754 
755  ItemCreateJob *createJob = new ItemCreateJob( item, collectionToUse, d->parentJob( change ) );
756  d->mChangeForJob.insert( createJob, change );
757 
758  if ( d->mBatchOperationInProgress ) {
759  AtomicOperation *atomic = d->mAtomicOperations[d->mLatestAtomicOperationId];
760  Q_ASSERT( atomic );
761  atomic->addChange( change );
762  }
763 
764  // QueuedConnection because of possible sync exec calls.
765  connect( createJob, SIGNAL(result(KJob*)),
766  d, SLOT(handleCreateJobResult(KJob*)), Qt::QueuedConnection );
767 
768  d->mChangeById.insert( changeId, change );
769  return change->id;
770 }
771 
772 int IncidenceChanger::deleteIncidence( const Item &item, QWidget *parent )
773 {
774  Item::List list;
775  list.append( item );
776 
777  return deleteIncidences( list, parent );
778 }
779 
780 int IncidenceChanger::deleteIncidences( const Item::List &items, QWidget *parent )
781 {
782  //kDebug();
783  if ( items.isEmpty() ) {
784  kError() << "Delete what?";
785  d->cancelTransaction();
786  return -1;
787  }
788 
789  foreach( const Item &item, items ) {
790  if ( !item.isValid() ) {
791  kError() << "Items must be valid!";
792  d->cancelTransaction();
793  return -1;
794  }
795  }
796 
797  const uint atomicOperationId = d->mBatchOperationInProgress ? d->mLatestAtomicOperationId : 0;
798  const int changeId = ++d->mLatestChangeId;
799  const Change::Ptr change( new DeletionChange( this, changeId, atomicOperationId, parent ) );
800 
801  foreach( const Item &item, items ) {
802  if ( !d->hasRights( item.parentCollection(), ChangeTypeDelete ) ) {
803  kWarning() << "Item " << item.id() << " can't be deleted due to ACL restrictions";
804  const QString errorString = d->showErrorDialog( ResultCodePermissions, parent );
805  change->resultCode = ResultCodePermissions;
806  change->errorString = errorString;
807  d->cancelTransaction();
808  return changeId;
809  }
810  }
811 
812  if ( !d->allowAtomicOperation( atomicOperationId, change ) ) {
813  const QString errorString = d->showErrorDialog( ResultCodeDuplicateId, parent );
814  change->resultCode = ResultCodeDuplicateId;
815  change->errorString = errorString;
816  kWarning() << errorString;
817  d->cancelTransaction();
818  return changeId;
819  }
820 
821  Item::List itemsToDelete;
822  foreach( const Item &item, items ) {
823  if ( d->deleteAlreadyCalled( item.id() ) ) {
824  // IncidenceChanger::deleteIncidence() called twice, ignore this one.
825  kDebug() << "Item " << item.id() << " already deleted or being deleted, skipping";
826  } else {
827  itemsToDelete.append( item );
828  }
829  }
830 
831  if ( d->mBatchOperationInProgress && d->mAtomicOperations[atomicOperationId]->rolledback() ) {
832  const QString errorMessage = d->showErrorDialog( ResultCodeRolledback, parent );
833  change->resultCode = ResultCodeRolledback;
834  change->errorString = errorMessage;
835  kError() << errorMessage;
836  d->cleanupTransaction();
837  return changeId;
838  }
839 
840  if ( itemsToDelete.isEmpty() ) {
841  QVector<Akonadi::Item::Id> itemIdList;
842  itemIdList.append( Item().id() );
843  kDebug() << "Items already deleted or being deleted, skipping";
844  const QString errorMessage =
845  i18n( "That calendar item was already deleted, or currently being deleted." );
846  // Queued emit because return must be executed first, otherwise caller won't know this workId
847  change->resultCode = ResultCodeAlreadyDeleted;
848  change->errorString = errorMessage;
849  d->cancelTransaction();
850  kWarning() << errorMessage;
851  return changeId;
852  }
853  change->originalItems = itemsToDelete;
854  d->handleInvitationsBeforeChange( change );
855 
856  ItemDeleteJob *deleteJob = new ItemDeleteJob( itemsToDelete, d->parentJob( change ) );
857  d->mChangeForJob.insert( deleteJob, change );
858  d->mChangeById.insert( changeId, change );
859 
860  if ( d->mBatchOperationInProgress ) {
861  AtomicOperation *atomic = d->mAtomicOperations[atomicOperationId];
862  Q_ASSERT( atomic );
863  atomic->addChange( change );
864  }
865 
866  foreach( const Item &item, itemsToDelete ) {
867  d->mDeletedItemIds << item.id();
868  }
869 
870  // Do some cleanup
871  if ( d->mDeletedItemIds.count() > 100 )
872  d->mDeletedItemIds.remove( 0, 50 );
873 
874  // QueuedConnection because of possible sync exec calls.
875  connect( deleteJob, SIGNAL(result(KJob*)),
876  d, SLOT(handleDeleteJobResult(KJob*)), Qt::QueuedConnection );
877 
878  return changeId;
879 }
880 
881 int IncidenceChanger::modifyIncidence( const Item &changedItem,
882  const KCalCore::Incidence::Ptr &originalPayload,
883  QWidget *parent )
884 {
885  if ( !changedItem.isValid() || !changedItem.hasPayload<Incidence::Ptr>() ) {
886  kWarning() << "An invalid item or payload is not allowed.";
887  d->cancelTransaction();
888  return -1;
889  }
890 
891  if ( !d->hasRights( changedItem.parentCollection(), ChangeTypeModify ) ) {
892  kWarning() << "Item " << changedItem.id() << " can't be deleted due to ACL restrictions";
893  const int changeId = ++d->mLatestChangeId;
894  const QString errorString = d->showErrorDialog( ResultCodePermissions, parent );
895  emitModifyFinished( this, changeId, changedItem, ResultCodePermissions, errorString );
896  d->cancelTransaction();
897  return changeId;
898  }
899 
900  //TODO also update revision here instead of in the editor
901  changedItem.payload<Incidence::Ptr>()->setLastModified( KDateTime::currentUtcDateTime() );
902 
903  const uint atomicOperationId = d->mBatchOperationInProgress ? d->mLatestAtomicOperationId : 0;
904  const int changeId = ++d->mLatestChangeId;
905  ModificationChange *modificationChange = new ModificationChange( this, changeId,
906  atomicOperationId, parent );
907  Change::Ptr change( modificationChange );
908 
909  if ( originalPayload ) {
910  Item originalItem( changedItem );
911  originalItem.setPayload<KCalCore::Incidence::Ptr>( originalPayload );
912  modificationChange->originalItems << originalItem;
913  }
914 
915  modificationChange->newItem = changedItem;
916  d->mChangeById.insert( changeId, change );
917 
918  if ( !d->allowAtomicOperation( atomicOperationId, change ) ) {
919  const QString errorString = d->showErrorDialog( ResultCodeDuplicateId, parent );
920  change->resultCode = ResultCodeDuplicateId;
921  change->errorString = errorString;
922  d->cancelTransaction();
923  kWarning() << "Atomic operation now allowed";
924  return changeId;
925  }
926 
927  if ( d->mBatchOperationInProgress && d->mAtomicOperations[atomicOperationId]->rolledback() ) {
928  const QString errorMessage = d->showErrorDialog( ResultCodeRolledback, parent );
929  kError() << errorMessage;
930  d->cleanupTransaction();
931  emitModifyFinished( this, changeId, changedItem, ResultCodeRolledback, errorMessage );
932  } else {
933  d->performModification( change );
934  }
935 
936  return changeId;
937 }
938 
939 void IncidenceChanger::Private::performModification( Change::Ptr change )
940 {
941  const Item::Id id = change->newItem.id();
942  Akonadi::Item &newItem = change->newItem;
943  Q_ASSERT( newItem.isValid() );
944  Q_ASSERT( newItem.hasPayload<Incidence::Ptr>() );
945 
946  const int changeId = change->id;
947 
948  if ( deleteAlreadyCalled( id ) ) {
949  // IncidenceChanger::deleteIncidence() called twice, ignore this one.
950  kDebug() << "Item " << id << " already deleted or being deleted, skipping";
951 
952  // Queued emit because return must be executed first, otherwise caller won't know this workId
953  emitModifyFinished( q, change->id, newItem, ResultCodeAlreadyDeleted,
954  i18n( "That calendar item was already deleted, or currently being deleted." ) );
955  return;
956  }
957 
958  const uint atomicOperationId = change->atomicOperationId;
959  const bool hasAtomicOperationId = atomicOperationId != 0;
960  if ( hasAtomicOperationId &&
961  mAtomicOperations[atomicOperationId]->rolledback() ) {
962  const QString errorMessage = showErrorDialog( ResultCodeRolledback, 0 );
963  kError() << errorMessage;
964  emitModifyFinished( q, changeId, newItem, ResultCodeRolledback, errorMessage );
965  return;
966  }
967 
968  const bool userCancelled = !handleInvitationsBeforeChange( change );
969  if ( userCancelled ) {
970  // User got a "You're not the organizer, do you really want to send" dialog, and said "no"
971  kDebug() << "User cancelled, giving up";
972  emitModifyFinished( q, changeId, newItem, ResultCodeUserCanceled, QString() );
973  return;
974  }
975 
976  QHash<Akonadi::Item::Id, int> &latestRevisionByItemId =
977  ConflictPreventer::self()->mLatestRevisionByItemId;
978  if ( latestRevisionByItemId.contains( id ) &&
979  latestRevisionByItemId[id] > newItem.revision() ) {
980  /* When a ItemModifyJob ends, the application can still modify the old items if the user
981  * is quick because the ETM wasn't updated yet, and we'll get a STORE error, because
982  * we are not modifying the latest revision.
983  *
984  * When a job ends, we keep the new revision in mLatestRevisionByItemId
985  * so we can update the item's revision
986  */
987  newItem.setRevision( latestRevisionByItemId[id] );
988  }
989 
990  Incidence::Ptr incidence = newItem.payload<Incidence::Ptr>();
991  { // increment revision ( KCalCore revision, not akonadi )
992  const int revision = incidence->revision();
993  incidence->setRevision( revision + 1 );
994  }
995 
996  // Dav Fix
997  // Don't write back remote revision since we can't make sure it is the current one
998  newItem.setRemoteRevision( QString() );
999 
1000  if ( mModificationsInProgress.contains( newItem.id() ) ) {
1001  // There's already a ItemModifyJob running for this item ID
1002  // Let's wait for it to end.
1003  queueModification( change );
1004  } else {
1005  ItemModifyJob *modifyJob = new ItemModifyJob( newItem, parentJob( change ) );
1006  mChangeForJob.insert( modifyJob, change );
1007  mDirtyFieldsByJob.insert( modifyJob, incidence->dirtyFields() );
1008 
1009  if ( hasAtomicOperationId ) {
1010  AtomicOperation *atomic = mAtomicOperations[atomicOperationId];
1011  Q_ASSERT( atomic );
1012  atomic->addChange( change );
1013  }
1014 
1015  mModificationsInProgress[newItem.id()] = change;
1016  // QueuedConnection because of possible sync exec calls.
1017  connect( modifyJob, SIGNAL(result(KJob*)),
1018  SLOT(handleModifyJobResult(KJob*)), Qt::QueuedConnection );
1019  }
1020 }
1021 
1022 void IncidenceChanger::startAtomicOperation( const QString &operationDescription )
1023 {
1024  if ( d->mBatchOperationInProgress ) {
1025  kDebug() << "An atomic operation is already in progress.";
1026  return;
1027  }
1028 
1029  ++d->mLatestAtomicOperationId;
1030  d->mBatchOperationInProgress = true;
1031 
1032  AtomicOperation *atomicOperation = new AtomicOperation( d, d->mLatestAtomicOperationId );
1033  atomicOperation->m_description = operationDescription;
1034  d->mAtomicOperations.insert( d->mLatestAtomicOperationId, atomicOperation );
1035 }
1036 
1037 void IncidenceChanger::endAtomicOperation()
1038 {
1039  if ( !d->mBatchOperationInProgress ) {
1040  kDebug() << "No atomic operation is in progress.";
1041  return;
1042  }
1043 
1044  Q_ASSERT_X( d->mLatestAtomicOperationId != 0,
1045  "IncidenceChanger::endAtomicOperation()",
1046  "Call startAtomicOperation() first." );
1047 
1048  Q_ASSERT( d->mAtomicOperations.contains(d->mLatestAtomicOperationId) );
1049  AtomicOperation *atomicOperation = d->mAtomicOperations[d->mLatestAtomicOperationId];
1050  Q_ASSERT( atomicOperation );
1051  atomicOperation->m_endCalled = true;
1052 
1053  const bool allJobsCompleted = !atomicOperation->pendingJobs();
1054 
1055  if ( allJobsCompleted && atomicOperation->rolledback() &&
1056  atomicOperation->m_transactionCompleted ) {
1057  // The transaction job already completed, we can cleanup:
1058  delete d->mAtomicOperations.take( d->mLatestAtomicOperationId );
1059  d->mBatchOperationInProgress = false;
1060  }/* else if ( allJobsCompleted ) {
1061  Q_ASSERT( atomicOperation->transaction );
1062  atomicOperation->transaction->commit(); we using autocommit now
1063  }*/
1064 }
1065 
1066 void IncidenceChanger::setShowDialogsOnError( bool enable )
1067 {
1068  d->mShowDialogsOnError = enable;
1069 }
1070 
1071 bool IncidenceChanger::showDialogsOnError() const
1072 {
1073  return d->mShowDialogsOnError;
1074 }
1075 
1076 void IncidenceChanger::setRespectsCollectionRights( bool respects )
1077 {
1078  d->mRespectsCollectionRights = respects;
1079 }
1080 
1081 bool IncidenceChanger::respectsCollectionRights() const
1082 {
1083  return d->mRespectsCollectionRights;
1084 }
1085 
1086 void IncidenceChanger::setDestinationPolicy( IncidenceChanger::DestinationPolicy destinationPolicy )
1087 {
1088  d->mDestinationPolicy = destinationPolicy;
1089 }
1090 
1091 IncidenceChanger::DestinationPolicy IncidenceChanger::destinationPolicy() const
1092 {
1093  return d->mDestinationPolicy;
1094 }
1095 
1096 void IncidenceChanger::setDefaultCollection( const Akonadi::Collection &collection )
1097 {
1098  d->mDefaultCollection = collection;
1099 }
1100 
1101 Collection IncidenceChanger::defaultCollection() const
1102 {
1103  return d->mDefaultCollection;
1104 }
1105 
1106 bool IncidenceChanger::historyEnabled() const
1107 {
1108  return d->mUseHistory;
1109 }
1110 
1111 void IncidenceChanger::setHistoryEnabled( bool enable )
1112 {
1113  if ( d->mUseHistory != enable ) {
1114  d->mUseHistory = enable;
1115  if ( enable && !d->mHistory )
1116  d->mHistory = new History( this );
1117  }
1118 }
1119 
1120 History* IncidenceChanger::history() const
1121 {
1122  return d->mHistory;
1123 }
1124 
1125 bool IncidenceChanger::deletedRecently( Akonadi::Item::Id id ) const
1126 {
1127  return d->deleteAlreadyCalled( id );
1128 }
1129 
1130 void IncidenceChanger::setGroupwareCommunication( bool enabled )
1131 {
1132  d->mGroupwareCommunication = enabled;
1133 }
1134 
1135 bool IncidenceChanger::groupwareCommunication() const
1136 {
1137  return d->mGroupwareCommunication;
1138 }
1139 
1140 Akonadi::Collection IncidenceChanger::lastCollectionUsed() const
1141 {
1142  return d->mLastCollectionUsed;
1143 }
1144 
1145 QString IncidenceChanger::Private::showErrorDialog( IncidenceChanger::ResultCode resultCode,
1146  QWidget *parent )
1147 {
1148  QString errorString;
1149  switch( resultCode ) {
1150  case IncidenceChanger::ResultCodePermissions:
1151  errorString = i18n( "Operation can not be performed due to ACL restrictions" );
1152  break;
1153  case IncidenceChanger::ResultCodeInvalidUserCollection:
1154  errorString = i18n( "The chosen collection is invalid" );
1155  break;
1156  case IncidenceChanger::ResultCodeInvalidDefaultCollection:
1157  errorString = i18n( "Default collection is invalid or doesn't have proper ACLs"
1158  " and DestinationPolicyNeverAsk was used" );
1159  break;
1160  case IncidenceChanger::ResultCodeDuplicateId:
1161  errorString = i18n( "Duplicate item id in a group operation");
1162  break;
1163  case IncidenceChanger::ResultCodeRolledback:
1164  errorString = i18n( "One change belonging to a group of changes failed. "
1165  "All changes are being rolled back." );
1166  break;
1167  default:
1168  Q_ASSERT( false );
1169  return QString( i18n( "Unknown error" ) );
1170  }
1171 
1172  if ( mShowDialogsOnError ) {
1173  KMessageBox::sorry( parent, errorString );
1174  }
1175 
1176  return errorString;
1177 }
1178 
1179 void IncidenceChanger::Private::cancelTransaction()
1180 {
1181  if ( mBatchOperationInProgress ) {
1182  mAtomicOperations[mLatestAtomicOperationId]->setRolledback();
1183  }
1184 }
1185 
1186 void IncidenceChanger::Private::cleanupTransaction()
1187 {
1188  Q_ASSERT( mAtomicOperations.contains(mLatestAtomicOperationId) );
1189  AtomicOperation *operation = mAtomicOperations[mLatestAtomicOperationId];
1190  Q_ASSERT( operation );
1191  Q_ASSERT( operation->rolledback() );
1192  if ( !operation->pendingJobs() && operation->m_endCalled && operation->m_transactionCompleted ) {
1193  delete mAtomicOperations.take(mLatestAtomicOperationId);
1194  mBatchOperationInProgress = false;
1195  }
1196 }
1197 
1198 bool IncidenceChanger::Private::allowAtomicOperation( int atomicOperationId,
1199  const Change::Ptr &change ) const
1200 {
1201  bool allow = true;
1202  if ( atomicOperationId > 0 ) {
1203  Q_ASSERT( mAtomicOperations.contains( atomicOperationId ) );
1204  AtomicOperation *operation = mAtomicOperations.value( atomicOperationId );
1205 
1206  if ( change->type == ChangeTypeCreate ) {
1207  allow = true;
1208  } else if ( change->type == ChangeTypeModify ) {
1209  allow = !operation->m_itemIdsInOperation.contains( change->newItem.id() );
1210  } else if ( change->type == ChangeTypeDelete ) {
1211  DeletionChange::Ptr deletion = change.staticCast<DeletionChange>();
1212  foreach( Akonadi::Item::Id id, deletion->mItemIds ) {
1213  if ( operation->m_itemIdsInOperation.contains( id ) ) {
1214  allow = false;
1215  break;
1216  }
1217  }
1218  }
1219  }
1220 
1221  if ( !allow ) {
1222  kWarning() << "Each change belonging to a group operation"
1223  << "must have a different Akonadi::Item::Id";
1224  }
1225 
1226  return allow;
1227 }
1228 
1230 void ModificationChange::emitCompletionSignal()
1231 {
1232  emitModifyFinished( changer, id, newItem, resultCode, errorString );
1233 }
1234 
1236 void CreationChange::emitCompletionSignal()
1237 {
1238  // Does a queued emit, with QMetaObject::invokeMethod
1239  emitCreateFinished( changer, id, newItem, resultCode, errorString );
1240 }
1241 
1243 void DeletionChange::emitCompletionSignal()
1244 {
1245  emitDeleteFinished( changer, id, mItemIds, resultCode, errorString );
1246 }
1247 
1280 AtomicOperation::AtomicOperation( IncidenceChanger::Private *icp,
1281  uint ident ) : m_id ( ident )
1282  , m_endCalled( false )
1283  , m_numCompletedChanges( 0 )
1284  , m_transactionCompleted( false )
1285  , m_wasRolledback( false )
1286  , m_transaction( 0 )
1287  , m_incidenceChangerPrivate( icp )
1288 
1289 {
1290  Q_ASSERT( m_id != 0 );
1291 }
1292 
1293 Akonadi::TransactionSequence *AtomicOperation::transaction()
1294 {
1295  if ( !m_transaction ) {
1296  m_transaction = new Akonadi::TransactionSequence;
1297  m_transaction->setAutomaticCommittingEnabled( true );
1298 
1299  m_incidenceChangerPrivate->mAtomicOperationByTransaction.insert( m_transaction, m_id );
1300 
1301  QObject::connect( m_transaction, SIGNAL(result(KJob*)),
1302  m_incidenceChangerPrivate, SLOT(handleTransactionJobResult(KJob*)) );
1303  }
1304 
1305  return m_transaction;
1306 }
Akonadi::ItemCreateJob::item
Item item() const
Returns the created item with the new unique id, or an invalid item if the job failed.
Definition: itemcreatejob.cpp:155
Akonadi::CollectionDialog
A collection selection dialog.
Definition: collectiondialog.h:67
Akonadi::Collection
Represents a collection of PIM items.
Definition: collection.h:75
Akonadi::ItemModifyJob::item
Item item() const
Returns the modified and stored item including the changed revision number.
Definition: itemmodifyjob.cpp:366
Akonadi::Job
Base class for all actions in the Akonadi storage.
Definition: job.h:86
Akonadi::Collection::CanChangeItem
Can change items in this collection.
Definition: collection.h:88
Akonadi::TransactionSequence::setAutomaticCommittingEnabled
void setAutomaticCommittingEnabled(bool enable)
Disable automatic committing.
Definition: transactionsequence.cpp:197
Akonadi::ItemDeleteJob
Job that deletes items from the Akonadi storage.
Definition: itemdeletejob.h:62
Akonadi::Collection::CanDeleteItem
Can delete items in this collection.
Definition: collection.h:90
Akonadi::History
History class for implementing undo/redo of calendar operations.
Definition: history.h:56
Akonadi::Collection::CanCreateItem
Can create new items in this collection.
Definition: collection.h:89
Akonadi::ITIPHandlerHelper::ResultSuccess
The invitation was sent to all attendees.
Definition: itiphandlerhelper_p.h:82
Akonadi::ItemCreateJob
Job that creates a new item in the Akonadi storage.
Definition: itemcreatejob.h:73
Akonadi::Entity::id
Id id() const
Returns the unique identifier of the entity.
Definition: entity.cpp:72
Akonadi::Collection::rights
Rights rights() const
Returns the rights the user has on the collection.
Definition: collection.cpp:99
Akonadi::ITIPHandlerHelper
This class handles sending of invitations to attendees when Incidences (e.g.
Definition: itiphandlerhelper_p.h:67
Akonadi::ITIPHandlerHelper::ResultCanceled
Sending was canceled by the user, meaning there are local changes of which other attendees are not aw...
Definition: itiphandlerhelper_p.h:75
Akonadi::ITIPHandlerHelper::SendResult
SendResult
Definition: itiphandlerhelper_p.h:74
Akonadi::TransactionSequence
Base class for jobs that need to run a sequence of sub-jobs in a transaction.
Definition: transactionsequence.h:69
Akonadi::ItemModifyJob
Job that modifies an existing item in the Akonadi storage.
Definition: itemmodifyjob.h:97
Akonadi::Job::errorString
virtual QString errorString() const
Returns the error string, if there has been an error, an empty string otherwise.
Definition: job.cpp:291
Akonadi::Entity::isValid
bool isValid() const
Returns whether the entity is valid.
Definition: entity.cpp:97
Akonadi::ItemDeleteJob::deletedItems
Item::List deletedItems() const
Returns the items passed on in the constructor.
Definition: itemdeletejob.cpp:89
This file is part of the KDE documentation.
Documentation copyright © 1996-2013 The KDE developers.
Generated on Tue Nov 26 2013 09:03:17 by doxygen 1.8.5 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.

akonadi

Skip menu "akonadi"
  • Main Page
  • Namespace List
  • Namespace Members
  • Alphabetical List
  • Class List
  • Class Hierarchy
  • Class Members
  • File List
  • Modules
  • Related Pages

kdepimlibs-4.11.3 API Reference

Skip menu "kdepimlibs-4.11.3 API Reference"
  • akonadi
  •   contact
  •   kmime
  •   socialutils
  • kabc
  • kalarmcal
  • kblog
  • kcal
  • kcalcore
  • kcalutils
  • kholidays
  • kimap
  • kioslave
  •   imap4
  •   mbox
  •   nntp
  • kldap
  • kmbox
  • kmime
  • kontactinterface
  • kpimidentities
  • kpimtextedit
  • kpimutils
  • kresources
  • ktnef
  • kxmlrpcclient
  • mailtransport
  • microblog
  • qgpgme
  • syndication
  •   atom
  •   rdf
  •   rss2
Report problems with this website to our bug tracking system.
Contact the specific authors with questions and comments about the page contents.

KDE® and the K Desktop Environment® logo are registered trademarks of KDE e.V. | Legal