File indexing completed on 2024-05-12 05:10:46

0001 /*
0002   SPDX-FileCopyrightText: 2004 Reinhold Kainhofer <reinhold@kainhofer.com>
0003 
0004   SPDX-FileCopyrightText: 2010 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.net
0005   SPDX-FileContributor: Sergio Martins <sergio.martins@kdab.com>
0006 
0007   SPDX-FileCopyrightText: 2010-2012 SĂ©rgio Martins <iamsergio@gmail.com>
0008 
0009   SPDX-License-Identifier: LGPL-2.0-or-later
0010 */
0011 #pragma once
0012 
0013 #include "history.h"
0014 #include "incidencechanger.h"
0015 #include "itiphandlerhelper_p.h"
0016 
0017 #include <Akonadi/Collection>
0018 #include <Akonadi/Item>
0019 #include <Akonadi/TransactionSequence>
0020 
0021 #include <QList>
0022 #include <QObject>
0023 #include <QPointer>
0024 #include <QSet>
0025 
0026 class KJob;
0027 class QWidget;
0028 
0029 namespace Akonadi
0030 {
0031 class TransactionSequence;
0032 class CollectionFetchJob;
0033 
0034 class Change : public QObject
0035 {
0036     Q_OBJECT
0037 public:
0038     using Ptr = QSharedPointer<Change>;
0039     using List = QList<Ptr>;
0040     Change(IncidenceChanger *incidenceChanger, int changeId, IncidenceChanger::ChangeType changeType, uint operationId, QWidget *parent)
0041         : id(changeId)
0042         , type(changeType)
0043         , recordToHistory(incidenceChanger->historyEnabled())
0044         , parentWidget(parent)
0045         , atomicOperationId(operationId)
0046         , resultCode(Akonadi::IncidenceChanger::ResultCodeSuccess)
0047         , completed(false)
0048         , queuedModification(false)
0049         , useGroupwareCommunication(incidenceChanger->groupwareCommunication())
0050         , changer(incidenceChanger)
0051     {
0052     }
0053 
0054     ~Change() override
0055     {
0056         if (parentChange) {
0057             parentChange->childAboutToDie(this);
0058         }
0059     }
0060 
0061     virtual void childAboutToDie(Change *child)
0062     {
0063         Q_UNUSED(child)
0064     }
0065 
0066     virtual void emitCompletionSignal() = 0;
0067 
0068     const int id;
0069     const IncidenceChanger::ChangeType type;
0070     const bool recordToHistory;
0071     const QPointer<QWidget> parentWidget;
0072     uint atomicOperationId;
0073 
0074     // If this change is internal, i.e. not initiated by the user, mParentChange will
0075     // contain the non-internal change.
0076     QSharedPointer<Change> parentChange;
0077 
0078     Akonadi::Item::List originalItems;
0079     Akonadi::Item newItem;
0080 
0081     QString errorString;
0082     IncidenceChanger::ResultCode resultCode;
0083     bool completed;
0084     bool queuedModification;
0085     bool useGroupwareCommunication;
0086 
0087 Q_SIGNALS:
0088     void dialogClosedBeforeChange(int id, ITIPHandlerHelper::SendResult status);
0089     void dialogClosedAfterChange(int id, ITIPHandlerHelper::SendResult status);
0090 
0091 public Q_SLOTS:
0092     void emitUserDialogClosedAfterChange(Akonadi::ITIPHandlerHelper::SendResult status);
0093     void emitUserDialogClosedBeforeChange(Akonadi::ITIPHandlerHelper::SendResult status);
0094 
0095 protected:
0096     IncidenceChanger *const changer;
0097 };
0098 
0099 class ModificationChange : public Change
0100 {
0101     Q_OBJECT
0102 public:
0103     using Ptr = QSharedPointer<ModificationChange>;
0104     ModificationChange(IncidenceChanger *changer, int id, uint atomicOperationId, QWidget *parent)
0105         : Change(changer, id, IncidenceChanger::ChangeTypeModify, atomicOperationId, parent)
0106     {
0107     }
0108 
0109     ~ModificationChange() override
0110     {
0111         if (!parentChange) {
0112             emitCompletionSignal();
0113         }
0114     }
0115 
0116     void emitCompletionSignal() override;
0117 };
0118 
0119 class CreationChange : public Change
0120 {
0121     Q_OBJECT
0122 public:
0123     using Ptr = QSharedPointer<CreationChange>;
0124     CreationChange(IncidenceChanger *changer, int id, uint atomicOperationId, QWidget *parent)
0125         : Change(changer, id, IncidenceChanger::ChangeTypeCreate, atomicOperationId, parent)
0126     {
0127     }
0128 
0129     ~CreationChange() override
0130     {
0131         // qCDebug(AKONADICALENDAR_LOG) << "CreationChange::~ will emit signal with " << resultCode;
0132         if (!parentChange) {
0133             emitCompletionSignal();
0134         }
0135     }
0136 
0137     void emitCompletionSignal() override;
0138 
0139     Akonadi::Collection mUsedCol1lection;
0140 };
0141 
0142 class DeletionChange : public Change
0143 {
0144     Q_OBJECT
0145 public:
0146     using Ptr = QSharedPointer<DeletionChange>;
0147     DeletionChange(IncidenceChanger *changer, int id, uint atomicOperationId, QWidget *parent)
0148         : Change(changer, id, IncidenceChanger::ChangeTypeDelete, atomicOperationId, parent)
0149     {
0150     }
0151 
0152     ~DeletionChange() override
0153     {
0154         // qCDebug(AKONADICALENDAR_LOG) << "DeletionChange::~ will emit signal with " << resultCode;
0155         if (!parentChange) {
0156             emitCompletionSignal();
0157         }
0158     }
0159 
0160     void emitCompletionSignal() override;
0161 
0162     QList<Akonadi::Item::Id> mItemIds;
0163 };
0164 
0165 class AtomicOperation
0166 {
0167 public:
0168     uint m_id;
0169 
0170     // To make sure they are not repeated
0171     QSet<Akonadi::Item::Id> m_itemIdsInOperation;
0172 
0173     // After endAtomicOperation() is called we don't accept more changes
0174     bool m_endCalled;
0175 
0176     // Number of completed changes(jobs)
0177     int m_numCompletedChanges;
0178     QString m_description;
0179     bool m_transactionCompleted;
0180 
0181     AtomicOperation(IncidenceChangerPrivate *icp, uint ident);
0182 
0183     ~AtomicOperation()
0184     {
0185         // qCDebug(AKONADICALENDAR_LOG) << "AtomicOperation::~ " << wasRolledback << changes.count();
0186         if (m_wasRolledback) {
0187             for (int i = 0; i < m_changes.count(); ++i) {
0188                 // When a job that can finish successfully is aborted because the transaction failed
0189                 // because of some other job, akonadi is returning an Unknown error
0190                 // which isn't very specific
0191                 if (m_changes[i]->completed
0192                     && (m_changes[i]->resultCode == IncidenceChanger::ResultCodeSuccess
0193                         || (m_changes[i]->resultCode == IncidenceChanger::ResultCodeJobError
0194                             && m_changes[i]->errorString == QLatin1StringView("Unknown error.")))) {
0195                     m_changes[i]->resultCode = IncidenceChanger::ResultCodeRolledback;
0196                 }
0197             }
0198         }
0199     }
0200 
0201     // Did all jobs return ?
0202     bool pendingJobs() const
0203     {
0204         return m_changes.count() > m_numCompletedChanges;
0205     }
0206 
0207     void setRolledback()
0208     {
0209         // qCDebug(AKONADICALENDAR_LOG) << "AtomicOperation::setRolledBack()";
0210         m_wasRolledback = true;
0211         transaction()->rollback();
0212     }
0213 
0214     bool rolledback() const
0215     {
0216         return m_wasRolledback;
0217     }
0218 
0219     void addChange(const Change::Ptr &change)
0220     {
0221         if (change->type == IncidenceChanger::ChangeTypeDelete) {
0222             DeletionChange::Ptr deletion = change.staticCast<DeletionChange>();
0223             for (Akonadi::Item::Id id : std::as_const(deletion->mItemIds)) {
0224                 Q_ASSERT(!m_itemIdsInOperation.contains(id));
0225                 m_itemIdsInOperation.insert(id);
0226             }
0227         } else if (change->type == IncidenceChanger::ChangeTypeModify) {
0228             Q_ASSERT(!m_itemIdsInOperation.contains(change->newItem.id()));
0229             m_itemIdsInOperation.insert(change->newItem.id());
0230         }
0231 
0232         m_changes << change;
0233     }
0234 
0235     Akonadi::TransactionSequence *transaction();
0236 
0237 private:
0238     Q_DISABLE_COPY(AtomicOperation)
0239     QList<Change::Ptr> m_changes;
0240     bool m_wasRolledback = false;
0241     Akonadi::TransactionSequence *m_transaction = nullptr; // constructed in first use
0242     IncidenceChangerPrivate *m_incidenceChangerPrivate = nullptr;
0243 };
0244 
0245 class IncidenceChangerPrivate : public QObject
0246 {
0247     Q_OBJECT
0248 public:
0249     explicit IncidenceChangerPrivate(bool enableHistory, ITIPHandlerComponentFactory *factory, IncidenceChanger *mIncidenceChanger);
0250     ~IncidenceChangerPrivate() override;
0251 
0252     void loadCollections(); // async-loading of list of writable collections
0253     bool isLoadingCollections() const;
0254     Collection::List collectionsForMimeType(const QString &mimeType, const Collection::List &collections);
0255 
0256     // steps for the async operation:
0257     void step1DetermineDestinationCollection(const Change::Ptr &change, const Collection &collection);
0258     void step2CreateIncidence(const Change::Ptr &change, const Collection &collection);
0259 
0260     /**
0261         Returns true if, for a specific item, an ItemDeleteJob is already running,
0262         or if one already run successfully.
0263     */
0264     bool deleteAlreadyCalled(Akonadi::Item::Id id) const;
0265 
0266     QString showErrorDialog(Akonadi::IncidenceChanger::ResultCode, QWidget *parent);
0267 
0268     void adjustRecurrence(const KCalendarCore::Incidence::Ptr &originalIncidence, const KCalendarCore::Incidence::Ptr &incidence);
0269 
0270     bool hasRights(const Akonadi::Collection &collection, IncidenceChanger::ChangeType) const;
0271     void queueModification(const Change::Ptr &);
0272     void performModification(const Change::Ptr &);
0273     bool atomicOperationIsValid(uint atomicOperationId) const;
0274     Akonadi::Job *parentJob(const Change::Ptr &change) const;
0275     void cancelTransaction();
0276     void cleanupTransaction();
0277     bool allowAtomicOperation(int atomicOperationId, const Change::Ptr &change) const;
0278 
0279     void handleInvitationsBeforeChange(const Change::Ptr &change);
0280     void handleInvitationsAfterChange(const Change::Ptr &change);
0281     static bool
0282     myAttendeeStatusChanged(const KCalendarCore::Incidence::Ptr &newIncidence, const KCalendarCore::Incidence::Ptr &oldIncidence, const QStringList &myEmails);
0283 
0284 public Q_SLOTS:
0285     void handleCreateJobResult(KJob *job);
0286     void handleModifyJobResult(KJob *job);
0287     void handleDeleteJobResult(KJob *job);
0288     void handleTransactionJobResult(KJob *job);
0289     void performNextModification(Akonadi::Item::Id id);
0290     void onCollectionsLoaded(KJob *job);
0291 
0292     void handleCreateJobResult2(int changeId, ITIPHandlerHelper::SendResult);
0293     void handleDeleteJobResult2(int changeId, ITIPHandlerHelper::SendResult);
0294     void handleModifyJobResult2(int changeId, ITIPHandlerHelper::SendResult);
0295     void performModification2(int changeId, ITIPHandlerHelper::SendResult);
0296     void deleteIncidences2(int changeId, ITIPHandlerHelper::SendResult);
0297 
0298 public:
0299     int mLatestChangeId;
0300     QHash<const KJob *, Change::Ptr> mChangeForJob;
0301     bool mShowDialogsOnError = false;
0302     Akonadi::Collection mDefaultCollection;
0303     Akonadi::EntityTreeModel *mEntityTreeModel = nullptr;
0304     IncidenceChanger::DestinationPolicy mDestinationPolicy;
0305     QList<Akonadi::Item::Id> mDeletedItemIds;
0306     Change::List mPendingCreations; // Creations waiting for collections to be loaded
0307 
0308     History *mHistory = nullptr;
0309     bool mUseHistory = false;
0310 
0311     /**
0312       Queue modifications by ID. We can only send a modification to akonadi when the previous
0313       one ended.
0314 
0315       The container doesn't look like a queue because of an optimization: if there's a modification
0316       A in progress, a modification B waiting (queued), and then a new one C comes in,
0317       we just discard B, and queue C. The queue always has 1 element max.
0318     */
0319     QHash<Akonadi::Item::Id, Change::Ptr> mQueuedModifications;
0320 
0321     /**
0322         So we know if there's already a modification in progress
0323       */
0324     QHash<Akonadi::Item::Id, Change::Ptr> mModificationsInProgress;
0325 
0326     QHash<int, Change::Ptr> mChangeById;
0327 
0328     /**
0329         Indexed by atomic operation id.
0330     */
0331     QHash<uint, AtomicOperation *> mAtomicOperations;
0332 
0333     bool mRespectsCollectionRights = false;
0334     bool mGroupwareCommunication = false;
0335 
0336     QHash<Akonadi::TransactionSequence *, uint> mAtomicOperationByTransaction;
0337     QHash<uint, ITIPHandlerHelper::SendResult> mInvitationStatusByAtomicOperation;
0338 
0339     uint mLatestAtomicOperationId;
0340     bool mBatchOperationInProgress;
0341     Akonadi::Collection mLastCollectionUsed;
0342     bool mAutoAdjustRecurrence;
0343 
0344     Akonadi::CollectionFetchJob *m_collectionFetchJob = nullptr;
0345 
0346     QMap<KJob *, QSet<KCalendarCore::IncidenceBase::Field>> mDirtyFieldsByJob;
0347 
0348     IncidenceChanger::InvitationPolicy m_invitationPolicy;
0349     IncidenceChanger::InvitationPrivacyFlags m_invitationPrivacy = IncidenceChanger::InvitationPrivacyPlain;
0350 
0351     ITIPHandlerComponentFactory *mFactory = nullptr;
0352 
0353 private:
0354     IncidenceChanger *q = nullptr;
0355 };
0356 }