• Skip to content
  • Skip to link menu
  • KDE API Reference
  • kdepimlibs-4.11.5 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 }
This file is part of the KDE documentation.
Documentation copyright © 1996-2014 The KDE developers.
Generated on Fri Jan 3 2014 22:27:35 by doxygen 1.8.3.1 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.5 API Reference

Skip menu "kdepimlibs-4.11.5 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