File indexing completed on 2024-11-10 04:40:32

0001 /*
0002     SPDX-FileCopyrightText: 2009 Constantin Berzan <exit3219@gmail.com>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 #include "specialcollectionsrequestjob.h"
0008 
0009 #include "specialcollectionattribute.h"
0010 #include "specialcollections_p.h"
0011 #include "specialcollectionshelperjobs_p.h"
0012 
0013 #include "agentmanager.h"
0014 #include "collectioncreatejob.h"
0015 
0016 #include "akonadicore_debug.h"
0017 
0018 using namespace Akonadi;
0019 
0020 /**
0021   @internal
0022 */
0023 class Akonadi::SpecialCollectionsRequestJobPrivate
0024 {
0025 public:
0026     SpecialCollectionsRequestJobPrivate(SpecialCollections *collections, SpecialCollectionsRequestJob *qq);
0027 
0028     bool isEverythingReady() const;
0029     void lockResult(KJob *job); // slot
0030     void releaseLock(); // slot
0031     void nextResource();
0032     void resourceScanResult(KJob *job); // slot
0033     void createRequestedFolders(ResourceScanJob *job, QHash<QByteArray, bool> &requestedFolders);
0034     void collectionCreateResult(KJob *job); // slot
0035 
0036     SpecialCollectionsRequestJob *const q;
0037     SpecialCollections *mSpecialCollections = nullptr;
0038     int mPendingCreateJobs = 0;
0039 
0040     QByteArray mRequestedType;
0041     AgentInstance mRequestedResource;
0042 
0043     // Input:
0044     QHash<QByteArray, bool> mDefaultFolders;
0045     bool mRequestingDefaultFolders = false;
0046     QHash<QString, QHash<QByteArray, bool>> mFoldersForResource;
0047     QString mDefaultResourceType;
0048     QVariantMap mDefaultResourceOptions;
0049     QList<QByteArray> mKnownTypes;
0050     QMap<QByteArray, QString> mNameForTypeMap;
0051     QMap<QByteArray, QString> mIconForTypeMap;
0052 
0053     // Output:
0054     QStringList mToForget;
0055     QList<QPair<Collection, QByteArray>> mToRegister;
0056 };
0057 
0058 SpecialCollectionsRequestJobPrivate::SpecialCollectionsRequestJobPrivate(SpecialCollections *collections, SpecialCollectionsRequestJob *qq)
0059     : q(qq)
0060     , mSpecialCollections(collections)
0061 {
0062 }
0063 
0064 bool SpecialCollectionsRequestJobPrivate::isEverythingReady() const
0065 {
0066     // check if all requested folders are known already
0067     if (mRequestingDefaultFolders) {
0068         for (auto it = mDefaultFolders.cbegin(), end = mDefaultFolders.cend(); it != end; ++it) {
0069             if (it.value() && !mSpecialCollections->hasDefaultCollection(it.key())) {
0070                 return false;
0071             }
0072         }
0073     }
0074 
0075     for (auto resourceIt = mFoldersForResource.cbegin(), end = mFoldersForResource.cend(); resourceIt != end; ++resourceIt) {
0076         const QHash<QByteArray, bool> &requested = resourceIt.value();
0077         for (auto it = requested.cbegin(), end = requested.cend(); it != end; ++it) {
0078             if (it.value() && !mSpecialCollections->hasCollection(it.key(), AgentManager::self()->instance(resourceIt.key()))) {
0079                 return false;
0080             }
0081         }
0082     }
0083 
0084     return true;
0085 }
0086 
0087 void SpecialCollectionsRequestJobPrivate::lockResult(KJob *job)
0088 {
0089     if (job->error()) {
0090         qCWarning(AKONADICORE_LOG) << "Failed to get lock:" << job->errorString();
0091         q->setError(job->error());
0092         q->setErrorText(job->errorString());
0093         q->emitResult();
0094         return;
0095     }
0096 
0097     if (mRequestingDefaultFolders) {
0098         // If default folders are requested, deal with that first.
0099         auto resjob = new DefaultResourceJob(mSpecialCollections->d->mSettings, q);
0100         resjob->setDefaultResourceType(mDefaultResourceType);
0101         resjob->setDefaultResourceOptions(mDefaultResourceOptions);
0102         resjob->setTypes(mKnownTypes);
0103         resjob->setNameForTypeMap(mNameForTypeMap);
0104         resjob->setIconForTypeMap(mIconForTypeMap);
0105         QObject::connect(resjob, &KJob::result, q, [this](KJob *job) {
0106             resourceScanResult(job);
0107         });
0108     } else {
0109         // If no default folders are requested, go straight to the next step.
0110         nextResource();
0111     }
0112 }
0113 
0114 void SpecialCollectionsRequestJobPrivate::releaseLock()
0115 {
0116     const bool ok = Akonadi::releaseLock();
0117     if (!ok) {
0118         qCWarning(AKONADICORE_LOG) << "WTF, can't release lock.";
0119     }
0120 }
0121 
0122 void SpecialCollectionsRequestJobPrivate::nextResource()
0123 {
0124     if (mFoldersForResource.isEmpty()) {
0125         qCDebug(AKONADICORE_LOG) << "All done! Committing.";
0126 
0127         mSpecialCollections->d->beginBatchRegister();
0128 
0129         // Forget everything we knew before about these resources.
0130         for (const QString &resourceId : std::as_const(mToForget)) {
0131             mSpecialCollections->d->forgetFoldersForResource(resourceId);
0132         }
0133 
0134         // Register all the collections that we fetched / created.
0135         using RegisterPair = QPair<Collection, QByteArray>;
0136         for (const RegisterPair &pair : std::as_const(mToRegister)) {
0137             const bool ok = mSpecialCollections->registerCollection(pair.second, pair.first);
0138             Q_ASSERT(ok);
0139             Q_UNUSED(ok)
0140         }
0141 
0142         mSpecialCollections->d->endBatchRegister();
0143 
0144         // Release the lock once the transaction has been committed.
0145         QObject::connect(q, &KJob::result, q, [this]() {
0146             releaseLock();
0147         });
0148 
0149         // We are done!
0150         q->commit();
0151 
0152     } else {
0153         const QString resourceId = mFoldersForResource.cbegin().key();
0154         qCDebug(AKONADICORE_LOG) << "A resource is done," << mFoldersForResource.count() << "more to do. Now doing resource" << resourceId;
0155         auto resjob = new ResourceScanJob(resourceId, mSpecialCollections->d->mSettings, q);
0156         QObject::connect(resjob, &KJob::result, q, [this](KJob *job) {
0157             resourceScanResult(job);
0158         });
0159     }
0160 }
0161 
0162 void SpecialCollectionsRequestJobPrivate::resourceScanResult(KJob *job)
0163 {
0164     auto resjob = qobject_cast<ResourceScanJob *>(job);
0165     Q_ASSERT(resjob);
0166 
0167     const QString resourceId = resjob->resourceId();
0168     qCDebug(AKONADICORE_LOG) << "resourceId" << resourceId;
0169 
0170     if (job->error()) {
0171         qCWarning(AKONADICORE_LOG) << "Failed to request resource" << resourceId << ":" << job->errorString();
0172         return;
0173     }
0174 
0175     if (qobject_cast<DefaultResourceJob *>(job)) {
0176         // This is the default resource.
0177         if (resourceId != mSpecialCollections->d->defaultResourceId()) {
0178             qCWarning(AKONADICORE_LOG) << "Resource id's don't match: " << resourceId << mSpecialCollections->d->defaultResourceId();
0179             Q_ASSERT(false);
0180         }
0181         // mToForget.append( mSpecialCollections->defaultResourceId() );
0182         createRequestedFolders(resjob, mDefaultFolders);
0183     } else {
0184         // This is not the default resource.
0185         QHash<QByteArray, bool> requestedFolders = mFoldersForResource[resourceId];
0186         mFoldersForResource.remove(resourceId);
0187         createRequestedFolders(resjob, requestedFolders);
0188     }
0189 }
0190 
0191 void SpecialCollectionsRequestJobPrivate::createRequestedFolders(ResourceScanJob *scanJob, QHash<QByteArray, bool> &requestedFolders)
0192 {
0193     // Remove from the request list the folders which already exist.
0194     const Akonadi::Collection::List lstSpecialCols = scanJob->specialCollections();
0195     for (const Collection &collection : lstSpecialCols) {
0196         Q_ASSERT(collection.hasAttribute<SpecialCollectionAttribute>());
0197         const auto attr = collection.attribute<SpecialCollectionAttribute>();
0198         const QByteArray type = attr->collectionType();
0199 
0200         if (!type.isEmpty()) {
0201             mToRegister.append(qMakePair(collection, type));
0202             requestedFolders.insert(type, false);
0203         }
0204     }
0205     mToForget.append(scanJob->resourceId());
0206 
0207     // Folders left in the request list must be created.
0208     Q_ASSERT(mPendingCreateJobs == 0);
0209     Q_ASSERT(scanJob->rootResourceCollection().isValid());
0210 
0211     QHashIterator<QByteArray, bool> it(requestedFolders);
0212     while (it.hasNext()) {
0213         it.next();
0214 
0215         if (it.value()) {
0216             Collection collection;
0217             collection.setParentCollection(scanJob->rootResourceCollection());
0218             collection.setName(mNameForTypeMap.value(it.key()));
0219 
0220             setCollectionAttributes(collection, it.key(), mNameForTypeMap, mIconForTypeMap);
0221 
0222             auto createJob = new CollectionCreateJob(collection, q);
0223             createJob->setProperty("type", it.key());
0224             QObject::connect(createJob, &KJob::result, q, [this](KJob *job) {
0225                 collectionCreateResult(job);
0226             });
0227 
0228             mPendingCreateJobs++;
0229         }
0230     }
0231 
0232     if (mPendingCreateJobs == 0) {
0233         nextResource();
0234     }
0235 }
0236 
0237 void SpecialCollectionsRequestJobPrivate::collectionCreateResult(KJob *job)
0238 {
0239     if (job->error()) {
0240         qCWarning(AKONADICORE_LOG) << "Failed CollectionCreateJob." << job->errorString();
0241         return;
0242     }
0243 
0244     auto createJob = qobject_cast<CollectionCreateJob *>(job);
0245     Q_ASSERT(createJob);
0246 
0247     const Collection collection = createJob->collection();
0248     mToRegister.append(qMakePair(collection, createJob->property("type").toByteArray()));
0249 
0250     Q_ASSERT(mPendingCreateJobs > 0);
0251     mPendingCreateJobs--;
0252     qCDebug(AKONADICORE_LOG) << "mPendingCreateJobs now" << mPendingCreateJobs;
0253 
0254     if (mPendingCreateJobs == 0) {
0255         nextResource();
0256     }
0257 }
0258 
0259 // TODO KDE5: do not inherit from TransactionSequence
0260 SpecialCollectionsRequestJob::SpecialCollectionsRequestJob(SpecialCollections *collections, QObject *parent)
0261     : TransactionSequence(parent)
0262     , d(new SpecialCollectionsRequestJobPrivate(collections, this))
0263 {
0264     setProperty("transactionsDisabled", true);
0265 }
0266 
0267 SpecialCollectionsRequestJob::~SpecialCollectionsRequestJob() = default;
0268 
0269 void SpecialCollectionsRequestJob::requestDefaultCollection(const QByteArray &type)
0270 {
0271     d->mDefaultFolders[type] = true;
0272     d->mRequestingDefaultFolders = true;
0273     d->mRequestedType = type;
0274 }
0275 
0276 void SpecialCollectionsRequestJob::requestCollection(const QByteArray &type, const AgentInstance &instance)
0277 {
0278     d->mFoldersForResource[instance.identifier()][type] = true;
0279     d->mRequestedType = type;
0280     d->mRequestedResource = instance;
0281 }
0282 
0283 Akonadi::Collection SpecialCollectionsRequestJob::collection() const
0284 {
0285     if (d->mRequestedResource.isValid()) {
0286         return d->mSpecialCollections->collection(d->mRequestedType, d->mRequestedResource);
0287     } else {
0288         return d->mSpecialCollections->defaultCollection(d->mRequestedType);
0289     }
0290 }
0291 
0292 void SpecialCollectionsRequestJob::setDefaultResourceType(const QString &type)
0293 {
0294     d->mDefaultResourceType = type;
0295 }
0296 
0297 void SpecialCollectionsRequestJob::setDefaultResourceOptions(const QVariantMap &options)
0298 {
0299     d->mDefaultResourceOptions = options;
0300 }
0301 
0302 void SpecialCollectionsRequestJob::setTypes(const QList<QByteArray> &types)
0303 {
0304     d->mKnownTypes = types;
0305 }
0306 
0307 void SpecialCollectionsRequestJob::setNameForTypeMap(const QMap<QByteArray, QString> &map)
0308 {
0309     d->mNameForTypeMap = map;
0310 }
0311 
0312 void SpecialCollectionsRequestJob::setIconForTypeMap(const QMap<QByteArray, QString> &map)
0313 {
0314     d->mIconForTypeMap = map;
0315 }
0316 
0317 void SpecialCollectionsRequestJob::doStart()
0318 {
0319     if (d->isEverythingReady()) {
0320         emitResult();
0321     } else {
0322         auto lockJob = new GetLockJob(this);
0323         connect(lockJob, &GetLockJob::result, this, [this](KJob *job) {
0324             d->lockResult(job);
0325         });
0326         lockJob->start();
0327     }
0328 }
0329 
0330 void SpecialCollectionsRequestJob::slotResult(KJob *job)
0331 {
0332     if (job->error()) {
0333         // If we failed, let others try.
0334         qCWarning(AKONADICORE_LOG) << "Failed SpecialCollectionsRequestJob::slotResult" << job->errorString();
0335 
0336         d->releaseLock();
0337     }
0338     TransactionSequence::slotResult(job);
0339 }
0340 
0341 #include "moc_specialcollectionsrequestjob.cpp"