File indexing completed on 2024-06-02 05:33:08

0001 /*
0002  * SPDX-FileCopyrightText: 2015 Kevin Ottens <ervin@kde.org>
0003  SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0004 */
0005 
0006 
0007 #include "akonadifakestorage.h"
0008 
0009 #include <QTimer>
0010 
0011 #include "akonadi/akonadistoragesettings.h"
0012 #include "akonadifakedata.h"
0013 #include "akonadifakejobs.h"
0014 
0015 #include "utils/jobhandler.h"
0016 
0017 using namespace Testlib;
0018 
0019 class AkonadiFakeTransaction : public FakeJob
0020 {
0021     Q_OBJECT
0022 public:
0023     explicit AkonadiFakeTransaction(QObject *parent = nullptr)
0024         : FakeJob(parent),
0025           m_nextIdx(0)
0026     {
0027     }
0028 
0029 private slots:
0030     void onTimeout() override
0031     {
0032         auto jobs = childJobs();
0033         if (m_nextIdx == 0) {
0034             const auto it = std::find_if(jobs.constBegin(), jobs.constEnd(),
0035                                          [] (FakeJob *job) { return job->expectedError() != 0; });
0036             if (it != jobs.constEnd()) {
0037                 setError((*it)->expectedError());
0038                 setErrorText((*it)->expectedErrorText());
0039                 emitResult();
0040                 return;
0041             }
0042         }
0043 
0044         if (m_nextIdx >= jobs.size()) {
0045             emitResult();
0046             return;
0047         }
0048 
0049         auto job = jobs[m_nextIdx];
0050         connect(job, &KJob::result, this, &AkonadiFakeTransaction::onTimeout);
0051         job->start();
0052         m_nextIdx++;
0053     }
0054 
0055 private:
0056     QList<FakeJob*> childJobs() const
0057     {
0058         QList<FakeJob*> jobs = findChildren<FakeJob*>();
0059         jobs.erase(std::remove_if(jobs.begin(), jobs.end(),
0060                                   [this] (FakeJob *job) {
0061                                       return job->parent() != this;
0062                                  }),
0063                    jobs.end());
0064         return jobs;
0065     }
0066 
0067     int m_nextIdx;
0068 };
0069 
0070 Utils::JobHandler::StartMode startModeForParent(QObject *parent)
0071 {
0072     bool isTransaction = qobject_cast<AkonadiFakeTransaction*>(parent);
0073     return isTransaction ? Utils::JobHandler::ManualStart
0074                          : Utils::JobHandler::AutoStart;
0075 }
0076 
0077 void noop() {}
0078 
0079 AkonadiFakeStorage::AkonadiFakeStorage(AkonadiFakeData *data)
0080     : m_data(data)
0081 {
0082 }
0083 
0084 Akonadi::Collection AkonadiFakeStorage::defaultCollection()
0085 {
0086     return Akonadi::StorageSettings::instance().defaultCollection();
0087 }
0088 
0089 KJob *AkonadiFakeStorage::createItem(Akonadi::Item item, Akonadi::Collection collection)
0090 {
0091     Q_ASSERT(!item.isValid());
0092 
0093     auto job = new FakeJob;
0094     if (!m_data->item(item.id()).isValid()) {
0095         job->setExpectedError(m_data->storageBehavior().createNextItemErrorCode(),
0096                               m_data->storageBehavior().createNextItemErrorText());
0097         Utils::JobHandler::install(job, [=] () mutable {
0098             if (!job->error()) {
0099                 item.setId(m_data->maxItemId() + 1);
0100                 item.setParentCollection(collection);
0101                 // Force payload detach
0102                 item.setPayloadFromData(item.payloadData());
0103                 m_data->createItem(item);
0104             }
0105         });
0106     } else {
0107         job->setExpectedError(1, QStringLiteral("Item already exists"));
0108         Utils::JobHandler::install(job, noop);
0109     }
0110     return job;
0111 }
0112 
0113 KJob *AkonadiFakeStorage::updateItem(Akonadi::Item item, QObject *parent)
0114 {
0115     auto job = new FakeJob(parent);
0116     auto startMode = startModeForParent(parent);
0117 
0118     if (m_data->item(item.id()).isValid()) {
0119         job->setExpectedError(m_data->storageBehavior().updateNextItemErrorCode(),
0120                               m_data->storageBehavior().updateNextItemErrorText());
0121         Utils::JobHandler::install(job, [=] () mutable {
0122             if (!job->error()) {
0123                 // Force payload detach
0124                 item.setPayloadFromData(item.payloadData());
0125                 m_data->modifyItem(item);
0126             }
0127         }, startMode);
0128     } else {
0129         job->setExpectedError(1, QStringLiteral("Item doesn't exist"));
0130         Utils::JobHandler::install(job, noop, startMode);
0131     }
0132     return job;
0133 }
0134 
0135 KJob *AkonadiFakeStorage::removeItem(Akonadi::Item item, QObject *parent)
0136 {
0137     auto job = new FakeJob(parent);
0138     if (m_data->item(item.id()).isValid()) {
0139         Utils::JobHandler::install(job, [=] {
0140             if (!job->error()) {
0141                 m_data->removeItem(item);
0142             }
0143         });
0144     } else {
0145         job->setExpectedError(1, QStringLiteral("Item doesn't exist"));
0146         Utils::JobHandler::install(job, noop);
0147     }
0148     return job;
0149 }
0150 
0151 KJob *AkonadiFakeStorage::removeItems(Akonadi::Item::List items, QObject *parent)
0152 {
0153     auto job = new FakeJob;
0154     auto startMode = startModeForParent(parent);
0155     bool allItemsExist = std::all_of(items.constBegin(), items.constEnd(),
0156                                      [=] (const Akonadi::Item &item) {
0157                                          return m_data->item(item.id()).isValid();
0158                                      });
0159 
0160     if (allItemsExist) {
0161         job->setExpectedError(m_data->storageBehavior().deleteNextItemErrorCode(),
0162                               m_data->storageBehavior().deleteNextItemErrorText());
0163         Utils::JobHandler::install(job, [=] {
0164             if (!job->error()) {
0165                 foreach (const Akonadi::Item &item, items) {
0166                     m_data->removeItem(item);
0167                 }
0168             }
0169         }, startMode);
0170     } else {
0171         job->setExpectedError(1, QStringLiteral("At least one item doesn't exist"));
0172         Utils::JobHandler::install(job, noop, startMode);
0173     }
0174     return job;
0175 }
0176 
0177 KJob *AkonadiFakeStorage::moveItem(Akonadi::Item item, Akonadi::Collection collection, QObject *parent)
0178 {
0179     auto job = new FakeJob(parent);
0180     auto startMode = startModeForParent(parent);
0181     if (m_data->item(item.id()).isValid()
0182      && m_data->collection(collection.id()).isValid()) {
0183         Utils::JobHandler::install(job, [=] () mutable {
0184             if (!job->error()) {
0185                 item.setParentCollection(collection);
0186                 // Force payload detach
0187                 item.setPayloadFromData(item.payloadData());
0188                 m_data->modifyItem(item);
0189             }
0190         }, startMode);
0191     } else {
0192         job->setExpectedError(1, QStringLiteral("The item or the collection doesn't exist"));
0193         Utils::JobHandler::install(job, noop, startMode);
0194     }
0195     return job;
0196 }
0197 
0198 KJob *AkonadiFakeStorage::moveItems(Akonadi::Item::List items, Akonadi::Collection collection, QObject *parent)
0199 {
0200     using namespace std::placeholders;
0201 
0202     auto job = new FakeJob(parent);
0203     auto startMode = startModeForParent(parent);
0204     bool allItemsExist = std::all_of(items.constBegin(), items.constEnd(),
0205                                      [=] (const Akonadi::Item &item) {
0206                                          return m_data->item(item.id()).isValid();
0207                                      });
0208 
0209     if (allItemsExist
0210      && m_data->collection(collection.id()).isValid()) {
0211         Utils::JobHandler::install(job, [=] () mutable {
0212             if (!job->error()) {
0213                 std::transform(items.constBegin(), items.constEnd(),
0214                                items.begin(),
0215                                [=] (const Akonadi::Item &item) {
0216                     auto result = item;
0217                     result.setParentCollection(collection);
0218                     // Force payload detach
0219                     result.setPayloadFromData(result.payloadData());
0220                     return result;
0221                 });
0222 
0223                 foreach (const Akonadi::Item &item, items) {
0224                     m_data->modifyItem(item);
0225                 }
0226             }
0227         }, startMode);
0228     } else {
0229         job->setExpectedError(1, QStringLiteral("One of the items or the collection doesn't exist"));
0230         Utils::JobHandler::install(job, noop, startMode);
0231     }
0232     return job;
0233 }
0234 
0235 KJob *AkonadiFakeStorage::createCollection(Akonadi::Collection collection, QObject *parent)
0236 {
0237     Q_ASSERT(!collection.isValid());
0238 
0239     auto job = new FakeJob(parent);
0240     auto startMode = startModeForParent(parent);
0241     if (!m_data->collection(collection.id()).isValid()) {
0242         Utils::JobHandler::install(job, [=] () mutable {
0243             if (!job->error()) {
0244                 collection.setId(m_data->maxCollectionId() + 1);
0245                 m_data->createCollection(collection);
0246             }
0247         }, startMode);
0248     } else {
0249         job->setExpectedError(1, QStringLiteral("The collection already exists"));
0250         Utils::JobHandler::install(job, noop, startMode);
0251     }
0252     return job;
0253 }
0254 
0255 KJob *AkonadiFakeStorage::updateCollection(Akonadi::Collection collection, QObject *parent)
0256 {
0257     auto job = new FakeJob(parent);
0258     auto startMode = startModeForParent(parent);
0259     if (m_data->collection(collection.id()).isValid()) {
0260         Utils::JobHandler::install(job, [=] {
0261             if (!job->error()) {
0262                 m_data->modifyCollection(collection);
0263             }
0264         }, startMode);
0265     } else {
0266         job->setExpectedError(1, QStringLiteral("The collection doesn't exist"));
0267         Utils::JobHandler::install(job, noop, startMode);
0268     }
0269     return job;
0270 }
0271 
0272 KJob *AkonadiFakeStorage::removeCollection(Akonadi::Collection collection, QObject *parent)
0273 {
0274     auto job = new FakeJob(parent);
0275     auto startMode = startModeForParent(parent);
0276     if (m_data->collection(collection.id()).isValid()) {
0277         Utils::JobHandler::install(job, [=] {
0278             if (!job->error()) {
0279                 m_data->removeCollection(collection);
0280             }
0281         }, startMode);
0282     } else {
0283         job->setExpectedError(1, QStringLiteral("The collection doesn't exist"));
0284         Utils::JobHandler::install(job, noop, startMode);
0285     }
0286     return job;
0287 }
0288 
0289 KJob *AkonadiFakeStorage::createTransaction(QObject *parent)
0290 {
0291     auto job = new AkonadiFakeTransaction(parent);
0292     Utils::JobHandler::install(job, noop);
0293     return job;
0294 }
0295 
0296 Akonadi::CollectionFetchJobInterface *AkonadiFakeStorage::fetchCollections(Akonadi::Collection collection,
0297                                                                            Akonadi::StorageInterface::FetchDepth depth,
0298                                                                            QObject *parent)
0299 {
0300     auto job = new AkonadiFakeCollectionFetchJob(parent);
0301     auto children = Akonadi::Collection::List();
0302 
0303     switch (depth) {
0304     case Base:
0305         children << m_data->collection(findId(collection));
0306         break;
0307     case FirstLevel:
0308         children << m_data->childCollections(findId(collection));
0309         break;
0310     case Recursive:
0311         children = collectChildren(collection);
0312         break;
0313     }
0314 
0315     auto collections = children;
0316 
0317     if (depth != Base) {
0318         // Replace the dummy parents in the ancestor chain with proper ones
0319         // full of juicy data
0320         using namespace std::placeholders;
0321         auto completeCollection = std::bind(&AkonadiFakeData::reconstructAncestors,
0322                                             m_data, _1, collection);
0323         std::transform(collections.begin(), collections.end(),
0324                        collections.begin(), completeCollection);
0325     }
0326 
0327     const auto behavior = m_data->storageBehavior().fetchCollectionsBehavior(collection.id());
0328     if (behavior == AkonadiFakeStorageBehavior::NormalFetch)
0329         job->setCollections(collections);
0330     job->setExpectedError(m_data->storageBehavior().fetchCollectionsErrorCode(collection.id()));
0331     Utils::JobHandler::install(job, noop);
0332     return job;
0333 }
0334 
0335 Akonadi::ItemFetchJobInterface *AkonadiFakeStorage::fetchItems(Akonadi::Collection collection, QObject *parent)
0336 {
0337     auto items = m_data->childItems(findId(collection));
0338     std::transform(items.begin(), items.end(),
0339                    items.begin(),
0340                    [this] (const Akonadi::Item &item) {
0341                        auto result = m_data->reconstructItemDependencies(item);
0342                        // Force payload detach
0343                        result.setPayloadFromData(result.payloadData());
0344                        return result;
0345                    });
0346 
0347     auto job = new AkonadiFakeItemFetchJob(parent);
0348     const auto behavior = m_data->storageBehavior().fetchItemsBehavior(collection.id());
0349     if (behavior == AkonadiFakeStorageBehavior::NormalFetch)
0350         job->setItems(items);
0351     job->setExpectedError(m_data->storageBehavior().fetchItemsErrorCode(collection.id()));
0352     Utils::JobHandler::install(job, noop);
0353     return job;
0354 }
0355 
0356 Akonadi::ItemFetchJobInterface *AkonadiFakeStorage::fetchItem(Akonadi::Item item, QObject *parent)
0357 {
0358     auto fullItem = m_data->item(findId(item));
0359     fullItem = m_data->reconstructItemDependencies(fullItem);
0360     // Force payload detach
0361     fullItem.setPayloadFromData(fullItem.payloadData());
0362 
0363     auto job = new AkonadiFakeItemFetchJob(parent);
0364     const auto behavior = m_data->storageBehavior().fetchItemBehavior(item.id());
0365     if (behavior == AkonadiFakeStorageBehavior::NormalFetch)
0366         job->setItems(Akonadi::Item::List() << fullItem);
0367     job->setExpectedError(m_data->storageBehavior().fetchItemErrorCode(item.id()));
0368     Utils::JobHandler::install(job, noop);
0369     return job;
0370 }
0371 
0372 Akonadi::Collection::Id AkonadiFakeStorage::findId(const Akonadi::Collection &collection)
0373 {
0374     if (collection.isValid() || collection.remoteId().isEmpty())
0375         return collection.id();
0376 
0377     const auto remoteId = collection.remoteId();
0378     auto collections = m_data->collections();
0379     auto result = std::find_if(collections.constBegin(), collections.constEnd(),
0380                                [remoteId] (const Akonadi::Collection &collection) {
0381                                    return collection.remoteId() == remoteId;
0382                                });
0383     return (result != collections.constEnd()) ? result->id() : collection.id();
0384 }
0385 
0386 Akonadi::Item::Id AkonadiFakeStorage::findId(const Akonadi::Item &item)
0387 {
0388     if (item.isValid() || item.remoteId().isEmpty())
0389         return item.id();
0390 
0391     const auto remoteId = item.remoteId();
0392     auto items = m_data->items();
0393     auto result = std::find_if(items.constBegin(), items.constEnd(),
0394                                [remoteId] (const Akonadi::Item &item) {
0395                                    return item.remoteId() == remoteId;
0396                                });
0397     return (result != items.constEnd()) ? result->id() : item.id();
0398 
0399 }
0400 
0401 Akonadi::Collection::List AkonadiFakeStorage::collectChildren(const Akonadi::Collection &root)
0402 {
0403     auto collections = Akonadi::Collection::List();
0404 
0405     foreach (const auto &child, m_data->childCollections(findId(root))) {
0406         if (child.enabled())
0407             collections << m_data->collection(findId(child));
0408         collections += collectChildren(child);
0409     }
0410 
0411     return collections;
0412 }
0413 
0414 #include "akonadifakestorage.moc"