File indexing completed on 2024-05-26 05:14:35
0001 /*************************************************************************** 0002 * SPDX-FileCopyrightText: 2006 Tobias Koenig <tokoe@kde.org> * 0003 * * 0004 * SPDX-License-Identifier: LGPL-2.0-or-later * 0005 ***************************************************************************/ 0006 0007 #include "handlerhelper.h" 0008 #include "akonadi.h" 0009 #include "commandcontext.h" 0010 #include "connection.h" 0011 #include "handler.h" 0012 #include "storage/collectionqueryhelper.h" 0013 #include "storage/collectionstatistics.h" 0014 #include "storage/countquerybuilder.h" 0015 #include "storage/datastore.h" 0016 #include "storage/queryhelper.h" 0017 #include "storage/selectquerybuilder.h" 0018 #include "utils.h" 0019 0020 #include "private/imapset_p.h" 0021 #include "private/protocol_p.h" 0022 #include "private/scope_p.h" 0023 0024 using namespace Akonadi; 0025 using namespace Akonadi::Server; 0026 0027 Collection HandlerHelper::collectionFromIdOrName(const QByteArray &id) 0028 { 0029 // id is a number 0030 bool ok = false; 0031 qint64 collectionId = id.toLongLong(&ok); 0032 if (ok) { 0033 return Collection::retrieveById(collectionId); 0034 } 0035 0036 // id is a path 0037 QString path = QString::fromUtf8(id); // ### should be UTF-7 for real IMAP compatibility 0038 0039 const QStringList pathParts = path.split(QLatin1Char('/'), Qt::SkipEmptyParts); 0040 0041 Collection col; 0042 for (const QString &part : pathParts) { 0043 SelectQueryBuilder<Collection> qb; 0044 qb.addValueCondition(Collection::nameColumn(), Query::Equals, part); 0045 if (col.isValid()) { 0046 qb.addValueCondition(Collection::parentIdColumn(), Query::Equals, col.id()); 0047 } else { 0048 qb.addValueCondition(Collection::parentIdColumn(), Query::Is, QVariant()); 0049 } 0050 if (!qb.exec()) { 0051 return Collection(); 0052 } 0053 Collection::List list = qb.result(); 0054 if (list.count() != 1) { 0055 return Collection(); 0056 } 0057 col = list.first(); 0058 } 0059 return col; 0060 } 0061 0062 QString HandlerHelper::pathForCollection(const Collection &col) 0063 { 0064 QStringList parts; 0065 Collection current = col; 0066 while (current.isValid()) { 0067 parts.prepend(current.name()); 0068 current = current.parent(); 0069 } 0070 return parts.join(QLatin1Char('/')); 0071 } 0072 0073 Protocol::CachePolicy HandlerHelper::cachePolicyResponse(const Collection &col) 0074 { 0075 Protocol::CachePolicy cachePolicy; 0076 cachePolicy.setInherit(col.cachePolicyInherit()); 0077 cachePolicy.setCacheTimeout(col.cachePolicyCacheTimeout()); 0078 cachePolicy.setCheckInterval(col.cachePolicyCheckInterval()); 0079 if (!col.cachePolicyLocalParts().isEmpty()) { 0080 cachePolicy.setLocalParts(col.cachePolicyLocalParts().split(QLatin1Char(' '))); 0081 } 0082 cachePolicy.setSyncOnDemand(col.cachePolicySyncOnDemand()); 0083 return cachePolicy; 0084 } 0085 0086 Protocol::FetchCollectionsResponse HandlerHelper::fetchCollectionsResponse(AkonadiServer &akonadi, const Collection &col) 0087 { 0088 QStringList mimeTypes; 0089 const auto mimeTypesList = col.mimeTypes(); 0090 mimeTypes.reserve(mimeTypesList.size()); 0091 for (const MimeType &mt : mimeTypesList) { 0092 mimeTypes << mt.name(); 0093 } 0094 0095 return fetchCollectionsResponse(akonadi, col, col.attributes(), false, 0, QStack<Collection>(), QStack<CollectionAttribute::List>(), mimeTypes); 0096 } 0097 0098 Protocol::FetchCollectionsResponse HandlerHelper::fetchCollectionsResponse(AkonadiServer &akonadi, 0099 const Collection &col, 0100 const CollectionAttribute::List &attrs, 0101 bool includeStatistics, 0102 int ancestorDepth, 0103 const QStack<Collection> &ancestors, 0104 const QStack<CollectionAttribute::List> &ancestorAttributes, 0105 const QStringList &mimeTypes) 0106 { 0107 Protocol::FetchCollectionsResponse response; 0108 response.setId(col.id()); 0109 response.setParentId(col.parentId()); 0110 response.setName(col.name()); 0111 response.setMimeTypes(mimeTypes); 0112 response.setRemoteId(col.remoteId()); 0113 response.setRemoteRevision(col.remoteRevision()); 0114 response.setResource(col.resource().name()); 0115 response.setIsVirtual(col.isVirtual()); 0116 0117 if (includeStatistics) { 0118 const auto stats = akonadi.collectionStatistics().statistics(col); 0119 if (stats.count > -1) { 0120 Protocol::FetchCollectionStatsResponse statsResponse; 0121 statsResponse.setCount(stats.count); 0122 statsResponse.setUnseen(stats.count - stats.read); 0123 statsResponse.setSize(stats.size); 0124 response.setStatistics(statsResponse); 0125 } 0126 } 0127 0128 if (!col.queryString().isEmpty()) { 0129 response.setSearchQuery(col.queryString()); 0130 QList<qint64> searchCols; 0131 const QStringList searchColIds = col.queryCollections().split(QLatin1Char(' ')); 0132 searchCols.reserve(searchColIds.size()); 0133 for (const QString &searchColId : searchColIds) { 0134 searchCols << searchColId.toLongLong(); 0135 } 0136 response.setSearchCollections(searchCols); 0137 } 0138 0139 Protocol::CachePolicy cachePolicy = cachePolicyResponse(col); 0140 response.setCachePolicy(cachePolicy); 0141 0142 if (ancestorDepth) { 0143 QList<Protocol::Ancestor> ancestorList = HandlerHelper::ancestorsResponse(ancestorDepth, ancestors, ancestorAttributes); 0144 response.setAncestors(ancestorList); 0145 } 0146 0147 response.setEnabled(col.enabled()); 0148 response.setDisplayPref(static_cast<Tristate>(col.displayPref())); 0149 response.setSyncPref(static_cast<Tristate>(col.syncPref())); 0150 response.setIndexPref(static_cast<Tristate>(col.indexPref())); 0151 0152 QMap<QByteArray, QByteArray> ra; 0153 for (const CollectionAttribute &attr : attrs) { 0154 ra.insert(attr.type(), attr.value()); 0155 } 0156 response.setAttributes(ra); 0157 0158 return response; 0159 } 0160 0161 QList<Protocol::Ancestor> 0162 HandlerHelper::ancestorsResponse(int ancestorDepth, const QStack<Collection> &_ancestors, const QStack<CollectionAttribute::List> &_ancestorsAttributes) 0163 { 0164 QList<Protocol::Ancestor> rv; 0165 if (ancestorDepth > 0) { 0166 QStack<Collection> ancestors(_ancestors); 0167 QStack<CollectionAttribute::List> ancestorAttributes(_ancestorsAttributes); 0168 for (int i = 0; i < ancestorDepth; ++i) { 0169 if (ancestors.isEmpty()) { 0170 Protocol::Ancestor ancestor; 0171 ancestor.setId(0); 0172 rv << ancestor; 0173 break; 0174 } 0175 const Collection c = ancestors.pop(); 0176 Protocol::Ancestor a; 0177 a.setId(c.id()); 0178 a.setRemoteId(c.remoteId()); 0179 a.setName(c.name()); 0180 if (!ancestorAttributes.isEmpty()) { 0181 QMap<QByteArray, QByteArray> attrs; 0182 const auto ancestorAttrs = ancestorAttributes.pop(); 0183 for (const CollectionAttribute &attr : ancestorAttrs) { 0184 attrs.insert(attr.type(), attr.value()); 0185 } 0186 a.setAttributes(attrs); 0187 } 0188 0189 rv << a; 0190 } 0191 } 0192 0193 return rv; 0194 } 0195 0196 Protocol::FetchTagsResponse HandlerHelper::fetchTagsResponse(const Tag &tag, const Protocol::TagFetchScope &tagFetchScope, Connection *connection) 0197 { 0198 Protocol::FetchTagsResponse response; 0199 response.setId(tag.id()); 0200 if (tagFetchScope.fetchIdOnly()) { 0201 return response; 0202 } 0203 0204 response.setType(tag.tagType().name().toUtf8()); 0205 // FIXME FIXME FIXME Terrible hack to workaround limitations of the generated entities code: 0206 // The invalid parent is represented in code by -1 but in the DB it is stored as NULL, which 0207 // gets converted to 0 by our entities code. 0208 if (tag.parentId() == 0) { 0209 response.setParentId(-1); 0210 } else { 0211 response.setParentId(tag.parentId()); 0212 } 0213 response.setGid(tag.gid().toUtf8()); 0214 if (tagFetchScope.fetchRemoteID() && connection) { 0215 // Fail silently if retrieving tag RID is not allowed in current context 0216 if (connection->context().resource().isValid()) { 0217 QueryBuilder qb(TagRemoteIdResourceRelation::tableName()); 0218 qb.addColumn(TagRemoteIdResourceRelation::remoteIdColumn()); 0219 qb.addValueCondition(TagRemoteIdResourceRelation::resourceIdColumn(), Query::Equals, connection->context().resource().id()); 0220 qb.addValueCondition(TagRemoteIdResourceRelation::tagIdColumn(), Query::Equals, tag.id()); 0221 if (!qb.exec()) { 0222 throw HandlerException("Unable to query Tag Remote ID"); 0223 } 0224 QSqlQuery query = qb.query(); 0225 // RID may not be available 0226 if (query.next()) { 0227 response.setRemoteId(Utils::variantToByteArray(query.value(0))); 0228 } 0229 query.finish(); 0230 } 0231 } 0232 0233 if (tagFetchScope.fetchAllAttributes() || !tagFetchScope.attributes().isEmpty()) { 0234 QueryBuilder qb(TagAttribute::tableName()); 0235 qb.addColumns({TagAttribute::typeFullColumnName(), TagAttribute::valueFullColumnName()}); 0236 Query::Condition cond(Query::And); 0237 cond.addValueCondition(TagAttribute::tagIdFullColumnName(), Query::Equals, tag.id()); 0238 if (!tagFetchScope.fetchAllAttributes() && !tagFetchScope.attributes().isEmpty()) { 0239 QVariantList types; 0240 const auto scope = tagFetchScope.attributes(); 0241 std::transform(scope.cbegin(), scope.cend(), std::back_inserter(types), [](const QByteArray &ba) { 0242 return QVariant(ba); 0243 }); 0244 cond.addValueCondition(TagAttribute::typeFullColumnName(), Query::In, types); 0245 } 0246 qb.addCondition(cond); 0247 if (!qb.exec()) { 0248 throw HandlerException("Unable to query Tag Attributes"); 0249 } 0250 QSqlQuery query = qb.query(); 0251 Protocol::Attributes attributes; 0252 while (query.next()) { 0253 attributes.insert(Utils::variantToByteArray(query.value(0)), Utils::variantToByteArray(query.value(1))); 0254 } 0255 query.finish(); 0256 response.setAttributes(attributes); 0257 } 0258 0259 return response; 0260 } 0261 0262 Protocol::FetchRelationsResponse HandlerHelper::fetchRelationsResponse(const Relation &relation) 0263 { 0264 Protocol::FetchRelationsResponse resp; 0265 resp.setLeft(relation.leftId()); 0266 resp.setLeftMimeType(relation.left().mimeType().name().toUtf8()); 0267 resp.setRight(relation.rightId()); 0268 resp.setRightMimeType(relation.right().mimeType().name().toUtf8()); 0269 resp.setType(relation.relationType().name().toUtf8()); 0270 return resp; 0271 } 0272 0273 Flag::List HandlerHelper::resolveFlags(const QSet<QByteArray> &flagNames) 0274 { 0275 Flag::List flagList; 0276 flagList.reserve(flagNames.size()); 0277 for (const QByteArray &flagName : flagNames) { 0278 const Flag flag = Flag::retrieveByNameOrCreate(QString::fromUtf8(flagName)); 0279 if (!flag.isValid()) { 0280 throw HandlerException("Unable to create flag"); 0281 } 0282 flagList.append(flag); 0283 } 0284 return flagList; 0285 } 0286 0287 Tag::List HandlerHelper::resolveTagsByUID(const ImapSet &tags) 0288 { 0289 if (tags.isEmpty()) { 0290 return Tag::List(); 0291 } 0292 SelectQueryBuilder<Tag> qb; 0293 QueryHelper::setToQuery(tags, Tag::idFullColumnName(), qb); 0294 if (!qb.exec()) { 0295 throw HandlerException("Unable to resolve tags"); 0296 } 0297 const Tag::List result = qb.result(); 0298 if (result.isEmpty()) { 0299 throw HandlerException("No tags found"); 0300 } 0301 return result; 0302 } 0303 0304 Tag::List HandlerHelper::resolveTagsByGID(const QStringList &tagsGIDs) 0305 { 0306 Tag::List tagList; 0307 if (tagsGIDs.isEmpty()) { 0308 return tagList; 0309 } 0310 0311 for (const QString &tagGID : tagsGIDs) { 0312 Tag::List tags = Tag::retrieveFiltered(Tag::gidColumn(), tagGID); 0313 Tag tag; 0314 if (tags.isEmpty()) { 0315 tag.setGid(tagGID); 0316 tag.setParentId(0); 0317 0318 const TagType type = TagType::retrieveByNameOrCreate(QStringLiteral("PLAIN")); 0319 if (!type.isValid()) { 0320 throw HandlerException("Unable to create tag type"); 0321 } 0322 tag.setTagType(type); 0323 if (!tag.insert()) { 0324 throw HandlerException("Unable to create tag"); 0325 } 0326 } else if (tags.count() == 1) { 0327 tag = tags[0]; 0328 } else { 0329 // Should not happen 0330 throw HandlerException("Tag GID is not unique"); 0331 } 0332 0333 tagList.append(tag); 0334 } 0335 0336 return tagList; 0337 } 0338 0339 Tag::List HandlerHelper::resolveTagsByRID(const QStringList &tagsRIDs, const CommandContext &context) 0340 { 0341 Tag::List tags; 0342 if (tagsRIDs.isEmpty()) { 0343 return tags; 0344 } 0345 0346 if (!context.resource().isValid()) { 0347 throw HandlerException("Tags can be resolved by their RID only in resource context"); 0348 } 0349 0350 tags.reserve(tagsRIDs.size()); 0351 for (const QString &tagRID : tagsRIDs) { 0352 SelectQueryBuilder<Tag> qb; 0353 Query::Condition cond; 0354 cond.addColumnCondition(Tag::idFullColumnName(), Query::Equals, TagRemoteIdResourceRelation::tagIdFullColumnName()); 0355 cond.addValueCondition(TagRemoteIdResourceRelation::resourceIdFullColumnName(), Query::Equals, context.resource().id()); 0356 qb.addJoin(QueryBuilder::LeftJoin, TagRemoteIdResourceRelation::tableName(), cond); 0357 qb.addValueCondition(TagRemoteIdResourceRelation::remoteIdFullColumnName(), Query::Equals, tagRID); 0358 if (!qb.exec()) { 0359 throw HandlerException("Unable to resolve tags"); 0360 } 0361 0362 Tag tag; 0363 Tag::List results = qb.result(); 0364 if (results.isEmpty()) { 0365 // If the tag does not exist, we create a new one with GID matching RID 0366 Tag::List tags = resolveTagsByGID(QStringList() << tagRID); 0367 if (tags.count() != 1) { 0368 throw HandlerException("Unable to resolve tag"); 0369 } 0370 tag = tags[0]; 0371 TagRemoteIdResourceRelation rel; 0372 rel.setRemoteId(tagRID); 0373 rel.setTagId(tag.id()); 0374 rel.setResourceId(context.resource().id()); 0375 if (!rel.insert()) { 0376 throw HandlerException("Unable to create tag"); 0377 } 0378 } else if (results.count() == 1) { 0379 tag = results[0]; 0380 } else { 0381 throw HandlerException("Tag RID is not unique within this resource context"); 0382 } 0383 0384 tags.append(tag); 0385 } 0386 0387 return tags; 0388 } 0389 0390 Collection HandlerHelper::collectionFromScope(const Scope &scope, const CommandContext &context) 0391 { 0392 if (scope.scope() == Scope::Invalid || scope.scope() == Scope::Gid) { 0393 throw HandlerException("Invalid collection scope"); 0394 } 0395 0396 SelectQueryBuilder<Collection> qb; 0397 CollectionQueryHelper::scopeToQuery(scope, context, qb); 0398 if (!qb.exec()) { 0399 throw HandlerException("Failed to execute SQL query"); 0400 } 0401 0402 const Collection::List c = qb.result(); 0403 if (c.isEmpty()) { 0404 return Collection(); 0405 } else if (c.count() == 1) { 0406 return c.at(0); 0407 } else { 0408 throw HandlerException("Query returned more than one result"); 0409 } 0410 } 0411 0412 Tag::List HandlerHelper::tagsFromScope(const Scope &scope, const CommandContext &context) 0413 { 0414 if (scope.scope() == Scope::Invalid || scope.scope() == Scope::HierarchicalRid) { 0415 throw HandlerException("Invalid tag scope"); 0416 } 0417 0418 if (scope.scope() == Scope::Uid) { 0419 return resolveTagsByUID(scope.uidSet()); 0420 } else if (scope.scope() == Scope::Gid) { 0421 return resolveTagsByGID(scope.gidSet()); 0422 } else if (scope.scope() == Scope::Rid) { 0423 return resolveTagsByRID(scope.ridSet(), context); 0424 } 0425 0426 Q_ASSERT(false); 0427 return Tag::List(); 0428 }