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 }