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 QSqlQuery attributeQuery = getAttributeQuery(ids, mAncestorAttributes);
176 while (attributeQuery.
next()) {
177 CollectionAttribute attr;
188static QSqlQuery 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();
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 QSqlQuery mimeTypeQuery(storageBackend()->database());
396 QSqlQuery attributeQuery(storageBackend()->database());
397 auto it = mCollections.begin();
398 while (it != mCollections.end()) {
404 if (!mimeTypeQuery.isValid() && mimetypeQueryStart < mimeTypeIds.size()) {
405 const QVariantList ids = mimeTypeIds.
mid(mimetypeQueryStart, querySizeLimit);
406 mimetypeQueryStart += querySizeLimit;
407 mimeTypeQuery = getMimeTypeQuery(ids);
408 mimeTypeQuery.next();
411 while (mimeTypeQuery.isValid() && mimeTypeQuery.value(0).toLongLong() < col.id()) {
412 if (!mimeTypeQuery.next()) {
417 while (mimeTypeQuery.isValid() && mimeTypeQuery.value(0).toLongLong() == col.id()) {
418 mimeTypes << mimeTypeQuery.
value(2).toString();
419 if (!mimeTypeQuery.next()) {
425 CollectionAttribute::List attributes;
428 if (!attributeQuery.
isValid() && attributeQueryStart < attributeIds.size()) {
429 const QVariantList ids = attributeIds.mid(attributeQueryStart, querySizeLimit);
430 attributeQueryStart += querySizeLimit;
432 attributeQuery.
next();
436 if (!attributeQuery.
next()) {
442 CollectionAttribute attr;
447 if (!attributeQuery.
next()) {
453 listCollection(col, ancestorsForCollection(col), mimeTypes, attributes);
457 mimeTypeQuery.finish();
462 const auto &cmd = Protocol::cmdCast<Protocol::FetchCollectionsCommand>(m_command);
464 if (!cmd.resource().isEmpty()) {
466 if (!mResource.isValid()) {
467 return failureResponse(QStringLiteral(
"Unknown resource %1").arg(cmd.resource()));
471 for (
const QString &mtName : lstMimeTypes) {
472 const MimeType mt = MimeType::retrieveByNameOrCreate(mtName);
474 return failureResponse(
"Failed to create mimetype record");
476 mMimeTypes.
append(mt.id());
479 mEnabledCollections = cmd.enabled();
480 mCollectionsToSynchronize = cmd.syncPref();
481 mCollectionsToDisplay = cmd.displayPref();
482 mCollectionsToIndex = cmd.indexPref();
483 mIncludeStatistics = cmd.fetchStats();
486 switch (cmd.depth()) {
487 case Protocol::FetchCollectionsCommand::BaseCollection:
490 case Protocol::FetchCollectionsCommand::ParentCollection:
493 case Protocol::FetchCollectionsCommand::AllCollections:
498 switch (cmd.ancestorsDepth()) {
499 case Protocol::Ancestor::NoAncestor:
502 case Protocol::Ancestor::ParentAncestor:
505 case Protocol::Ancestor::AllAncestors:
506 mAncestorDepth = INT_MAX;
509 mAncestorAttributes = cmd.ancestorsAttributes();
511 Scope scope = cmd.collections();
512 if (!isRootCollection(scope)) {
514 if (scope.scope() == Scope::Uid) {
515 col = Collection::retrieveById(scope.uid());
516 }
else if (scope.scope() == Scope::Rid) {
518 qb.
addValueCondition(Collection::remoteIdFullColumnName(), Query::Equals, scope.rid());
520 if (mCollectionsToSynchronize) {
521 qb.
addCondition(filterCondition(Collection::syncPrefFullColumnName()));
522 }
else if (mCollectionsToDisplay) {
523 qb.
addCondition(filterCondition(Collection::displayPrefFullColumnName()));
524 }
else if (mCollectionsToIndex) {
525 qb.
addCondition(filterCondition(Collection::indexPrefFullColumnName()));
527 if (mResource.isValid()) {
529 }
else if (connection()->context().resource().isValid()) {
532 return failureResponse(
"Cannot retrieve collection based on remote identifier without a resource context");
535 return failureResponse(
"Unable to retrieve collection for listing");
538 if (results.
count() != 1) {
539 return failureResponse(
QString::number(results.
count()) + QStringLiteral(
" collections found"));
541 col = results.
first();
542 }
else if (scope.scope() == Scope::HierarchicalRid) {
543 if (!connection()->context().resource().isValid()) {
544 return failureResponse(
"Cannot retrieve collection based on hierarchical remote identifier without a resource context");
548 return failureResponse(
"Unexpected error");
551 if (!col.isValid()) {
552 return failureResponse(
"Collection does not exist");
555 retrieveCollections(col, depth);
562 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)
const QList< QKeySequence > & end()
A glue between Qt and the standard library.
void append(QList< T > &&value)
bool contains(const AT &value) const const
qsizetype count() const const
bool isEmpty() const const
QList< T > mid(qsizetype pos, qsizetype length) 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)
T qobject_cast(QObject *object)
iterator insert(const T &value)
bool isEmpty() const const
qsizetype size() const const
bool isValid() const const
QVariant value(const QString &name) const const
QString number(double n, char format, int precision)
QFuture< void > filter(QThreadPool *pool, Sequence &sequence, KeepFunctor &&filterFunction)
QByteArray toByteArray() const const
qlonglong toLongLong(bool *ok) const const