File indexing completed on 2024-05-19 05:11:16
0001 /* 0002 SPDX-FileCopyrightText: 2010-2012 Sérgio Martins <iamsergio@gmail.com> 0003 0004 SPDX-License-Identifier: LGPL-2.0-or-later 0005 */ 0006 0007 #include "history_p.h" 0008 #include "akonadicalendar_debug.h" 0009 #include <KCalUtils/Stringify> 0010 #include <KLocalizedString> 0011 0012 Entry::Entry(const Akonadi::Item &item, const QString &description, History *qq) 0013 : QObject() 0014 { 0015 mItems << item; 0016 init(description, qq); 0017 } 0018 0019 Entry::Entry(const Akonadi::Item::List &items, const QString &description, History *qq) 0020 : QObject() 0021 , mItems(items) 0022 { 0023 init(description, qq); 0024 } 0025 0026 void Entry::init(const QString &description, History *qq) 0027 { 0028 mDescription = description; 0029 q = qq; 0030 mChanger = qq->d->mChanger; 0031 } 0032 0033 QWidget *Entry::currentParent() const 0034 { 0035 return q->d->mCurrentParent; 0036 } 0037 0038 void Entry::updateIds(Item::Id oldId, Item::Id newId) 0039 { 0040 Q_ASSERT(newId != -1); 0041 Q_ASSERT(oldId != newId); 0042 0043 Akonadi::Item::List::iterator it = mItems.begin(); 0044 while (it != mItems.end()) { 0045 if ((*it).id() == oldId) { 0046 (*it).setId(newId); 0047 (*it).setRevision(0); 0048 } 0049 ++it; 0050 } 0051 } 0052 0053 void Entry::updateIdsGlobaly(Item::Id oldId, Item::Id newId) 0054 { 0055 q->d->updateIds(oldId, newId); 0056 } 0057 0058 void Entry::doIt(OperationType type) 0059 { 0060 bool result = false; 0061 mChangeIds.clear(); 0062 if (type == TypeRedo) { 0063 result = redo(); 0064 } else if (type == TypeUndo) { 0065 result = undo(); 0066 } else { 0067 Q_ASSERT(false); 0068 } 0069 0070 if (!result) { 0071 Q_EMIT finished(IncidenceChanger::ResultCodeJobError, i18n("General error")); 0072 } 0073 } 0074 0075 CreationEntry::CreationEntry(const Akonadi::Item &item, const QString &description, History *q) 0076 : Entry(item, description, q) 0077 { 0078 mLatestRevisionByItemId.insert(item.id(), item.revision()); 0079 Q_ASSERT(mItems.count() == 1); 0080 const auto incidence = mItems.constFirst().payload<KCalendarCore::Incidence::Ptr>(); 0081 if (mDescription.isEmpty()) { 0082 mDescription = i18nc("%1 is event, todo or journal", "%1 creation", KCalUtils::Stringify::incidenceType(incidence->type())); 0083 } 0084 connect(mChanger, &IncidenceChanger::createFinished, this, &CreationEntry::onCreateFinished); 0085 connect(mChanger, &IncidenceChanger::deleteFinished, this, &CreationEntry::onDeleteFinished); 0086 } 0087 0088 bool CreationEntry::undo() 0089 { 0090 const int changeId = mChanger->deleteIncidence(mItems.constFirst(), currentParent()); 0091 mChangeIds << changeId; 0092 0093 if (changeId == -1) { 0094 qCritical() << "Undo failed"; 0095 } 0096 0097 return changeId != -1; 0098 } 0099 0100 bool CreationEntry::redo() 0101 { 0102 const Akonadi::Item item = mItems.constFirst(); 0103 Q_ASSERT(item.hasPayload<KCalendarCore::Incidence::Ptr>()); 0104 const int changeId = mChanger->createIncidence(item.payload<KCalendarCore::Incidence::Ptr>(), Collection(item.storageCollectionId()), currentParent()); 0105 mChangeIds << changeId; 0106 0107 if (changeId == -1) { 0108 qCritical() << "Redo failed"; 0109 } 0110 0111 return changeId != -1; 0112 } 0113 0114 void CreationEntry::onDeleteFinished(int changeId, 0115 const QList<Akonadi::Item::Id> &deletedIds, 0116 Akonadi::IncidenceChanger::ResultCode resultCode, 0117 const QString &errorString) 0118 { 0119 if (mChangeIds.contains(changeId)) { 0120 if (resultCode == IncidenceChanger::ResultCodeSuccess) { 0121 Q_ASSERT(deletedIds.count() == 1); 0122 mLatestRevisionByItemId.remove(deletedIds.constFirst()); // TODO 0123 } 0124 Q_EMIT finished(resultCode, errorString); 0125 } 0126 } 0127 0128 void CreationEntry::onCreateFinished(int changeId, const Akonadi::Item &item, Akonadi::IncidenceChanger::ResultCode resultCode, const QString &errorString) 0129 { 0130 if (mChangeIds.contains(changeId)) { 0131 if (resultCode == IncidenceChanger::ResultCodeSuccess) { 0132 mLatestRevisionByItemId.insert(item.id(), item.revision()); 0133 Q_ASSERT(mItems.count() == 1); 0134 0135 if (mItems.constFirst().id() == item.id()) { 0136 qCWarning(AKONADICALENDAR_LOG) << "Duplicate id. Old= " << mItems.constFirst().id() << item.id(); 0137 Q_ASSERT(false); 0138 } 0139 updateIdsGlobaly(mItems.constFirst().id(), item.id()); 0140 } 0141 Q_EMIT finished(resultCode, errorString); 0142 } 0143 } 0144 0145 DeletionEntry::DeletionEntry(const Akonadi::Item::List &items, const QString &description, History *q) 0146 : Entry(items, description, q) 0147 { 0148 const auto incidence = items.constFirst().payload<KCalendarCore::Incidence::Ptr>(); 0149 if (mDescription.isEmpty()) { 0150 mDescription = i18nc("%1 is event, todo or journal", "%1 deletion", KCalUtils::Stringify::incidenceType(incidence->type())); 0151 } 0152 connect(mChanger, &IncidenceChanger::createFinished, this, &DeletionEntry::onCreateFinished); 0153 connect(mChanger, &IncidenceChanger::deleteFinished, this, &DeletionEntry::onDeleteFinished); 0154 } 0155 0156 bool DeletionEntry::undo() 0157 { 0158 mResultCode = IncidenceChanger::ResultCodeSuccess; 0159 mErrorString.clear(); 0160 const bool useAtomicOperation = mItems.count() > 1; 0161 bool success = true; 0162 for (const Akonadi::Item &item : std::as_const(mItems)) { 0163 if (useAtomicOperation) { 0164 mChanger->startAtomicOperation(); 0165 } 0166 0167 Q_ASSERT(item.hasPayload<KCalendarCore::Incidence::Ptr>()); 0168 const int changeId = mChanger->createIncidence(item.payload<KCalendarCore::Incidence::Ptr>(), Collection(item.storageCollectionId()), currentParent()); 0169 success = (changeId != -1) && success; 0170 mChangeIds << changeId; 0171 if (useAtomicOperation) { 0172 mChanger->endAtomicOperation(); 0173 } 0174 0175 mOldIdByChangeId.insert(changeId, item.id()); 0176 } 0177 mNumPendingCreations = mItems.count(); 0178 return success; 0179 } 0180 0181 bool DeletionEntry::redo() 0182 { 0183 const int changeId = mChanger->deleteIncidences(mItems, currentParent()); 0184 mChangeIds << changeId; 0185 0186 if (changeId == -1) { 0187 qCritical() << "Redo failed"; 0188 } 0189 0190 return changeId != -1; 0191 } 0192 0193 void DeletionEntry::onDeleteFinished(int changeId, 0194 const QList<Akonadi::Item::Id> &deletedIds, 0195 Akonadi::IncidenceChanger::ResultCode resultCode, 0196 const QString &errorString) 0197 { 0198 if (mChangeIds.contains(changeId)) { 0199 if (resultCode == IncidenceChanger::ResultCodeSuccess) { 0200 for (const Akonadi::Item::Id id : deletedIds) { 0201 mLatestRevisionByItemId.remove(id); // TODO 0202 } 0203 } 0204 Q_EMIT finished(resultCode, errorString); 0205 } 0206 } 0207 0208 void DeletionEntry::onCreateFinished(int changeId, const Akonadi::Item &item, Akonadi::IncidenceChanger::ResultCode resultCode, const QString &errorString) 0209 { 0210 if (mChangeIds.contains(changeId)) { 0211 if (resultCode == IncidenceChanger::ResultCodeSuccess) { 0212 updateIdsGlobaly(mOldIdByChangeId.value(changeId), item.id()); 0213 mLatestRevisionByItemId.insert(item.id(), item.revision()); 0214 } else { 0215 mResultCode = resultCode; 0216 mErrorString = errorString; 0217 } 0218 --mNumPendingCreations; 0219 mOldIdByChangeId.remove(changeId); 0220 if (mNumPendingCreations == 0) { 0221 Q_EMIT finished(mResultCode, mErrorString); 0222 } 0223 } 0224 } 0225 0226 ModificationEntry::ModificationEntry(const Akonadi::Item &item, const Incidence::Ptr &originalPayload, const QString &description, History *q) 0227 : Entry(item, description, q) 0228 , mOriginalPayload(originalPayload->clone()) 0229 { 0230 const auto incidence = mItems.constFirst().payload<KCalendarCore::Incidence::Ptr>(); 0231 if (mDescription.isEmpty()) { 0232 mDescription = i18nc("%1 is event, todo or journal", "%1 modification", KCalUtils::Stringify::incidenceType(incidence->type())); 0233 } 0234 0235 connect(mChanger, &IncidenceChanger::modifyFinished, this, &ModificationEntry::onModifyFinished); 0236 } 0237 0238 bool ModificationEntry::undo() 0239 { 0240 Item oldItem = mItems.constFirst(); 0241 oldItem.setPayload<KCalendarCore::Incidence::Ptr>(mOriginalPayload); 0242 const int changeId = mChanger->modifyIncidence(oldItem, Incidence::Ptr(), currentParent()); 0243 mChangeIds << changeId; 0244 0245 if (changeId == -1) { 0246 qCritical() << "Undo failed"; 0247 } 0248 0249 return changeId != -1; 0250 } 0251 0252 bool ModificationEntry::redo() 0253 { 0254 const int changeId = mChanger->modifyIncidence(mItems.constFirst(), mOriginalPayload, currentParent()); 0255 mChangeIds << changeId; 0256 0257 if (changeId == -1) { 0258 qCritical() << "Redo failed"; 0259 } 0260 0261 return changeId != -1; 0262 } 0263 0264 void ModificationEntry::onModifyFinished(int changeId, const Akonadi::Item &item, Akonadi::IncidenceChanger::ResultCode resultCode, const QString &errorString) 0265 { 0266 if (mChangeIds.contains(changeId)) { 0267 if (resultCode == IncidenceChanger::ResultCodeSuccess) { 0268 mLatestRevisionByItemId.insert(item.id(), item.revision()); 0269 } 0270 Q_EMIT finished(resultCode, errorString); 0271 } 0272 } 0273 0274 MultiEntry::MultiEntry(int id, const QString &description, History *q) 0275 : Entry(Item(), description, q) 0276 , mAtomicOperationId(id) 0277 , mFinishedEntries(0) 0278 , mOperationInProgress(TypeNone) 0279 { 0280 } 0281 0282 void MultiEntry::addEntry(const Entry::Ptr &entry) 0283 { 0284 Q_ASSERT(mOperationInProgress == TypeNone); 0285 mEntries.append(entry); 0286 connect(entry.data(), &Entry::finished, this, &MultiEntry::onEntryFinished, Qt::UniqueConnection); 0287 } 0288 0289 void MultiEntry::updateIds(Item::Id oldId, Item::Id newId) 0290 { 0291 const int numberOfEntries(mEntries.count()); 0292 for (int i = 0; i < numberOfEntries; ++i) { 0293 mEntries.at(i)->updateIds(oldId, newId); 0294 } 0295 } 0296 0297 bool MultiEntry::undo() 0298 { 0299 mChanger->startAtomicOperation(); 0300 mOperationInProgress = TypeUndo; 0301 Q_ASSERT(!mEntries.isEmpty()); 0302 mFinishedEntries = 0; 0303 0304 const int count = mEntries.count(); 0305 // To undo a batch of changes we iterate in reverse order so we don't violate 0306 // causality. 0307 for (int i = count - 1; i >= 0; --i) { 0308 mEntries[i]->doIt(TypeUndo); 0309 } 0310 0311 mChanger->endAtomicOperation(); 0312 return true; 0313 } 0314 0315 bool MultiEntry::redo() 0316 { 0317 mChanger->startAtomicOperation(); 0318 mOperationInProgress = TypeRedo; 0319 Q_ASSERT(!mEntries.isEmpty()); 0320 mFinishedEntries = 0; 0321 for (const Entry::Ptr &entry : std::as_const(mEntries)) { 0322 entry->doIt(TypeRedo); 0323 } 0324 mChanger->endAtomicOperation(); 0325 return true; 0326 } 0327 0328 void MultiEntry::onEntryFinished(Akonadi::IncidenceChanger::ResultCode resultCode, const QString &errorString) 0329 { 0330 ++mFinishedEntries; 0331 if (mFinishedEntries == mEntries.count() || (mFinishedEntries < mEntries.count() && resultCode != IncidenceChanger::ResultCodeSuccess)) { 0332 mFinishedEntries = mEntries.count(); // we're done 0333 mOperationInProgress = TypeNone; 0334 Q_EMIT finished(resultCode, errorString); 0335 } 0336 } 0337 0338 #include "moc_history_p.cpp"