File indexing completed on 2024-05-26 05:14:17

0001 /*
0002     SPDX-FileCopyrightText: 2009 Constantin Berzan <exit3219@gmail.com>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 #include "specialcollections.h"
0008 #include "akonadicore_debug.h"
0009 #include "specialcollectionattribute.h"
0010 #include "specialcollections_p.h"
0011 
0012 #include "agentinstance.h"
0013 #include "agentmanager.h"
0014 #include "collectionfetchjob.h"
0015 #include "collectionfetchscope.h"
0016 #include "collectionmodifyjob.h"
0017 #include "monitor.h"
0018 
0019 #include <KCoreConfigSkeleton>
0020 
0021 #include <QHash>
0022 
0023 using namespace Akonadi;
0024 
0025 SpecialCollectionsPrivate::SpecialCollectionsPrivate(KCoreConfigSkeleton *settings, SpecialCollections *qq)
0026     : q(qq)
0027     , mSettings(settings)
0028     , mBatchMode(false)
0029 {
0030     mMonitor = new Monitor(q);
0031     mMonitor->setObjectName(QLatin1StringView("SpecialCollectionsMonitor"));
0032     mMonitor->fetchCollectionStatistics(true);
0033 
0034     /// In order to know if items are added or deleted
0035     /// from one of our specialcollection folders,
0036     /// we have to watch all mail item add/move/delete notifications
0037     /// and check for the parent to see if it is one we care about
0038     QObject::connect(mMonitor, &Monitor::collectionRemoved, q, [this](const Akonadi::Collection &col) {
0039         collectionRemoved(col);
0040     });
0041     QObject::connect(mMonitor, &Monitor::collectionStatisticsChanged, q, [this](Akonadi::Collection::Id id, const Akonadi::CollectionStatistics &statistics) {
0042         collectionStatisticsChanged(id, statistics);
0043     });
0044 }
0045 
0046 SpecialCollectionsPrivate::~SpecialCollectionsPrivate()
0047 {
0048 }
0049 
0050 QString SpecialCollectionsPrivate::defaultResourceId() const
0051 {
0052     if (mDefaultResourceId.isEmpty()) {
0053         mSettings->load();
0054         const KConfigSkeletonItem *item = mSettings->findItem(QStringLiteral("DefaultResourceId"));
0055         Q_ASSERT(item);
0056 
0057         mDefaultResourceId = item->property().toString();
0058     }
0059     return mDefaultResourceId;
0060 }
0061 
0062 void SpecialCollectionsPrivate::emitChanged(const QString &resourceId)
0063 {
0064     if (mBatchMode) {
0065         mToEmitChangedFor.insert(resourceId);
0066     } else {
0067         qCDebug(AKONADICORE_LOG) << "Emitting changed for" << resourceId;
0068         const AgentInstance agentInstance = AgentManager::self()->instance(resourceId);
0069         Q_EMIT q->collectionsChanged(agentInstance);
0070         // first compare with local value then with config value (which also updates the local value)
0071         if (resourceId == mDefaultResourceId || resourceId == defaultResourceId()) {
0072             qCDebug(AKONADICORE_LOG) << "Emitting defaultFoldersChanged.";
0073             Q_EMIT q->defaultCollectionsChanged();
0074         }
0075     }
0076 }
0077 
0078 void SpecialCollectionsPrivate::collectionRemoved(const Collection &collection)
0079 {
0080     qCDebug(AKONADICORE_LOG) << "Collection" << collection.id() << "resource" << collection.resource();
0081     if (mFoldersForResource.contains(collection.resource())) {
0082         // Retrieve the list of special folders for the resource the collection belongs to
0083         QHash<QByteArray, Collection> &folders = mFoldersForResource[collection.resource()];
0084         {
0085             QMutableHashIterator<QByteArray, Collection> it(folders);
0086             while (it.hasNext()) {
0087                 it.next();
0088                 if (it.value() == collection) {
0089                     // The collection to be removed is a special folder
0090                     it.remove();
0091                     emitChanged(collection.resource());
0092                 }
0093             }
0094         }
0095 
0096         if (folders.isEmpty()) {
0097             // This resource has no more folders, so remove it completely.
0098             mFoldersForResource.remove(collection.resource());
0099         }
0100     }
0101 }
0102 
0103 void SpecialCollectionsPrivate::collectionStatisticsChanged(Akonadi::Collection::Id collectionId, const Akonadi::CollectionStatistics &statistics)
0104 {
0105     // need to get the name of the collection in order to be able to check if we are storing it,
0106     // but we have the id from the monitor, so fetch the name.
0107     auto fetchJob = new Akonadi::CollectionFetchJob(Collection(collectionId), Akonadi::CollectionFetchJob::Base);
0108     fetchJob->fetchScope().setAncestorRetrieval(Akonadi::CollectionFetchScope::None);
0109     fetchJob->setProperty("statistics", QVariant::fromValue(statistics));
0110 
0111     q->connect(fetchJob, &CollectionFetchJob::result, q, [this](KJob *job) {
0112         collectionFetchJobFinished(job);
0113     });
0114 }
0115 
0116 void SpecialCollectionsPrivate::collectionFetchJobFinished(KJob *job)
0117 {
0118     if (job->error()) {
0119         qCWarning(AKONADICORE_LOG) << "Error fetching collection to get name from id for statistics updating in specialcollections!";
0120         return;
0121     }
0122 
0123     const Akonadi::CollectionFetchJob *fetchJob = qobject_cast<Akonadi::CollectionFetchJob *>(job);
0124 
0125     Q_ASSERT(!fetchJob->collections().empty());
0126     const Akonadi::Collection collection = fetchJob->collections().at(0);
0127     const auto statistics = fetchJob->property("statistics").value<Akonadi::CollectionStatistics>();
0128 
0129     mFoldersForResource[collection.resource()][collection.name().toUtf8()].setStatistics(statistics);
0130 }
0131 
0132 void SpecialCollectionsPrivate::beginBatchRegister()
0133 {
0134     Q_ASSERT(!mBatchMode);
0135     mBatchMode = true;
0136     Q_ASSERT(mToEmitChangedFor.isEmpty());
0137 }
0138 
0139 void SpecialCollectionsPrivate::endBatchRegister()
0140 {
0141     Q_ASSERT(mBatchMode);
0142     mBatchMode = false;
0143 
0144     for (const QString &resourceId : std::as_const(mToEmitChangedFor)) {
0145         emitChanged(resourceId);
0146     }
0147 
0148     mToEmitChangedFor.clear();
0149 }
0150 
0151 void SpecialCollectionsPrivate::forgetFoldersForResource(const QString &resourceId)
0152 {
0153     if (mFoldersForResource.contains(resourceId)) {
0154         const auto folders = mFoldersForResource[resourceId];
0155         for (const auto &collection : folders) {
0156             mMonitor->setCollectionMonitored(collection, false);
0157         }
0158 
0159         mFoldersForResource.remove(resourceId);
0160         emitChanged(resourceId);
0161     }
0162 }
0163 
0164 AgentInstance SpecialCollectionsPrivate::defaultResource() const
0165 {
0166     const QString identifier = defaultResourceId();
0167     return AgentManager::self()->instance(identifier);
0168 }
0169 
0170 SpecialCollections::SpecialCollections(KCoreConfigSkeleton *settings, QObject *parent)
0171     : QObject(parent)
0172     , d(new SpecialCollectionsPrivate(settings, this))
0173 {
0174 }
0175 
0176 SpecialCollections::~SpecialCollections() = default;
0177 
0178 bool SpecialCollections::hasCollection(const QByteArray &type, const AgentInstance &instance) const
0179 {
0180     return d->mFoldersForResource.value(instance.identifier()).contains(type);
0181 }
0182 
0183 Akonadi::Collection SpecialCollections::collection(const QByteArray &type, const AgentInstance &instance) const
0184 {
0185     return d->mFoldersForResource.value(instance.identifier()).value(type);
0186 }
0187 
0188 void SpecialCollections::setSpecialCollectionType(const QByteArray &type, const Akonadi::Collection &collection)
0189 {
0190     if (!collection.hasAttribute<SpecialCollectionAttribute>() || collection.attribute<SpecialCollectionAttribute>()->collectionType() != type) {
0191         Collection attributeCollection(collection);
0192         auto attribute = attributeCollection.attribute<SpecialCollectionAttribute>(Collection::AddIfMissing);
0193         attribute->setCollectionType(type);
0194         new CollectionModifyJob(attributeCollection);
0195     }
0196 }
0197 
0198 void SpecialCollections::unsetSpecialCollection(const Akonadi::Collection &collection)
0199 {
0200     if (collection.hasAttribute<SpecialCollectionAttribute>()) {
0201         Collection attributeCollection(collection);
0202         attributeCollection.removeAttribute<SpecialCollectionAttribute>();
0203         new CollectionModifyJob(attributeCollection);
0204     }
0205 }
0206 
0207 bool SpecialCollections::unregisterCollection(const Collection &collection)
0208 {
0209     if (!collection.isValid()) {
0210         qCWarning(AKONADICORE_LOG) << "Invalid collection.";
0211         return false;
0212     }
0213 
0214     const QString &resourceId = collection.resource();
0215     if (resourceId.isEmpty()) {
0216         qCWarning(AKONADICORE_LOG) << "Collection has empty resourceId.";
0217         return false;
0218     }
0219 
0220     unsetSpecialCollection(collection);
0221 
0222     d->mMonitor->setCollectionMonitored(collection, false);
0223     // Remove from list of collection
0224     d->collectionRemoved(collection);
0225     return true;
0226 }
0227 
0228 bool SpecialCollections::registerCollection(const QByteArray &type, const Collection &collection)
0229 {
0230     if (!collection.isValid()) {
0231         qCWarning(AKONADICORE_LOG) << "Invalid collection.";
0232         return false;
0233     }
0234 
0235     const QString &resourceId = collection.resource();
0236     if (resourceId.isEmpty()) {
0237         qCWarning(AKONADICORE_LOG) << "Collection has empty resourceId.";
0238         return false;
0239     }
0240 
0241     setSpecialCollectionType(type, collection);
0242 
0243     const Collection oldCollection = d->mFoldersForResource.value(resourceId).value(type);
0244     if (oldCollection != collection) {
0245         if (oldCollection.isValid()) {
0246             d->mMonitor->setCollectionMonitored(oldCollection, false);
0247         }
0248         d->mMonitor->setCollectionMonitored(collection, true);
0249         d->mFoldersForResource[resourceId].insert(type, collection);
0250         d->emitChanged(resourceId);
0251     }
0252 
0253     return true;
0254 }
0255 
0256 bool SpecialCollections::hasDefaultCollection(const QByteArray &type) const
0257 {
0258     return hasCollection(type, d->defaultResource());
0259 }
0260 
0261 Akonadi::Collection SpecialCollections::defaultCollection(const QByteArray &type) const
0262 {
0263     return collection(type, d->defaultResource());
0264 }
0265 
0266 #include "moc_specialcollections.cpp"