File indexing completed on 2024-11-10 04:40:28
0001 /* 0002 SPDX-FileCopyrightText: 2006-2007 Volker Krause <vkrause@kde.org> 0003 0004 SPDX-License-Identifier: LGPL-2.0-or-later 0005 */ 0006 0007 #include "collectionfetchjob.h" 0008 0009 #include "collection_p.h" 0010 #include "collectionfetchscope.h" 0011 #include "collectionutils.h" 0012 #include "job_p.h" 0013 #include "private/protocol_p.h" 0014 #include "protocolhelper_p.h" 0015 0016 #include "akonadicore_debug.h" 0017 0018 #include <KLocalizedString> 0019 0020 #include <QHash> 0021 #include <QObject> 0022 #include <QTimer> 0023 0024 using namespace Akonadi; 0025 0026 class Akonadi::CollectionFetchJobPrivate : public JobPrivate 0027 { 0028 public: 0029 explicit CollectionFetchJobPrivate(CollectionFetchJob *parent) 0030 : JobPrivate(parent) 0031 { 0032 mEmitTimer.setSingleShot(true); 0033 mEmitTimer.setInterval(std::chrono::milliseconds{100}); 0034 } 0035 0036 void init() 0037 { 0038 QObject::connect(&mEmitTimer, &QTimer::timeout, q_ptr, [this]() { 0039 timeout(); 0040 }); 0041 } 0042 0043 Q_DECLARE_PUBLIC(CollectionFetchJob) 0044 0045 CollectionFetchJob::Type mType = CollectionFetchJob::Base; 0046 Collection mBase; 0047 Collection::List mBaseList; 0048 Collection::List mCollections; 0049 CollectionFetchScope mScope; 0050 Collection::List mPendingCollections; 0051 QTimer mEmitTimer; 0052 bool mBasePrefetch = false; 0053 Collection::List mPrefetchList; 0054 0055 void aboutToFinish() override 0056 { 0057 timeout(); 0058 } 0059 0060 void timeout() 0061 { 0062 Q_Q(CollectionFetchJob); 0063 0064 mEmitTimer.stop(); // in case we are called by result() 0065 if (!mPendingCollections.isEmpty()) { 0066 if (!q->error() || mScope.ignoreRetrievalErrors()) { 0067 Q_EMIT q->collectionsReceived(mPendingCollections); 0068 } 0069 mPendingCollections.clear(); 0070 } 0071 } 0072 0073 void subJobCollectionReceived(const Akonadi::Collection::List &collections) 0074 { 0075 mPendingCollections += collections; 0076 if (!mEmitTimer.isActive()) { 0077 mEmitTimer.start(); 0078 } 0079 } 0080 0081 QString jobDebuggingString() const override 0082 { 0083 if (mBase.isValid()) { 0084 return QStringLiteral("Collection Id %1").arg(mBase.id()); 0085 } else if (CollectionUtils::hasValidHierarchicalRID(mBase)) { 0086 // return QLatin1StringView("(") + ProtocolHelper::hierarchicalRidToScope(mBase).hridChain().join(QLatin1StringView(", ")) + QLatin1Char(')'); 0087 return QStringLiteral("HRID chain"); 0088 } else { 0089 return QStringLiteral("Collection RemoteId %1").arg(mBase.remoteId()); 0090 } 0091 } 0092 0093 bool jobFailed(KJob *job) 0094 { 0095 Q_Q(CollectionFetchJob); 0096 if (mScope.ignoreRetrievalErrors()) { 0097 int error = job->error(); 0098 if (error && !q->error()) { 0099 q->setError(error); 0100 q->setErrorText(job->errorText()); 0101 } 0102 0103 return error == Job::ConnectionFailed || error == Job::ProtocolVersionMismatch || error == Job::UserCanceled; 0104 } else { 0105 return job->error(); 0106 } 0107 } 0108 }; 0109 0110 CollectionFetchJob::CollectionFetchJob(const Collection &collection, Type type, QObject *parent) 0111 : Job(new CollectionFetchJobPrivate(this), parent) 0112 { 0113 Q_D(CollectionFetchJob); 0114 d->init(); 0115 0116 d->mBase = collection; 0117 d->mType = type; 0118 } 0119 0120 CollectionFetchJob::CollectionFetchJob(const Collection::List &cols, QObject *parent) 0121 : Job(new CollectionFetchJobPrivate(this), parent) 0122 { 0123 Q_D(CollectionFetchJob); 0124 d->init(); 0125 0126 Q_ASSERT(!cols.isEmpty()); 0127 if (cols.size() == 1) { 0128 d->mBase = cols.first(); 0129 } else { 0130 d->mBaseList = cols; 0131 } 0132 d->mType = CollectionFetchJob::Base; 0133 } 0134 0135 CollectionFetchJob::CollectionFetchJob(const Collection::List &cols, Type type, QObject *parent) 0136 : Job(new CollectionFetchJobPrivate(this), parent) 0137 { 0138 Q_D(CollectionFetchJob); 0139 d->init(); 0140 0141 Q_ASSERT(!cols.isEmpty()); 0142 if (cols.size() == 1) { 0143 d->mBase = cols.first(); 0144 } else { 0145 d->mBaseList = cols; 0146 } 0147 d->mType = type; 0148 } 0149 0150 CollectionFetchJob::CollectionFetchJob(const QList<Collection::Id> &cols, Type type, QObject *parent) 0151 : Job(new CollectionFetchJobPrivate(this), parent) 0152 { 0153 Q_D(CollectionFetchJob); 0154 d->init(); 0155 0156 Q_ASSERT(!cols.isEmpty()); 0157 if (cols.size() == 1) { 0158 d->mBase = Collection(cols.first()); 0159 } else { 0160 for (Collection::Id id : cols) { 0161 d->mBaseList.append(Collection(id)); 0162 } 0163 } 0164 d->mType = type; 0165 } 0166 0167 CollectionFetchJob::~CollectionFetchJob() = default; 0168 0169 Akonadi::Collection::List CollectionFetchJob::collections() const 0170 { 0171 Q_D(const CollectionFetchJob); 0172 0173 return d->mCollections; 0174 } 0175 0176 void CollectionFetchJob::doStart() 0177 { 0178 Q_D(CollectionFetchJob); 0179 0180 if (!d->mBaseList.isEmpty()) { 0181 if (d->mType == Recursive) { 0182 // Because doStart starts several subjobs and @p cols could contain descendants of 0183 // other elements in the list, if type is Recursive, we could end up with duplicates in the result. 0184 // To fix this we require an initial fetch of @p cols with Base and RetrieveAncestors, 0185 // Iterate over that result removing intersections and then perform the Recursive fetch on 0186 // the remainder. 0187 d->mBasePrefetch = true; 0188 // No need to connect to the collectionsReceived signal here. This job is internal. The 0189 // result needs to be filtered through filterDescendants before it is useful. 0190 new CollectionFetchJob(d->mBaseList, NonOverlappingRoots, this); 0191 } else if (d->mType == NonOverlappingRoots) { 0192 for (const Collection &col : std::as_const(d->mBaseList)) { 0193 // No need to connect to the collectionsReceived signal here. This job is internal. The (aggregated) 0194 // result needs to be filtered through filterDescendants before it is useful. 0195 auto subJob = new CollectionFetchJob(col, Base, this); 0196 subJob->fetchScope().setAncestorRetrieval(Akonadi::CollectionFetchScope::All); 0197 } 0198 } else { 0199 for (const Collection &col : std::as_const(d->mBaseList)) { 0200 auto subJob = new CollectionFetchJob(col, d->mType, this); 0201 connect(subJob, &CollectionFetchJob::collectionsReceived, this, [d](const auto &cols) { 0202 d->subJobCollectionReceived(cols); 0203 }); 0204 subJob->setFetchScope(fetchScope()); 0205 } 0206 } 0207 return; 0208 } 0209 0210 if (!d->mBase.isValid() && d->mBase.remoteId().isEmpty()) { 0211 setError(Unknown); 0212 setErrorText(i18n("Invalid collection given.")); 0213 emitResult(); 0214 return; 0215 } 0216 0217 const auto cmd = Protocol::FetchCollectionsCommandPtr::create(ProtocolHelper::entityToScope(d->mBase)); 0218 switch (d->mType) { 0219 case Base: 0220 cmd->setDepth(Protocol::FetchCollectionsCommand::BaseCollection); 0221 break; 0222 case Akonadi::CollectionFetchJob::FirstLevel: 0223 cmd->setDepth(Protocol::FetchCollectionsCommand::ParentCollection); 0224 break; 0225 case Akonadi::CollectionFetchJob::Recursive: 0226 cmd->setDepth(Protocol::FetchCollectionsCommand::AllCollections); 0227 break; 0228 default: 0229 Q_ASSERT(false); 0230 } 0231 cmd->setResource(d->mScope.resource()); 0232 cmd->setMimeTypes(d->mScope.contentMimeTypes()); 0233 0234 switch (d->mScope.listFilter()) { 0235 case CollectionFetchScope::Display: 0236 cmd->setDisplayPref(true); 0237 break; 0238 case CollectionFetchScope::Sync: 0239 cmd->setSyncPref(true); 0240 break; 0241 case CollectionFetchScope::Index: 0242 cmd->setIndexPref(true); 0243 break; 0244 case CollectionFetchScope::Enabled: 0245 cmd->setEnabled(true); 0246 break; 0247 case CollectionFetchScope::NoFilter: 0248 break; 0249 default: 0250 Q_ASSERT(false); 0251 } 0252 0253 cmd->setFetchStats(d->mScope.includeStatistics()); 0254 switch (d->mScope.ancestorRetrieval()) { 0255 case CollectionFetchScope::None: 0256 cmd->setAncestorsDepth(Protocol::Ancestor::NoAncestor); 0257 break; 0258 case CollectionFetchScope::Parent: 0259 cmd->setAncestorsDepth(Protocol::Ancestor::ParentAncestor); 0260 break; 0261 case CollectionFetchScope::All: 0262 cmd->setAncestorsDepth(Protocol::Ancestor::AllAncestors); 0263 break; 0264 } 0265 if (d->mScope.ancestorRetrieval() != CollectionFetchScope::None) { 0266 cmd->setAncestorsAttributes(d->mScope.ancestorFetchScope().attributes()); 0267 } 0268 0269 d->sendCommand(cmd); 0270 } 0271 0272 bool CollectionFetchJob::doHandleResponse(qint64 tag, const Protocol::CommandPtr &response) 0273 { 0274 Q_D(CollectionFetchJob); 0275 0276 if (d->mBasePrefetch || d->mType == NonOverlappingRoots) { 0277 return false; 0278 } 0279 0280 if (!response->isResponse() || response->type() != Protocol::Command::FetchCollections) { 0281 return Job::doHandleResponse(tag, response); 0282 } 0283 0284 const auto &resp = Protocol::cmdCast<Protocol::FetchCollectionsResponse>(response); 0285 // Invalid response (no ID) means this was the last response 0286 if (resp.id() == -1) { 0287 return true; 0288 } 0289 0290 Collection collection = ProtocolHelper::parseCollection(resp, true); 0291 if (!collection.isValid()) { 0292 return false; 0293 } 0294 0295 collection.d_ptr->resetChangeLog(); 0296 d->mCollections.append(collection); 0297 d->mPendingCollections.append(collection); 0298 if (!d->mEmitTimer.isActive()) { 0299 d->mEmitTimer.start(); 0300 } 0301 0302 return false; 0303 } 0304 0305 static Collection::List filterDescendants(const Collection::List &list) 0306 { 0307 Collection::List result; 0308 0309 QList<QList<Collection::Id>> ids; 0310 ids.reserve(list.count()); 0311 for (const Collection &collection : list) { 0312 QList<Collection::Id> ancestors; 0313 Collection parent = collection.parentCollection(); 0314 ancestors << parent.id(); 0315 if (parent != Collection::root()) { 0316 while (parent.parentCollection() != Collection::root()) { 0317 parent = parent.parentCollection(); 0318 QList<Collection::Id>::iterator i = std::lower_bound(ancestors.begin(), ancestors.end(), parent.id()); 0319 ancestors.insert(i, parent.id()); 0320 } 0321 } 0322 ids << ancestors; 0323 } 0324 0325 QSet<Collection::Id> excludeList; 0326 for (const Collection &collection : list) { 0327 int i = 0; 0328 for (const QList<Collection::Id> &ancestors : std::as_const(ids)) { 0329 if (std::binary_search(ancestors.cbegin(), ancestors.cend(), collection.id())) { 0330 excludeList.insert(list.at(i).id()); 0331 } 0332 ++i; 0333 } 0334 } 0335 0336 for (const Collection &collection : list) { 0337 if (!excludeList.contains(collection.id())) { 0338 result.append(collection); 0339 } 0340 } 0341 0342 return result; 0343 } 0344 0345 void CollectionFetchJob::slotResult(KJob *job) 0346 { 0347 Q_D(CollectionFetchJob); 0348 0349 auto list = qobject_cast<CollectionFetchJob *>(job); 0350 Q_ASSERT(job); 0351 0352 if (d->mType == NonOverlappingRoots) { 0353 d->mPrefetchList += list->collections(); 0354 } else if (!d->mBasePrefetch) { 0355 d->mCollections += list->collections(); 0356 } 0357 0358 if (d_ptr->mCurrentSubJob == job && !d->jobFailed(job)) { 0359 if (job->error()) { 0360 qCWarning(AKONADICORE_LOG) << "Error during CollectionFetchJob: " << job->errorString(); 0361 } 0362 d_ptr->mCurrentSubJob = nullptr; 0363 removeSubjob(job); 0364 QTimer::singleShot(0, this, [d]() { 0365 d->startNext(); 0366 }); 0367 } else { 0368 Job::slotResult(job); 0369 } 0370 0371 if (d->mBasePrefetch) { 0372 d->mBasePrefetch = false; 0373 const Collection::List roots = list->collections(); 0374 Q_ASSERT(!hasSubjobs()); 0375 if (!job->error()) { 0376 for (const Collection &col : roots) { 0377 auto subJob = new CollectionFetchJob(col, d->mType, this); 0378 connect(subJob, &CollectionFetchJob::collectionsReceived, this, [d](const auto &cols) { 0379 d->subJobCollectionReceived(cols); 0380 }); 0381 subJob->setFetchScope(fetchScope()); 0382 } 0383 } 0384 // No result yet. 0385 } else if (d->mType == NonOverlappingRoots) { 0386 if (!d->jobFailed(job) && !hasSubjobs()) { 0387 const Collection::List result = filterDescendants(d->mPrefetchList); 0388 d->mPendingCollections += result; 0389 d->mCollections = result; 0390 d->delayedEmitResult(); 0391 } 0392 } else { 0393 if (!d->jobFailed(job) && !hasSubjobs()) { 0394 d->delayedEmitResult(); 0395 } 0396 } 0397 } 0398 0399 void CollectionFetchJob::setFetchScope(const CollectionFetchScope &scope) 0400 { 0401 Q_D(CollectionFetchJob); 0402 d->mScope = scope; 0403 } 0404 0405 CollectionFetchScope &CollectionFetchJob::fetchScope() 0406 { 0407 Q_D(CollectionFetchJob); 0408 return d->mScope; 0409 } 0410 0411 #include "moc_collectionfetchjob.cpp"