7#include "incidencechanger.h"
8#include "akonadicalendar_debug.h"
9#include "calendarutils.h"
10#include "incidencechanger_p.h"
11#include "mailscheduler_p.h"
13#include <Akonadi/ItemCreateJob>
14#include <Akonadi/ItemDeleteJob>
15#include <Akonadi/ItemModifyJob>
16#include <Akonadi/TransactionSequence>
20#include <KLocalizedString>
28AKONADI_CALENDAR_TESTS_EXPORT
bool akonadi_calendar_running_unittests =
false;
41 case ITIPHandlerHelper::ResultCanceled:
43 case ITIPHandlerHelper::ResultSuccess:
53 return Akonadi::CalendarUtils::thatIsMe(email);
56static bool allowedModificationsWithoutRevisionUpdate(
const Incidence::Ptr &incidence)
63 return dirtyFields == alarmOnlyModify;
68 ITIPHandlerHelper::MessagePrivacyFlags helperFlags;
69 helperFlags.setFlag(ITIPHandlerHelper::MessagePrivacySign, (flags & IncidenceChanger::InvitationPrivacySign) == IncidenceChanger::InvitationPrivacySign);
70 helperFlags.setFlag(ITIPHandlerHelper::MessagePrivacyEncrypt,
71 (flags & IncidenceChanger::InvitationPrivacyEncrypt) == IncidenceChanger::InvitationPrivacyEncrypt);
72 helper->setMessagePrivacy(helperFlags);
78static void emitCreateFinished(IncidenceChanger *changer,
81 Akonadi::IncidenceChanger::ResultCode resultCode,
89 Q_ARG(Akonadi::IncidenceChanger::ResultCode, resultCode),
95emitModifyFinished(IncidenceChanger *changer,
int changeId,
const Akonadi::Item &item, IncidenceChanger::ResultCode resultCode,
const QString &errorString)
100 Q_ARG(
int, changeId),
102 Q_ARG(Akonadi::IncidenceChanger::ResultCode, resultCode),
107static void emitDeleteFinished(IncidenceChanger *changer,
110 IncidenceChanger::ResultCode resultCode,
116 Q_ARG(
int, changeId),
118 Q_ARG(Akonadi::IncidenceChanger::ResultCode, resultCode),
130 mShowDialogsOnError =
true;
132 mHistory = enableHistory ?
new History(
this) : nullptr;
133 mUseHistory = enableHistory;
134 mDestinationPolicy = IncidenceChanger::DestinationPolicyDefault;
135 mRespectsCollectionRights =
false;
136 mGroupwareCommunication =
false;
137 mLatestAtomicOperationId = 0;
138 mBatchOperationInProgress =
false;
139 mAutoAdjustRecurrence =
true;
140 m_collectionFetchJob =
nullptr;
141 m_invitationPolicy = IncidenceChanger::InvitationPolicyAsk;
143 qRegisterMetaType<QList<Akonadi::Item::Id>>(
"QList<Akonadi::Item::Id>");
144 qRegisterMetaType<Akonadi::Item::Id>(
"Akonadi::Item::Id");
145 qRegisterMetaType<Akonadi::Item>(
"Akonadi::Item");
146 qRegisterMetaType<Akonadi::IncidenceChanger::ResultCode>(
"Akonadi::IncidenceChanger::ResultCode");
147 qRegisterMetaType<ITIPHandlerHelper::SendResult>(
"ITIPHandlerHelper::SendResult");
150IncidenceChangerPrivate::~IncidenceChangerPrivate()
152 if (!mAtomicOperations.isEmpty() || !mQueuedModifications.isEmpty() || !mModificationsInProgress.isEmpty()) {
153 qCDebug(AKONADICALENDAR_LOG) <<
"Normal if the application was being used. "
154 "But might indicate a memory leak if it wasn't";
158bool IncidenceChangerPrivate::atomicOperationIsValid(uint atomicOperationId)
const
161 return mAtomicOperations.contains(atomicOperationId) && !mAtomicOperations[atomicOperationId]->m_endCalled;
164bool IncidenceChangerPrivate::hasRights(
const Collection &collection, IncidenceChanger::ChangeType changeType)
const
167 switch (changeType) {
168 case IncidenceChanger::ChangeTypeCreate:
171 case IncidenceChanger::ChangeTypeModify:
174 case IncidenceChanger::ChangeTypeDelete:
178 Q_ASSERT_X(
false,
"hasRights",
"invalid type");
181 return !collection.
isValid() || !mRespectsCollectionRights || result;
184Akonadi::Job *IncidenceChangerPrivate::parentJob(
const Change::Ptr &change)
const
186 return (mBatchOperationInProgress && !change->queuedModification) ? mAtomicOperations[mLatestAtomicOperationId]->transaction() :
nullptr;
189void IncidenceChangerPrivate::queueModification(
const Change::Ptr &change)
195 if (mQueuedModifications.contains(
id)) {
196 Change::Ptr toBeDiscarded = mQueuedModifications.take(
id);
197 toBeDiscarded->resultCode = IncidenceChanger::ResultCodeModificationDiscarded;
198 toBeDiscarded->completed =
true;
199 mChangeById.remove(toBeDiscarded->id);
202 change->queuedModification =
true;
203 mQueuedModifications[id] = change;
208 mModificationsInProgress.remove(
id);
210 if (mQueuedModifications.contains(
id)) {
211 const Change::Ptr change = mQueuedModifications.take(
id);
212 performModification(change);
216void IncidenceChangerPrivate::handleTransactionJobResult(
KJob *job)
218 auto transaction = qobject_cast<TransactionSequence *>(job);
219 Q_ASSERT(transaction);
220 Q_ASSERT(mAtomicOperationByTransaction.contains(transaction));
222 const uint atomicOperationId = mAtomicOperationByTransaction.take(transaction);
224 Q_ASSERT(mAtomicOperations.contains(atomicOperationId));
225 AtomicOperation *operation = mAtomicOperations[atomicOperationId];
227 Q_ASSERT(operation->m_id == atomicOperationId);
229 if (!operation->rolledback()) {
230 operation->setRolledback();
232 qCritical() <<
"Transaction failed, everything was rolledback. " << job->
errorString();
234 Q_ASSERT(operation->m_endCalled);
235 Q_ASSERT(!operation->pendingJobs());
238 if (!operation->pendingJobs() && operation->m_endCalled) {
239 delete mAtomicOperations.take(atomicOperationId);
240 mBatchOperationInProgress =
false;
242 operation->m_transactionCompleted =
true;
246void IncidenceChangerPrivate::handleCreateJobResult(
KJob *job)
248 Change::Ptr change = mChangeForJob.take(job);
250 const auto j = qobject_cast<const ItemCreateJob *>(job);
255 const QString errorString = j->errorString();
256 IncidenceChanger::ResultCode resultCode = IncidenceChanger::ResultCodeJobError;
257 item = change->newItem;
258 qCritical() << errorString;
259 if (mShowDialogsOnError) {
260 KMessageBox::error(change->parentWidget,
i18n(
"Error while trying to create calendar item. Error was: %1", errorString));
262 mChangeById.
remove(change->id);
263 change->errorString = errorString;
264 change->resultCode = resultCode;
269 change->newItem = item;
271 if (change->useGroupwareCommunication) {
272 connect(change.data(), &Change::dialogClosedAfterChange,
this, &IncidenceChangerPrivate::handleCreateJobResult2);
273 handleInvitationsAfterChange(change);
275 handleCreateJobResult2(change->id, ITIPHandlerHelper::ResultSuccess);
280void IncidenceChangerPrivate::handleCreateJobResult2(
int changeId, ITIPHandlerHelper::SendResult
status)
282 Change::Ptr change = mChangeById[changeId];
285 mChangeById.remove(changeId);
287 if (
status == ITIPHandlerHelper::ResultFailAbortUpdate) {
288 qCritical() <<
"Sending invitations failed, but did not delete the incidence";
291 const uint atomicOperationId = change->atomicOperationId;
292 if (atomicOperationId != 0) {
293 mInvitationStatusByAtomicOperation.insert(atomicOperationId,
status);
297 if (change->atomicOperationId != 0) {
298 AtomicOperation *a = mAtomicOperations[change->atomicOperationId];
299 ++a->m_numCompletedChanges;
300 change->completed =
true;
301 description = a->m_description;
305 if (change->recordToHistory) {
306 mHistory->recordCreation(item, description, change->atomicOperationId);
309 change->errorString =
QString();
310 change->resultCode = IncidenceChanger::ResultCodeSuccess;
314void IncidenceChangerPrivate::handleDeleteJobResult(
KJob *job)
316 Change::Ptr change = mChangeForJob.take(job);
318 const auto j = qobject_cast<const ItemDeleteJob *>(job);
323 deletionChange->mItemIds.reserve(deletionChange->mItemIds.count() + items.
count());
325 deletionChange->mItemIds.append(item.
id());
328 if (change->atomicOperationId != 0) {
329 AtomicOperation *a = mAtomicOperations[change->atomicOperationId];
330 a->m_numCompletedChanges++;
331 change->completed =
true;
332 description = a->m_description;
335 const QString errorString = j->errorString();
336 qCritical() << errorString;
338 if (mShowDialogsOnError) {
339 KMessageBox::error(change->parentWidget,
i18n(
"Error while trying to delete calendar item. Error was: %1", errorString));
342 for (
const Item &item : items) {
344 mDeletedItemIds.remove(mDeletedItemIds.indexOf(item.
id()));
346 mChangeById.
remove(change->id);
347 change->resultCode = IncidenceChanger::ResultCodeJobError;
348 change->errorString = errorString;
349 change->emitCompletionSignal();
351 if (change->recordToHistory) {
353 mHistory->recordDeletions(items, description, change->atomicOperationId);
356 if (change->useGroupwareCommunication) {
357 connect(change.data(), &Change::dialogClosedAfterChange,
this, &IncidenceChangerPrivate::handleDeleteJobResult2);
358 handleInvitationsAfterChange(change);
360 handleDeleteJobResult2(change->id, ITIPHandlerHelper::ResultSuccess);
365void IncidenceChangerPrivate::handleDeleteJobResult2(
int changeId, ITIPHandlerHelper::SendResult
status)
367 Change::Ptr change = mChangeById[changeId];
368 mChangeById.
remove(change->id);
370 if (
status == ITIPHandlerHelper::ResultSuccess) {
371 change->errorString =
QString();
372 change->resultCode = IncidenceChanger::ResultCodeSuccess;
374 change->errorString =
i18nc(
"errormessage for a job ended with an unexpected result",
"An unknown error occurred");
375 change->resultCode = IncidenceChanger::ResultCodeJobError;
381void IncidenceChangerPrivate::handleModifyJobResult(
KJob *job)
383 Change::Ptr change = mChangeForJob.take(job);
385 const auto j = qobject_cast<const ItemModifyJob *>(job);
386 const Item item = j->item();
387 Q_ASSERT(mDirtyFieldsByJob.contains(job));
392 if (change->atomicOperationId != 0) {
393 AtomicOperation *a = mAtomicOperations[change->atomicOperationId];
394 a->m_numCompletedChanges++;
395 change->completed =
true;
396 description = a->m_description;
399 const QString errorString = j->errorString();
400 IncidenceChanger::ResultCode resultCode = IncidenceChanger::ResultCodeJobError;
401 if (deleteAlreadyCalled(item.id())) {
405 resultCode = IncidenceChanger::ResultCodeAlreadyDeleted;
406 qCWarning(AKONADICALENDAR_LOG) <<
"Trying to change item " << item.id() <<
" while deletion is in progress.";
408 qCritical() << errorString;
410 if (mShowDialogsOnError) {
411 KMessageBox::error(change->parentWidget,
i18n(
"Error while trying to modify calendar item. Error was: %1", errorString));
413 mChangeById.
remove(change->id);
414 change->errorString = errorString;
415 change->resultCode = resultCode;
420 (*(s_latestRevisionByItemId()))[item.id()] = item.revision();
421 change->newItem = item;
422 if (change->recordToHistory && !change->originalItems.isEmpty()) {
423 Q_ASSERT(change->originalItems.count() == 1);
424 mHistory->recordModification(change->originalItems.constFirst(), item, description, change->atomicOperationId);
427 if (change->useGroupwareCommunication) {
428 connect(change.data(), &Change::dialogClosedAfterChange,
this, &IncidenceChangerPrivate::handleModifyJobResult2);
429 handleInvitationsAfterChange(change);
431 handleModifyJobResult2(change->id, ITIPHandlerHelper::ResultSuccess);
436void IncidenceChangerPrivate::handleModifyJobResult2(
int changeId, ITIPHandlerHelper::SendResult
status)
438 Change::Ptr change = mChangeById[changeId];
440 mChangeById.
remove(changeId);
441 if (change->atomicOperationId != 0) {
442 mInvitationStatusByAtomicOperation.insert(change->atomicOperationId,
status);
444 change->errorString =
QString();
445 change->resultCode = IncidenceChanger::ResultCodeSuccess;
453 return mDeletedItemIds.contains(
id);
456void IncidenceChangerPrivate::handleInvitationsBeforeChange(
const Change::Ptr &change)
458 if (mGroupwareCommunication) {
459 ITIPHandlerHelper::SendResult result = ITIPHandlerHelper::ResultSuccess;
460 switch (change->type) {
461 case IncidenceChanger::ChangeTypeCreate:
464 case IncidenceChanger::ChangeTypeDelete: {
465 ITIPHandlerHelper::SendResult
status;
467 Q_ASSERT(!change->originalItems.isEmpty());
469 auto handler =
new ITIPHandlerHelper(mFactory, change->parentWidget);
470 handler->setParent(
this);
471 updateHandlerPrivacyPolicy(handler, m_invitationPrivacy);
473 if (m_invitationPolicy == IncidenceChanger::InvitationPolicySend) {
475 }
else if (m_invitationPolicy == IncidenceChanger::InvitationPolicyDontSend) {
477 }
else if (mInvitationStatusByAtomicOperation.contains(change->atomicOperationId)) {
478 handler->setDefaultAction(actionFromStatus(mInvitationStatusByAtomicOperation.value(change->atomicOperationId)));
481 connect(handler, &ITIPHandlerHelper::finished, change.data(), &Change::emitUserDialogClosedBeforeChange);
483 for (
const Akonadi::Item &item : std::as_const(change->originalItems)) {
486 if (!
incidence->supportsGroupwareCommunication()) {
491 if (Akonadi::CalendarUtils::thatIsMe(
incidence->organizer().email())) {
495 if (change->atomicOperationId) {
496 mInvitationStatusByAtomicOperation.insert(change->atomicOperationId,
status);
503 change->emitUserDialogClosedBeforeChange(result);
507 case IncidenceChanger::ChangeTypeModify: {
508 if (change->originalItems.isEmpty()) {
512 Q_ASSERT(change->originalItems.count() == 1);
516 if (!oldIncidence->supportsGroupwareCommunication()) {
520 if (allowedModificationsWithoutRevisionUpdate(newIncidence)) {
521 change->emitUserDialogClosedBeforeChange(ITIPHandlerHelper::ResultSuccess);
525 if (akonadi_calendar_running_unittests && !weAreOrganizer(newIncidence)) {
529 if (m_invitationPolicy == IncidenceChanger::InvitationPolicySend) {
530 change->emitUserDialogClosedBeforeChange(ITIPHandlerHelper::ResultSuccess);
532 }
else if (m_invitationPolicy == IncidenceChanger::InvitationPolicyDontSend) {
533 change->emitUserDialogClosedBeforeChange(ITIPHandlerHelper::ResultCanceled);
538 ITIPHandlerHelper handler(mFactory, change->parentWidget);
539 const bool modify = handler.handleIncidenceAboutToBeModified(newIncidence);
543 result = ITIPHandlerHelper::ResultCanceled;
546 if (newIncidence->type() == oldIncidence->type()) {
555 result = ITIPHandlerHelper::ResultCanceled;
557 change->emitUserDialogClosedBeforeChange(result);
559 change->emitUserDialogClosedBeforeChange(ITIPHandlerHelper::ResultSuccess);
563void IncidenceChangerPrivate::handleInvitationsAfterChange(
const Change::Ptr &change)
565 if (change->useGroupwareCommunication) {
566 auto handler =
new ITIPHandlerHelper(mFactory, change->parentWidget);
567 connect(handler, &ITIPHandlerHelper::finished, change.data(), &Change::emitUserDialogClosedAfterChange);
568 handler->setParent(
this);
569 updateHandlerPrivacyPolicy(handler, m_invitationPrivacy);
571 const bool alwaysSend = (m_invitationPolicy == IncidenceChanger::InvitationPolicySend);
572 const bool neverSend = (m_invitationPolicy == IncidenceChanger::InvitationPolicyDontSend);
581 switch (change->type) {
582 case IncidenceChanger::ChangeTypeCreate: {
584 if (
incidence->supportsGroupwareCommunication()) {
590 case IncidenceChanger::ChangeTypeDelete:
591 Q_ASSERT(!change->originalItems.isEmpty());
592 for (
const Akonadi::Item &item : std::as_const(change->originalItems)) {
593 Q_ASSERT(item.hasPayload());
596 if (!
incidence->supportsGroupwareCommunication()) {
600 if (!Akonadi::CalendarUtils::thatIsMe(
incidence->organizer().email())) {
601 const QStringList myEmails = Akonadi::CalendarUtils::allEmails();
602 bool notifyOrganizer =
false;
606 notifyOrganizer =
true;
615 if (notifyOrganizer) {
616 MailScheduler scheduler(mFactory, change->parentWidget);
622 case IncidenceChanger::ChangeTypeModify: {
623 if (change->originalItems.isEmpty()) {
627 Q_ASSERT(change->originalItems.count() == 1);
631 if (!newIncidence->supportsGroupwareCommunication() || !Akonadi::CalendarUtils::thatIsMe(newIncidence->organizer().email())) {
636 if (allowedModificationsWithoutRevisionUpdate(newIncidence)) {
640 if (!neverSend && !alwaysSend && mInvitationStatusByAtomicOperation.contains(change->atomicOperationId)) {
641 handler->setDefaultAction(actionFromStatus(mInvitationStatusByAtomicOperation.value(change->atomicOperationId)));
644 const bool attendeeStatusChanged = myAttendeeStatusChanged(newIncidence, oldIncidence, Akonadi::CalendarUtils::allEmails());
650 handler->deleteLater();
653 change->emitUserDialogClosedAfterChange(ITIPHandlerHelper::ResultCanceled);
656 handler->deleteLater();
658 change->emitUserDialogClosedAfterChange(ITIPHandlerHelper::ResultSuccess);
660 change->emitUserDialogClosedAfterChange(ITIPHandlerHelper::ResultSuccess);
669 const Attendee oldMe = oldInc->attendeeByMails(myEmails);
670 const Attendee newMe = newInc->attendeeByMails(myEmails);
675IncidenceChanger::IncidenceChanger(
QObject *parent)
677 , d(new IncidenceChangerPrivate(true, nullptr, this))
683 , d(new IncidenceChangerPrivate(true, factory, this))
687IncidenceChanger::IncidenceChanger(
bool enableHistory,
QObject *parent)
689 , d(new IncidenceChangerPrivate(enableHistory, nullptr, this))
693IncidenceChanger::~IncidenceChanger() =
default;
697 const uint atomicOperationId = d->mBatchOperationInProgress ? d->mLatestAtomicOperationId : 0;
699 const Change::Ptr change(
new CreationChange(
this, ++d->mLatestChangeId, atomicOperationId,
parent));
700 const int changeId = change->id;
701 Q_ASSERT(!(d->mBatchOperationInProgress && !d->mAtomicOperations.contains(atomicOperationId)));
702 if (d->mBatchOperationInProgress && d->mAtomicOperations[atomicOperationId]->rolledback()) {
706 change->resultCode = ResultCodeRolledback;
708 d->cleanupTransaction();
712 change->newItem = item;
714 d->step1DetermineDestinationCollection(change, collection);
722 qCWarning(AKONADICALENDAR_LOG) <<
"An invalid payload is not allowed.";
723 d->cancelTransaction();
731 return createFromItem(item, collection,
parent);
734int IncidenceChanger::deleteIncidence(
const Item &item,
QWidget *parent)
739 return deleteIncidences(list,
parent);
745 qCritical() <<
"Delete what?";
746 d->cancelTransaction();
750 for (
const Item &item : items) {
751 if (!item.isValid()) {
752 qCritical() <<
"Items must be valid!";
753 d->cancelTransaction();
758 const uint atomicOperationId = d->mBatchOperationInProgress ? d->mLatestAtomicOperationId : 0;
759 const int changeId = ++d->mLatestChangeId;
760 const Change::Ptr change(
new DeletionChange(
this, changeId, atomicOperationId,
parent));
762 for (
const Item &item : items) {
763 if (!d->hasRights(item.parentCollection(), ChangeTypeDelete)) {
764 qCWarning(AKONADICALENDAR_LOG) <<
"Item " << item.id() <<
" can't be deleted due to ACL restrictions";
765 const QString errorString = d->showErrorDialog(ResultCodePermissions,
parent);
766 change->resultCode = ResultCodePermissions;
767 change->errorString = errorString;
768 d->cancelTransaction();
773 if (!d->allowAtomicOperation(atomicOperationId, change)) {
774 const QString errorString = d->showErrorDialog(ResultCodeDuplicateId,
parent);
775 change->resultCode = ResultCodeDuplicateId;
776 change->errorString = errorString;
777 qCWarning(AKONADICALENDAR_LOG) << errorString;
778 d->cancelTransaction();
783 for (
const Item &item : items) {
784 if (d->deleteAlreadyCalled(item.id())) {
786 qCDebug(AKONADICALENDAR_LOG) <<
"Item " << item.id() <<
" already deleted or being deleted, skipping";
788 itemsToDelete.
append(item);
792 if (d->mBatchOperationInProgress && d->mAtomicOperations[atomicOperationId]->rolledback()) {
794 change->resultCode = ResultCodeRolledback;
797 d->cleanupTransaction();
804 qCDebug(AKONADICALENDAR_LOG) <<
"Items already deleted or being deleted, skipping";
807 change->resultCode = ResultCodeAlreadyDeleted;
809 d->cancelTransaction();
813 change->originalItems = itemsToDelete;
815 d->mChangeById.
insert(changeId, change);
817 if (d->mGroupwareCommunication) {
818 connect(change.data(), &Change::dialogClosedBeforeChange, d.get(), &IncidenceChangerPrivate::deleteIncidences2);
819 d->handleInvitationsBeforeChange(change);
821 d->deleteIncidences2(changeId, ITIPHandlerHelper::ResultSuccess);
826void IncidenceChangerPrivate::deleteIncidences2(
int changeId, ITIPHandlerHelper::SendResult
status)
829 Change::Ptr change = mChangeById[changeId];
830 const uint atomicOperationId = change->atomicOperationId;
831 auto deleteJob =
new ItemDeleteJob(change->originalItems, parentJob(change));
832 mChangeForJob.insert(deleteJob, change);
834 if (mBatchOperationInProgress) {
835 AtomicOperation *atomic = mAtomicOperations[atomicOperationId];
837 atomic->addChange(change);
840 mDeletedItemIds.reserve(mDeletedItemIds.count() + change->originalItems.count());
841 for (
const Item &item : std::as_const(change->originalItems)) {
842 mDeletedItemIds << item.id();
846 if (mDeletedItemIds.count() > 100) {
847 mDeletedItemIds.remove(0, 50);
856 if (!changedItem.isValid() || !changedItem.hasPayload<
Incidence::Ptr>()) {
857 qCWarning(AKONADICALENDAR_LOG) <<
"An invalid item or payload is not allowed.";
858 d->cancelTransaction();
862 if (!d->hasRights(changedItem.parentCollection(), ChangeTypeModify)) {
863 qCWarning(AKONADICALENDAR_LOG) <<
"Item " << changedItem.id() <<
" can't be deleted due to ACL restrictions";
864 const int changeId = ++d->mLatestChangeId;
865 const QString errorString = d->showErrorDialog(ResultCodePermissions,
parent);
866 emitModifyFinished(
this, changeId, changedItem, ResultCodePermissions, errorString);
867 d->cancelTransaction();
874 const uint atomicOperationId = d->mBatchOperationInProgress ? d->mLatestAtomicOperationId : 0;
875 const int changeId = ++d->mLatestChangeId;
876 auto modificationChange =
new ModificationChange(
this, changeId, atomicOperationId,
parent);
877 Change::Ptr change(modificationChange);
879 if (originalPayload) {
880 Item originalItem(changedItem);
882 modificationChange->originalItems << originalItem;
885 modificationChange->newItem = changedItem;
886 d->mChangeById.insert(changeId, change);
888 if (!d->allowAtomicOperation(atomicOperationId, change)) {
889 const QString errorString = d->showErrorDialog(ResultCodeDuplicateId,
parent);
891 change->resultCode = ResultCodeDuplicateId;
892 change->errorString = errorString;
893 d->cancelTransaction();
894 qCWarning(AKONADICALENDAR_LOG) <<
"Atomic operation now allowed";
898 if (d->mBatchOperationInProgress && d->mAtomicOperations[atomicOperationId]->rolledback()) {
901 d->cleanupTransaction();
902 emitModifyFinished(
this, changeId, changedItem, ResultCodeRolledback, errorMessage);
905 d->performModification(change);
911void IncidenceChangerPrivate::performModification(
const Change::Ptr &change)
913 const Item::Id id = change->newItem.id();
918 const int changeId = change->id;
920 if (deleteAlreadyCalled(
id)) {
922 qCDebug(AKONADICALENDAR_LOG) <<
"Item " <<
id <<
" already deleted or being deleted, skipping";
925 emitModifyFinished(q,
928 IncidenceChanger::ResultCodeAlreadyDeleted,
929 i18n(
"That calendar item was already deleted, or currently being deleted."));
933 const uint atomicOperationId = change->atomicOperationId;
934 const bool hasAtomicOperationId = atomicOperationId != 0;
935 if (hasAtomicOperationId && mAtomicOperations[atomicOperationId]->rolledback()) {
936 const QString errorMessage = showErrorDialog(IncidenceChanger::ResultCodeRolledback,
nullptr);
938 emitModifyFinished(q, changeId, newItem, IncidenceChanger::ResultCodeRolledback, errorMessage);
941 if (mGroupwareCommunication) {
942 connect(change.data(), &Change::dialogClosedBeforeChange,
this, &IncidenceChangerPrivate::performModification2);
943 handleInvitationsBeforeChange(change);
945 performModification2(change->id, ITIPHandlerHelper::ResultSuccess);
949void IncidenceChangerPrivate::performModification2(
int changeId, ITIPHandlerHelper::SendResult
status)
951 Change::Ptr change = mChangeById[changeId];
952 const Item::Id id = change->newItem.id();
956 if (
status == ITIPHandlerHelper::ResultCanceled) {
958 qCDebug(AKONADICALENDAR_LOG) <<
"User cancelled, giving up";
959 emitModifyFinished(q, change->id, newItem, IncidenceChanger::ResultCodeUserCanceled,
QString());
963 const uint atomicOperationId = change->atomicOperationId;
964 const bool hasAtomicOperationId = atomicOperationId != 0;
967 if (latestRevisionByItemId.
contains(
id) && latestRevisionByItemId[
id] > newItem.
revision()) {
980 if (!allowedModificationsWithoutRevisionUpdate(incidence)) {
981 const int revision =
incidence->revision();
989 if (!(
incidence->dirtyFields() & resetPartStatus).isEmpty() && weAreOrganizer(incidence)) {
991 for (
auto &attendee : attendees) {
994 attendee.setRSVP(
true);
1005 if (mModificationsInProgress.contains(newItem.
id())) {
1008 queueModification(change);
1010 auto modifyJob =
new ItemModifyJob(newItem, parentJob(change));
1011 mChangeForJob.insert(modifyJob, change);
1012 mDirtyFieldsByJob.insert(modifyJob,
incidence->dirtyFields());
1014 if (hasAtomicOperationId) {
1015 AtomicOperation *atomic = mAtomicOperations[atomicOperationId];
1017 atomic->addChange(change);
1020 mModificationsInProgress[newItem.
id()] = change;
1026void IncidenceChanger::startAtomicOperation(
const QString &operationDescription)
1028 if (d->mBatchOperationInProgress) {
1029 qCDebug(AKONADICALENDAR_LOG) <<
"An atomic operation is already in progress.";
1033 ++d->mLatestAtomicOperationId;
1034 d->mBatchOperationInProgress =
true;
1036 auto atomicOperation =
new AtomicOperation(d.get(), d->mLatestAtomicOperationId);
1037 atomicOperation->m_description = operationDescription;
1038 d->mAtomicOperations.
insert(d->mLatestAtomicOperationId, atomicOperation);
1041void IncidenceChanger::endAtomicOperation()
1043 if (!d->mBatchOperationInProgress) {
1044 qCDebug(AKONADICALENDAR_LOG) <<
"No atomic operation is in progress.";
1048 Q_ASSERT_X(d->mLatestAtomicOperationId != 0,
"IncidenceChanger::endAtomicOperation()",
"Call startAtomicOperation() first.");
1050 Q_ASSERT(d->mAtomicOperations.contains(d->mLatestAtomicOperationId));
1051 AtomicOperation *atomicOperation = d->mAtomicOperations[d->mLatestAtomicOperationId];
1052 Q_ASSERT(atomicOperation);
1053 atomicOperation->m_endCalled =
true;
1055 const bool allJobsCompleted = !atomicOperation->pendingJobs();
1057 if (allJobsCompleted && atomicOperation->rolledback() && atomicOperation->m_transactionCompleted) {
1059 delete d->mAtomicOperations.take(d->mLatestAtomicOperationId);
1060 d->mBatchOperationInProgress =
false;
1067void IncidenceChanger::setShowDialogsOnError(
bool enable)
1069 d->mShowDialogsOnError = enable;
1071 d->mHistory->incidenceChanger()->setShowDialogsOnError(enable);
1075bool IncidenceChanger::showDialogsOnError()
const
1077 return d->mShowDialogsOnError;
1080void IncidenceChanger::setRespectsCollectionRights(
bool respects)
1082 d->mRespectsCollectionRights = respects;
1085bool IncidenceChanger::respectsCollectionRights()
const
1087 return d->mRespectsCollectionRights;
1090void IncidenceChanger::setDestinationPolicy(IncidenceChanger::DestinationPolicy destinationPolicy)
1092 d->mDestinationPolicy = destinationPolicy;
1095IncidenceChanger::DestinationPolicy IncidenceChanger::destinationPolicy()
const
1097 return d->mDestinationPolicy;
1102 d->mEntityTreeModel = entityTreeModel;
1107 return d->mEntityTreeModel;
1112 d->mDefaultCollection = collection;
1115Collection IncidenceChanger::defaultCollection()
const
1117 return d->mDefaultCollection;
1120bool IncidenceChanger::historyEnabled()
const
1122 return d->mUseHistory;
1125void IncidenceChanger::setHistoryEnabled(
bool enable)
1127 if (d->mUseHistory != enable) {
1128 d->mUseHistory = enable;
1129 if (enable && !d->mHistory) {
1130 d->mHistory =
new History(d.get());
1135History *IncidenceChanger::history()
const
1142 return d->deleteAlreadyCalled(
id);
1145void IncidenceChanger::setGroupwareCommunication(
bool enabled)
1147 d->mGroupwareCommunication = enabled;
1150bool IncidenceChanger::groupwareCommunication()
const
1152 return d->mGroupwareCommunication;
1155void IncidenceChanger::setAutoAdjustRecurrence(
bool enable)
1157 d->mAutoAdjustRecurrence = enable;
1160bool IncidenceChanger::autoAdjustRecurrence()
const
1162 return d->mAutoAdjustRecurrence;
1165void IncidenceChanger::setInvitationPolicy(IncidenceChanger::InvitationPolicy policy)
1167 d->m_invitationPolicy = policy;
1170IncidenceChanger::InvitationPolicy IncidenceChanger::invitationPolicy()
const
1172 return d->m_invitationPolicy;
1177 return d->mLastCollectionUsed;
1182 d->m_invitationPrivacy = invitationPrivacy;
1187 return d->m_invitationPrivacy;
1190QString IncidenceChangerPrivate::showErrorDialog(IncidenceChanger::ResultCode resultCode,
QWidget *parent)
1193 switch (resultCode) {
1194 case IncidenceChanger::ResultCodePermissions:
1195 errorString =
i18n(
"Operation can not be performed due to ACL restrictions");
1197 case IncidenceChanger::ResultCodeInvalidUserCollection:
1198 errorString =
i18n(
"The chosen collection is invalid");
1200 case IncidenceChanger::ResultCodeInvalidDefaultCollection:
1202 "Default collection is invalid or doesn't have proper ACLs"
1203 " and DestinationPolicyNeverAsk was used");
1205 case IncidenceChanger::ResultCodeDuplicateId:
1206 errorString =
i18n(
"Duplicate item id in a group operation");
1208 case IncidenceChanger::ResultCodeRolledback:
1210 "One change belonging to a group of changes failed. "
1211 "All changes are being rolled back.");
1218 if (mShowDialogsOnError) {
1227 if (!originalIncidence || !
incidence->recurs() ||
incidence->hasRecurrenceId() || !mAutoAdjustRecurrence
1232 const QDate originalDate = originalIncidence->dtStart().date();
1235 if (!originalDate.
isValid() || !newStartDate.
isValid() || originalDate == newStartDate) {
1241 case KCalendarCore::Recurrence::rWeekly: {
1243 const int oldIndex = originalDate.
dayOfWeek() - 1;
1244 const int newIndex = newStartDate.
dayOfWeek() - 1;
1245 if (oldIndex != newIndex) {
1259 const QDate recurrenceEndDate = recurrence->defaultRRule() ? recurrence->defaultRRule()->endDt().date() :
QDate();
1260 if (recurrenceEndDate.
isValid() && recurrenceEndDate < newStartDate) {
1265void IncidenceChangerPrivate::cancelTransaction()
1267 if (mBatchOperationInProgress) {
1268 mAtomicOperations[mLatestAtomicOperationId]->setRolledback();
1272void IncidenceChangerPrivate::cleanupTransaction()
1274 Q_ASSERT(mAtomicOperations.contains(mLatestAtomicOperationId));
1275 AtomicOperation *operation = mAtomicOperations[mLatestAtomicOperationId];
1276 Q_ASSERT(operation);
1277 Q_ASSERT(operation->rolledback());
1278 if (!operation->pendingJobs() && operation->m_endCalled && operation->m_transactionCompleted) {
1279 delete mAtomicOperations.take(mLatestAtomicOperationId);
1280 mBatchOperationInProgress =
false;
1284bool IncidenceChangerPrivate::allowAtomicOperation(
int atomicOperationId,
const Change::Ptr &change)
const
1287 if (atomicOperationId > 0) {
1288 Q_ASSERT(mAtomicOperations.contains(atomicOperationId));
1289 AtomicOperation *operation = mAtomicOperations.value(atomicOperationId);
1291 if (change->type == IncidenceChanger::ChangeTypeCreate) {
1293 }
else if (change->type == IncidenceChanger::ChangeTypeModify) {
1294 allow = !operation->m_itemIdsInOperation.contains(change->newItem.id());
1295 }
else if (change->type == IncidenceChanger::ChangeTypeDelete) {
1296 DeletionChange::Ptr deletion = change.staticCast<DeletionChange>();
1298 if (operation->m_itemIdsInOperation.contains(
id)) {
1307 qCWarning(AKONADICALENDAR_LOG) <<
"Each change belonging to a group operation"
1308 <<
"must have a different Akonadi::Item::Id";
1315void ModificationChange::emitCompletionSignal()
1317 emitModifyFinished(changer,
id, newItem, resultCode, errorString);
1321void CreationChange::emitCompletionSignal()
1324 emitCreateFinished(changer,
id, newItem, resultCode, errorString);
1328void DeletionChange::emitCompletionSignal()
1330 emitDeleteFinished(changer,
id, mItemIds, resultCode, errorString);
1365AtomicOperation::AtomicOperation(IncidenceChangerPrivate *icp, uint ident)
1367 , m_endCalled(false)
1368 , m_numCompletedChanges(0)
1369 , m_transactionCompleted(false)
1370 , m_wasRolledback(false)
1371 , m_transaction(nullptr)
1372 , m_incidenceChangerPrivate(icp)
1374 Q_ASSERT(m_id != 0);
1379 if (!m_transaction) {
1383 m_incidenceChangerPrivate->mAtomicOperationByTransaction.insert(m_transaction, m_id);
1388 return m_transaction;
1391#include "moc_incidencechanger.cpp"
History class for implementing undo/redo of calendar operations.
Factory to create Akonadi::MessageQueueJob jobs or ITIPHandlerDialogDelegate objects.
@ ActionAsk
Ask the user for a decision.
@ ActionDontSendMessage
Answer with No.
@ ActionSendMessage
Answer with Yes.
void setRevision(int revision)
void setRemoteRevision(const QString &revision)
void setAutomaticCommittingEnabled(bool enable)
ushort recurrenceType() const
void setWeekly(int freq, const QBitArray &days, int weekStart=1)
void setEndDate(const QDate &endDate)
virtual QString errorString() const
Q_SCRIPTABLE CaptureState status()
QString i18nc(const char *context, const char *text, const TYPE &arg...)
QString i18n(const char *text, const TYPE &arg...)
AKONADI_CALENDAR_EXPORT KCalendarCore::Incidence::Ptr incidence(const Akonadi::Item &item)
Returns the incidence from an Akonadi item, or a null pointer if the item has no such payload.
FreeBusyManager::Singleton.
KCALUTILS_EXPORT QString errorMessage(const KCalendarCore::Exception &exception)
void error(QWidget *parent, const QString &text, const QString &title, const KGuiItem &buttonOk, Options options=Notify)
KIOCORE_EXPORT QStringList list(const QString &fileClass)
void clearBit(qsizetype i)
int dayOfWeek() const const
bool isValid(int year, int month, int day)
QDateTime currentDateTimeUtc()
bool contains(const Key &key) const const
void append(QList< T > &&value)
qsizetype count() const const
iterator insert(const_iterator before, parameter_type value)
bool isEmpty() const const
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
QObject * parent() const const
QSharedPointer< X > staticCast() const const
QString & insert(qsizetype position, QChar ch)
QString & remove(QChar ch, Qt::CaseSensitivity cs)
QFuture< ArgsType< Signal > > connect(Sender *sender, Signal signal)