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"