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

akonadi

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

Skip menu "kdepimlibs API Reference"
  • akonadi
  •   contact
  •   kmime
  •   socialutils
  • kabc
  • kalarmcal
  • kblog
  • kcal
  • kcalcore
  • kcalutils
  • kholidays
  • kimap
  • kldap
  • kmbox
  • kmime
  • kpimidentities
  • kpimtextedit
  • kresources
  • ktnef
  • kxmlrpcclient
  • microblog

Search



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