File indexing completed on 2024-12-22 04:55:36

0001 /*
0002  *  akonadicollectionsearch.cpp  -  Search Akonadi Collections
0003  *  Program:  kalarm
0004  *  SPDX-FileCopyrightText: 2014-2022 David Jarvie <djarvie@kde.org>
0005  *
0006  *  SPDX-License-Identifier: GPL-2.0-or-later
0007  */
0008 
0009 #include "akonadicollectionsearch.h"
0010 
0011 #include "akonadiplugin_debug.h"
0012 
0013 #include <Akonadi/AgentInstance>
0014 #include <Akonadi/AgentManager>
0015 #include <Akonadi/CollectionFetchJob>
0016 #include <Akonadi/CollectionFetchScope>
0017 #include <Akonadi/ItemDeleteJob>
0018 #include <Akonadi/ItemFetchJob>
0019 #include <Akonadi/ItemFetchScope>
0020 #include <KCalendarCore/Event>
0021 using namespace KCalendarCore;
0022 
0023 #include <QTimer>
0024 
0025 using namespace Akonadi;
0026 
0027 /******************************************************************************
0028 * Constructor.
0029 * Creates jobs to fetch all collections for resources containing the mime type.
0030 * Its subsequent actions depend on the parameters:
0031 * - If 'remove' is true, it will locate all Items with the specified 'gid' and
0032 *   delete them. The deleted() signal will be emitted.
0033 * - Otherwise, if 'gid' is specified, it will Q_EMIT the signal items() to
0034 *   notify all Items with that GID.
0035 * - Otherwise, it will Q_EMIT the signal collections() to notify all Collections.
0036 */
0037 AkonadiCollectionSearch::AkonadiCollectionSearch(const QString& mimeType, const QString& gid, const QString& uid, bool remove)
0038     : mMimeType(mimeType)
0039     , mGid(gid)
0040     , mUid(uid)
0041     , mDelete(remove && (!mGid.isEmpty() || !mUid.isEmpty()))
0042 {
0043     const AgentInstance::List agents = AgentManager::self()->instances();
0044     for (const AgentInstance& agent : agents)
0045     {
0046         if (agent.type().mimeTypes().contains(mimeType))
0047         {
0048             CollectionFetchJob* job = new CollectionFetchJob(Collection::root(), CollectionFetchJob::Recursive);
0049             job->fetchScope().setResource(agent.identifier());
0050             mCollectionJobs << job;
0051             connect(job, &CollectionFetchJob::result, this, &AkonadiCollectionSearch::collectionFetchResult);
0052         }
0053     }
0054 
0055     if (mCollectionJobs.isEmpty())
0056     {
0057         // There are no resources containing the mime type, so ensure that a
0058         // signal is emitted after construction.
0059         QTimer::singleShot(0, this, &AkonadiCollectionSearch::finish);   //NOLINT(clang-analyzer-cplusplus.NewDeleteLeaks)
0060     }
0061 }
0062 
0063 /******************************************************************************
0064 * Called when a CollectionFetchJob has completed.
0065 */
0066 void AkonadiCollectionSearch::collectionFetchResult(KJob* j)
0067 {
0068     auto job = qobject_cast<CollectionFetchJob*>(j);
0069     if (j->error())
0070         qCCritical(AKONADIPLUGIN_LOG) << "AkonadiCollectionSearch::collectionFetchResult: CollectionFetchJob" << job->fetchScope().resource()<< "error: " << j->errorString();
0071     else
0072     {
0073         const Collection::List collections = job->collections();
0074         for (const Collection& c : collections)
0075         {
0076             if (c.contentMimeTypes().contains(mMimeType))
0077             {
0078                 ItemFetchJob* ijob;
0079                 if (!mGid.isEmpty())
0080                 {
0081                     // Search for all Items with the specified GID
0082                     Item item;
0083                     item.setGid(mGid);
0084                     ijob = new ItemFetchJob(item, this);
0085                     ijob->setCollection(c);
0086                 }
0087                 else if (!mUid.isEmpty())
0088                 {
0089                     // Search for all Events with the specified UID
0090                     ijob = new ItemFetchJob(c, this);
0091                     ijob->fetchScope().fetchFullPayload(true);
0092                 }
0093                 else
0094                 {
0095                     mCollections << c;
0096                     continue;
0097                 }
0098                 mItemFetchJobs[ijob] = c.id();
0099                 connect(ijob, &ItemFetchJob::result, this, &AkonadiCollectionSearch::itemFetchResult);
0100             }
0101         }
0102     }
0103     mCollectionJobs.removeAll(job);
0104 
0105     if (mCollectionJobs.isEmpty())
0106     {
0107         // All collections have now been fetched
0108         if (mGid.isEmpty() && mUid.isEmpty())
0109             finish();
0110     }
0111 }
0112 
0113 /******************************************************************************
0114 * Called when an ItemFetchJob has completed.
0115 */
0116 void AkonadiCollectionSearch::itemFetchResult(KJob* j)
0117 {
0118     auto job = qobject_cast<ItemFetchJob*>(j);
0119     if (j->error())
0120     {
0121         if (!mUid.isEmpty())
0122             qCDebug(AKONADIPLUGIN_LOG) << "AkonadiCollectionSearch::itemFetchResult: ItemFetchJob: collection" << mItemFetchJobs[job] << "UID" << mUid << "error: " << j->errorString();
0123         else
0124             qCDebug(AKONADIPLUGIN_LOG) << "AkonadiCollectionSearch::itemFetchResult: ItemFetchJob: collection" << mItemFetchJobs[job] << "GID" << mGid << "error: " << j->errorString();
0125     }
0126     else
0127     {
0128         if (mDelete)
0129         {
0130             const Item::List items = job->items();
0131             for (const Item& item : items)
0132             {
0133                 if (!mUid.isEmpty())
0134                 {
0135                     if (item.mimeType() == mMimeType  &&  item.hasPayload<KCalendarCore::Event::Ptr>())
0136                     {
0137                         const KCalendarCore::Event::Ptr kcalEvent = item.payload<KCalendarCore::Event::Ptr>();
0138                         if (kcalEvent->uid() != mUid)
0139                             continue;
0140                     }
0141                 }
0142                 else if (mGid.isEmpty())
0143                     continue;
0144                 auto djob = new ItemDeleteJob(item, this);
0145                 mItemDeleteJobs[djob] = mItemFetchJobs.value(job);
0146                 connect(djob, &ItemDeleteJob::result, this, &AkonadiCollectionSearch::itemDeleteResult);
0147             }
0148         }
0149         else
0150             mItems << job->items();
0151     }
0152     mItemFetchJobs.remove(job);
0153 
0154     if (mItemFetchJobs.isEmpty()  &&  mItemDeleteJobs.isEmpty()  &&  mCollectionJobs.isEmpty())
0155         finish();  // all Items have now been fetched or deleted, so notify the result
0156 }
0157 
0158 /******************************************************************************
0159 * Called when an ItemDeleteJob has completed.
0160 */
0161 void AkonadiCollectionSearch::itemDeleteResult(KJob* j)
0162 {
0163     auto job = static_cast<ItemDeleteJob*>(j);
0164     if (j->error())
0165     {
0166         if (!mUid.isEmpty())
0167             qCDebug(AKONADIPLUGIN_LOG) << "AkonadiCollectionSearch::itemDeleteResult: ItemDeleteJob: resource" << mItemDeleteJobs[job] << "UID" << mUid << "error: " << j->errorString();
0168         else
0169             qCDebug(AKONADIPLUGIN_LOG) << "AkonadiCollectionSearch::itemDeleteResult: ItemDeleteJob: resource" << mItemDeleteJobs[job] << "GID" << mGid << "error: " << j->errorString();
0170     }
0171     else
0172         ++mDeleteCount;
0173     mItemDeleteJobs.remove(job);
0174 
0175     if (mItemFetchJobs.isEmpty()  &&  mItemDeleteJobs.isEmpty()  &&  mCollectionJobs.isEmpty())
0176         finish();  // all Items have now been deleted, so notify the result
0177 }
0178 
0179 /******************************************************************************
0180 * Notify the result of the search/delete operation, and delete this instance.
0181 */
0182 void AkonadiCollectionSearch::finish()
0183 {
0184     if (mDelete)
0185         Q_EMIT deleted(mDeleteCount);
0186     else if (mGid.isEmpty() && mUid.isEmpty())
0187         Q_EMIT collections(mCollections);
0188     else
0189         Q_EMIT items(mItems);
0190     deleteLater();
0191 }
0192 
0193 #include "moc_akonadicollectionsearch.cpp"
0194 
0195 // vim: et sw=4: