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) {
 
   45        return QStack<Collection>();
 
   47    QStack<Collection> ancestors;
 
   48    Collection parent = col;
 
   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");
 
   67CollectionAttribute::List CollectionFetchHandler::getAttributes(
const Collection &col, 
const QSet<QByteArray> &filter)
 
   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,
 
   89                                            const QStack<Collection> &ancestors,
 
   90                                            const QStringList &mimeTypes,
 
   91                                            const CollectionAttribute::List &attributes)
 
   93    QStack<CollectionAttribute::List> ancestorAttributes;
 
   95    if (!mAncestorAttributes.isEmpty()) {
 
   97        for (
const Collection &col : ancestors) {
 
   98            ancestorAttributes.
push(getAttributes(col, mAncestorAttributes));
 
  103    Collection dummy = root;
 
  104    storageBackend()->activeCachePolicy(dummy);
 
  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))) {
 
  141static QueryBuilder getAttributeQuery(
const QVariantList &ids, 
const QSet<QByteArray> &requestedAttributes)
 
  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;
 
  223        SelectQueryBuilder<Collection> qb;
 
  227        } 
else if (depth == 1) {
 
  228            if (topParent.isValid()) {
 
  229                qb.
addValueCondition(Collection::parentIdFullColumnName(), Query::Equals, parentId);
 
  231                qb.
addValueCondition(Collection::parentIdFullColumnName(), Query::Is, QVariant());
 
  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());
 
  256            if (!mMimeTypes.isEmpty()) {
 
  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()};
 
  275        for (
const Collection &col : 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) {
 
  294                    Collection col = mCollections.value(
id);
 
  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();
 
  328        Collection parent = topParent;
 
  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();
 
  340    QSet<qint64> missingCollections;
 
  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()) {
 
  360        SelectQueryBuilder<Collection> qb;
 
  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()) {
 
  399        const Collection col = it.value();
 
  401        QStringList mimeTypes;
 
  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;
 
  431                attributeQb = getAttributeQuery(ids, QSet<QByteArray>());
 
  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.
 
QList< Collection > List
Describes a list of collections.
 
bool parseStream() override
Parse and handle the IMAP message using the streaming parser.
 
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)
 
bool contains(const AT &value) const const
 
qsizetype count() const const
 
void prepend(parameter_type value)
 
void reserve(qsizetype size)
 
qsizetype size() const const
 
T value(qsizetype i) const const
 
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)