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

akonadi

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

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