7#include "collectionfetchhandler.h"
8#include "akonadiserver_debug.h"
10#include "connection.h"
11#include "handlerhelper.h"
12#include "storage/collectionqueryhelper.h"
13#include "storage/datastore.h"
14#include "storage/selectquerybuilder.h"
16#include "private/scope_p.h"
19using namespace Akonadi::Server;
24 for (
const T &e2 : l2) {
32[[nodiscard]]
static bool isRootCollection(
const Scope &scope)
34 return scope.isEmpty() || (scope.scope() == Scope::Uid && scope.uidSet().size() == 1 && scope.uid() == 0);
37CollectionFetchHandler::CollectionFetchHandler(AkonadiServer &akonadi)
44 if (mAncestorDepth <= 0) {
49 for (
int i = 0; i < mAncestorDepth; ++i) {
50 if (parent.parentId() == 0) {
53 if (mAncestors.contains(parent.parentId())) {
54 parent = mAncestors.value(parent.parentId());
56 parent = mCollections.value(parent.parentId());
58 if (!parent.isValid()) {
59 qCWarning(AKONADISERVER_LOG) <<
"Found an invalid parent in ancestors of Collection" << col.name() <<
"(ID:" << col.id() <<
")";
60 throw HandlerException(
"Found invalid parent in ancestors");
69 CollectionAttribute::List attributes;
70 auto it = mCollectionAttributes.
find(col.id());
71 while (it != mCollectionAttributes.
end() && it.key() == col.id()) {
72 if (
filter.isEmpty() ||
filter.contains(it.value().type())) {
73 attributes << it.value();
79 CollectionAttribute attr;
80 attr.setType(AKONADI_PARAM_ENABLED);
81 attr.setValue(col.enabled() ?
"TRUE" :
"FALSE");
88void CollectionFetchHandler::listCollection(
const Collection &root,
91 const CollectionAttribute::List &attributes)
95 if (!mAncestorAttributes.
isEmpty()) {
98 ancestorAttributes.
push(getAttributes(col, mAncestorAttributes));
113 orCondition.addValueCondition(column, Query::Equals,
static_cast<int>(Collection::True));
115 andCondition.addValueCondition(column, Query::Equals,
static_cast<int>(Collection::Undefined));
116 andCondition.addValueCondition(Collection::enabledFullColumnName(), Query::Equals,
true);
117 orCondition.addCondition(andCondition);
121bool CollectionFetchHandler::checkFilterCondition(
const Collection &col)
const
124 if (mEnabledCollections && !col.enabled()) {
128 if (mCollectionsToDisplay && (((col.displayPref() == Collection::Undefined) && !col.enabled()) || (col.displayPref() == Collection::False))) {
131 if (mCollectionsToIndex && (((col.indexPref() == Collection::Undefined) && !col.enabled()) || (col.indexPref() == Collection::False))) {
135 if (mCollectionsToSynchronize && (((col.syncPref() == Collection::Undefined) && !col.enabled()) || (col.syncPref() == Collection::False))) {
145 qb.addValueCondition(CollectionAttribute::collectionIdFullColumnName(), Query::In, ids);
147 qb.addColumn(CollectionAttribute::collectionIdFullColumnName());
148 qb.addColumn(CollectionAttribute::typeFullColumnName());
149 qb.addColumn(CollectionAttribute::valueFullColumnName());
151 if (!requestedAttributes.
isEmpty()) {
152 QVariantList attributes;
153 attributes.reserve(requestedAttributes.
size());
154 for (
const QByteArray &type : requestedAttributes) {
157 qb.addValueCondition(CollectionAttribute::typeFullColumnName(), Query::In, attributes);
160 qb.addSortColumn(CollectionAttribute::collectionIdFullColumnName(), Query::Ascending);
163 throw HandlerException(
"Unable to retrieve attributes for listing");
168void CollectionFetchHandler::retrieveAttributes(
const QVariantList &collectionIds)
172 const int size = 999;
173 while (
start < collectionIds.size()) {
174 const QVariantList ids = collectionIds.mid(
start, size);
175 auto attributeQb = getAttributeQuery(ids, mAncestorAttributes);
176 auto &attributeQuery = attributeQb.query();
177 while (attributeQuery.next()) {
178 CollectionAttribute attr;
179 attr.setType(attributeQuery.value(1).toByteArray());
180 attr.setValue(attributeQuery.value(2).toByteArray());
182 mCollectionAttributes.
insert(attributeQuery.value(0).toLongLong(), attr);
188static QueryBuilder getMimeTypeQuery(
const QVariantList &ids)
190 QueryBuilder qb(CollectionMimeTypeRelation::tableName());
192 qb.addJoin(
QueryBuilder::LeftJoin, MimeType::tableName(), MimeType::idFullColumnName(), CollectionMimeTypeRelation::rightFullColumnName());
193 qb.addValueCondition(CollectionMimeTypeRelation::leftFullColumnName(), Query::In, ids);
195 qb.addColumn(CollectionMimeTypeRelation::leftFullColumnName());
196 qb.addColumn(CollectionMimeTypeRelation::rightFullColumnName());
197 qb.addColumn(MimeType::nameFullColumnName());
198 qb.addSortColumn(CollectionMimeTypeRelation::leftFullColumnName(), Query::Ascending);
201 throw HandlerException(
"Unable to retrieve mimetypes for listing");
206void CollectionFetchHandler::retrieveCollections(
const Collection &topParent,
int depth)
221 const qint64 parentId = topParent.isValid() ? topParent.id() : 0;
227 }
else if (depth == 1) {
228 if (topParent.isValid()) {
229 qb.
addValueCondition(Collection::parentIdFullColumnName(), Query::Equals, parentId);
234 if (topParent.isValid()) {
235 qb.
addValueCondition(Collection::resourceIdFullColumnName(), Query::Equals, topParent.resourceId());
243 if (mCollectionsToSynchronize) {
244 qb.
addCondition(filterCondition(Collection::syncPrefFullColumnName()));
245 }
else if (mCollectionsToDisplay) {
246 qb.
addCondition(filterCondition(Collection::displayPrefFullColumnName()));
247 }
else if (mCollectionsToIndex) {
248 qb.
addCondition(filterCondition(Collection::indexPrefFullColumnName()));
249 }
else if (mEnabledCollections) {
252 if (mResource.isValid()) {
253 qb.
addValueCondition(Collection::resourceIdFullColumnName(), Query::Equals, mResource.id());
258 CollectionMimeTypeRelation::tableName(),
259 CollectionMimeTypeRelation::leftColumn(),
260 Collection::idFullColumnName());
261 QVariantList mimeTypeFilter;
262 mimeTypeFilter.reserve(mMimeTypes.
size());
263 for (MimeType::Id mtId : std::as_const(mMimeTypes)) {
264 mimeTypeFilter << mtId;
266 qb.
addValueCondition(CollectionMimeTypeRelation::rightColumn(), Query::In, mimeTypeFilter);
272 throw HandlerException(
"Unable to retrieve collection for listing");
274 const auto result{qb.
result()};
276 mCollections.insert(col.id(), col);
282 auto it = mCollections.begin();
283 while (it != mCollections.end()) {
284 if (topParent.isValid()) {
286 bool foundParent =
false;
290 if (
id == parentId) {
295 if (!col.isValid()) {
296 col = Collection::retrieveById(
id);
301 it = mCollections.erase(it);
309 QVariantList mimeTypeIds;
310 QVariantList attributeIds;
311 QVariantList ancestorIds;
312 const auto collectionSize{mCollections.size()};
313 mimeTypeIds.reserve(collectionSize);
314 attributeIds.reserve(collectionSize);
316 ancestorIds.reserve(collectionSize);
317 for (
auto it = mCollections.cbegin(), end = mCollections.cend(); it != end; ++it) {
318 mimeTypeIds << it.key();
319 attributeIds << it.key();
320 ancestorIds << it.key();
323 if (mAncestorDepth > 0 && topParent.isValid()) {
325 mAncestors.insert(topParent.id(), topParent);
326 ancestorIds << topParent.id();
329 for (
int i = 0; i < mAncestorDepth; ++i) {
330 if (parent.parentId() == 0) {
333 parent = parent.parent();
334 mAncestors.insert(parent.id(), parent);
336 ancestorIds << parent.id();
342 for (
const Collection &col : std::as_const(mCollections)) {
343 if (col.parentId() != parentId && !mCollections.contains(col.parentId())) {
344 missingCollections.
insert(col.parentId());
359 while (!missingCollections.
isEmpty()) {
362 ids.reserve(missingCollections.
size());
363 for (qint64
id : std::as_const(missingCollections)) {
368 throw HandlerException(
"Unable to retrieve collections for listing");
371 missingCollections.
clear();
372 const auto missingCols = qb.
result();
373 for (
const Collection &missingCol : missingCols) {
374 mCollections.insert(missingCol.id(), missingCol);
375 ancestorIds << missingCol.id();
376 attributeIds << missingCol.id();
377 mimeTypeIds << missingCol.id();
379 if (missingCol.parentId() != parentId && !mCollections.contains(missingCol.parentId())) {
380 missingCollections.
insert(missingCol.parentId());
387 if (!mAncestorAttributes.
isEmpty()) {
388 retrieveAttributes(ancestorIds);
392 const int querySizeLimit = 999;
393 int mimetypeQueryStart = 0;
394 int attributeQueryStart = 0;
395 std::optional<QueryBuilder> mimeTypeQb;
396 std::optional<QueryBuilder> attributeQb;
397 auto it = mCollections.begin();
398 while (it != mCollections.end()) {
404 if (!mimeTypeQb && mimetypeQueryStart < mimeTypeIds.size()) {
405 const QVariantList ids = mimeTypeIds.mid(mimetypeQueryStart, querySizeLimit);
406 mimetypeQueryStart += querySizeLimit;
407 mimeTypeQb = getMimeTypeQuery(ids);
408 mimeTypeQb->query().next();
411 while (mimeTypeQb && mimeTypeQb->query().isValid() && mimeTypeQb->query().value(0).toLongLong() < col.id()) {
412 if (!mimeTypeQb->query().next()) {
417 while (mimeTypeQb && mimeTypeQb->query().isValid() && mimeTypeQb->query().value(0).toLongLong() == col.id()) {
418 mimeTypes << mimeTypeQb->query().
value(2).toString();
419 if (!mimeTypeQb->query().next()) {
425 CollectionAttribute::List attributes;
428 if (!attributeQb && attributeQueryStart < attributeIds.size()) {
429 const QVariantList ids = attributeIds.mid(attributeQueryStart, querySizeLimit);
430 attributeQueryStart += querySizeLimit;
432 attributeQb->query().next();
435 while (attributeQb && attributeQb->query().isValid() && attributeQb->query().value(0).toLongLong() < col.id()) {
436 if (!attributeQb->query().next()) {
441 while (attributeQb && attributeQb->query().isValid() && attributeQb->query().value(0).toLongLong() == col.id()) {
442 auto &attributeQuery = attributeQb->query();
443 CollectionAttribute attr;
444 attr.setType(attributeQuery.value(1).toByteArray());
445 attr.setValue(attributeQuery.value(2).toByteArray());
448 if (!attributeQuery.next()) {
454 listCollection(col, ancestorsForCollection(col), mimeTypes, attributes);
461 const auto &cmd = Protocol::cmdCast<Protocol::FetchCollectionsCommand>(m_command);
463 if (!cmd.resource().isEmpty()) {
464 mResource = Resource::retrieveByName(cmd.resource());
465 if (!mResource.isValid()) {
466 return failureResponse(QStringLiteral(
"Unknown resource %1").arg(cmd.resource()));
470 for (
const QString &mtName : lstMimeTypes) {
471 const MimeType mt = MimeType::retrieveByNameOrCreate(mtName);
473 return failureResponse(
"Failed to create mimetype record");
475 mMimeTypes.
append(mt.id());
478 mEnabledCollections = cmd.enabled();
479 mCollectionsToSynchronize = cmd.syncPref();
480 mCollectionsToDisplay = cmd.displayPref();
481 mCollectionsToIndex = cmd.indexPref();
482 mIncludeStatistics = cmd.fetchStats();
485 switch (cmd.depth()) {
486 case Protocol::FetchCollectionsCommand::BaseCollection:
489 case Protocol::FetchCollectionsCommand::ParentCollection:
492 case Protocol::FetchCollectionsCommand::AllCollections:
497 switch (cmd.ancestorsDepth()) {
498 case Protocol::Ancestor::NoAncestor:
501 case Protocol::Ancestor::ParentAncestor:
504 case Protocol::Ancestor::AllAncestors:
505 mAncestorDepth = INT_MAX;
508 mAncestorAttributes = cmd.ancestorsAttributes();
510 Scope scope = cmd.collections();
511 if (!isRootCollection(scope)) {
513 if (scope.scope() == Scope::Uid) {
514 col = Collection::retrieveById(scope.uid());
515 }
else if (scope.scope() == Scope::Rid) {
517 qb.
addValueCondition(Collection::remoteIdFullColumnName(), Query::Equals, scope.rid());
519 if (mCollectionsToSynchronize) {
520 qb.
addCondition(filterCondition(Collection::syncPrefFullColumnName()));
521 }
else if (mCollectionsToDisplay) {
522 qb.
addCondition(filterCondition(Collection::displayPrefFullColumnName()));
523 }
else if (mCollectionsToIndex) {
524 qb.
addCondition(filterCondition(Collection::indexPrefFullColumnName()));
526 if (mResource.isValid()) {
527 qb.
addValueCondition(Resource::idFullColumnName(), Query::Equals, mResource.id());
528 }
else if (connection()->context().resource().isValid()) {
529 qb.
addValueCondition(Resource::idFullColumnName(), Query::Equals, connection()->context().resource().
id());
531 return failureResponse(
"Cannot retrieve collection based on remote identifier without a resource context");
534 return failureResponse(
"Unable to retrieve collection for listing");
537 if (results.
count() != 1) {
538 return failureResponse(
QString::number(results.
count()) + QStringLiteral(
" collections found"));
540 col = results.
first();
541 }
else if (scope.scope() == Scope::HierarchicalRid) {
542 if (!connection()->context().resource().isValid()) {
543 return failureResponse(
"Cannot retrieve collection based on hierarchical remote identifier without a resource context");
547 return failureResponse(
"Unexpected error");
550 if (!col.isValid()) {
551 return failureResponse(
"Collection does not exist");
554 retrieveCollections(col, depth);
561 return successResponse<Protocol::FetchCollectionsResponse>();
Represents a collection of PIM items.
qint64 Id
Describes the unique id type.
bool parseStream() override
Parse and handle the IMAP message using the streaming parser.
virtual void activeCachePolicy(Collection &col)
Determines the active cache policy for this Collection.
static Protocol::FetchCollectionsResponse fetchCollectionsResponse(AkonadiServer &akonadi, const Collection &col)
Returns the protocol representation of the given collection.
The handler interfaces describes an entity capable of handling an AkonadiIMAP command.
Helper class to construct arbitrary SQL queries.
void addValueCondition(const QString &column, Query::CompareOperator op, const QVariant &value, ConditionType type=WhereCondition)
Add a WHERE or HAVING condition which compares a column with a given value.
void addGroupColumn(const QString &column)
Add a GROUP BY column.
void addJoin(JoinType joinType, const QString &table, const Query::Condition &condition)
Join a table to the query.
bool exec()
Executes the query, returns true on success.
void addCondition(const Query::Condition &condition, ConditionType type=WhereCondition)
Add a WHERE condition.
@ InnerJoin
NOTE: only supported for UPDATE and SELECT queries.
@ LeftJoin
NOTE: only supported for SELECT queries.
Represents a WHERE condition tree.
Helper class for creating and executing database SELECT queries.
QList< T > result()
Returns the result of this SELECT query.
Q_SCRIPTABLE Q_NOREPLY void start()
Collection resolveHierarchicalRID(const QList< Scope::HRID > &hridChain, Resource::Id resId)
Retrieve the collection referred to by the given hierarchical RID chain.
Helper integration between Akonadi and Qt.
VehicleSection::Type type(QStringView coachNumber, QStringView coachClassification)
void append(QList< T > &&value)
bool contains(const AT &value) const const
qsizetype count() const const
bool isEmpty() const const
void prepend(parameter_type value)
void reserve(qsizetype size)
qsizetype size() const const
T value(qsizetype i) const const
iterator find(const Key &key, const T &value)
iterator insert(const Key &key, const T &value)
iterator insert(const T &value)
bool isEmpty() const const
qsizetype size() const const
QString number(double n, char format, int precision)
QFuture< void > filter(QThreadPool *pool, Sequence &sequence, KeepFunctor &&filterFunction)