Akonadi

handlerhelper.cpp
1/***************************************************************************
2 * SPDX-FileCopyrightText: 2006 Tobias Koenig <tokoe@kde.org> *
3 * *
4 * SPDX-License-Identifier: LGPL-2.0-or-later *
5 ***************************************************************************/
6
7#include "handlerhelper.h"
8#include "akonadi.h"
9#include "commandcontext.h"
10#include "connection.h"
11#include "handler.h"
12#include "storage/collectionqueryhelper.h"
13#include "storage/collectionstatistics.h"
14#include "storage/selectquerybuilder.h"
15#include "utils.h"
16
17#include "private/protocol_p.h"
18#include "private/scope_p.h"
19
20using namespace Akonadi;
21using namespace Akonadi::Server;
22
24{
25 // id is a number
26 bool ok = false;
27 qint64 collectionId = id.toLongLong(&ok);
28 if (ok) {
29 return Collection::retrieveById(collectionId);
30 }
31
32 // id is a path
33 QString path = QString::fromUtf8(id); // ### should be UTF-7 for real IMAP compatibility
34
35 const QStringList pathParts = path.split(QLatin1Char('/'), Qt::SkipEmptyParts);
36
37 Collection col;
38 for (const QString &part : pathParts) {
40 qb.addValueCondition(Collection::nameColumn(), Query::Equals, part);
41 if (col.isValid()) {
42 qb.addValueCondition(Collection::parentIdColumn(), Query::Equals, col.id());
43 } else {
44 qb.addValueCondition(Collection::parentIdColumn(), Query::Is, QVariant());
45 }
46 if (!qb.exec()) {
47 return Collection();
48 }
49 Collection::List list = qb.result();
50 if (list.count() != 1) {
51 return Collection();
52 }
53 col = list.first();
54 }
55 return col;
56}
57
59{
60 QStringList parts;
61 Collection current = col;
62 while (current.isValid()) {
63 parts.prepend(current.name());
64 current = current.parent();
65 }
66 return parts.join(QLatin1Char('/'));
67}
68
69Protocol::CachePolicy HandlerHelper::cachePolicyResponse(const Collection &col)
70{
71 Protocol::CachePolicy cachePolicy;
72 cachePolicy.setInherit(col.cachePolicyInherit());
73 cachePolicy.setCacheTimeout(col.cachePolicyCacheTimeout());
74 cachePolicy.setCheckInterval(col.cachePolicyCheckInterval());
75 if (!col.cachePolicyLocalParts().isEmpty()) {
76 cachePolicy.setLocalParts(col.cachePolicyLocalParts().split(QLatin1Char(' ')));
77 }
78 cachePolicy.setSyncOnDemand(col.cachePolicySyncOnDemand());
79 return cachePolicy;
80}
81
82Protocol::FetchCollectionsResponse HandlerHelper::fetchCollectionsResponse(AkonadiServer &akonadi, const Collection &col)
83{
84 QStringList mimeTypes;
85 const auto mimeTypesList = col.mimeTypes();
86 mimeTypes.reserve(mimeTypesList.size());
87 for (const MimeType &mt : mimeTypesList) {
88 mimeTypes << mt.name();
89 }
90
91 return fetchCollectionsResponse(akonadi, col, col.attributes(), false, 0, QStack<Collection>(), QStack<CollectionAttribute::List>(), mimeTypes);
92}
93
94Protocol::FetchCollectionsResponse HandlerHelper::fetchCollectionsResponse(AkonadiServer &akonadi,
95 const Collection &col,
96 const CollectionAttribute::List &attrs,
97 bool includeStatistics,
98 int ancestorDepth,
99 const QStack<Collection> &ancestors,
100 const QStack<CollectionAttribute::List> &ancestorAttributes,
101 const QStringList &mimeTypes)
102{
103 Protocol::FetchCollectionsResponse response;
104 response.setId(col.id());
105 response.setParentId(col.parentId());
106 response.setName(col.name());
107 response.setMimeTypes(mimeTypes);
108 response.setRemoteId(col.remoteId());
109 response.setRemoteRevision(col.remoteRevision());
110 response.setResource(col.resource().name());
111 response.setIsVirtual(col.isVirtual());
112
113 if (includeStatistics) {
114 const auto stats = akonadi.collectionStatistics().statistics(col);
115 if (stats.count > -1) {
116 Protocol::FetchCollectionStatsResponse statsResponse;
117 statsResponse.setCount(stats.count);
118 statsResponse.setUnseen(stats.count - stats.read);
119 statsResponse.setSize(stats.size);
120 response.setStatistics(statsResponse);
121 }
122 }
123
124 if (!col.queryString().isEmpty()) {
125 response.setSearchQuery(col.queryString());
126 QList<qint64> searchCols;
127 const QStringList searchColIds = col.queryCollections().split(QLatin1Char(' '));
128 searchCols.reserve(searchColIds.size());
129 for (const QString &searchColId : searchColIds) {
130 searchCols << searchColId.toLongLong();
131 }
132 response.setSearchCollections(searchCols);
133 }
134
135 Protocol::CachePolicy cachePolicy = cachePolicyResponse(col);
136 response.setCachePolicy(cachePolicy);
137
138 if (ancestorDepth) {
139 QList<Protocol::Ancestor> ancestorList = HandlerHelper::ancestorsResponse(ancestorDepth, ancestors, ancestorAttributes);
140 response.setAncestors(ancestorList);
141 }
142
143 response.setEnabled(col.enabled());
144 response.setDisplayPref(static_cast<Tristate>(col.displayPref()));
145 response.setSyncPref(static_cast<Tristate>(col.syncPref()));
146 response.setIndexPref(static_cast<Tristate>(col.indexPref()));
147
149 for (const CollectionAttribute &attr : attrs) {
150 ra.insert(attr.type(), attr.value());
151 }
152 response.setAttributes(ra);
153
154 return response;
155}
156
158HandlerHelper::ancestorsResponse(int ancestorDepth, const QStack<Collection> &_ancestors, const QStack<CollectionAttribute::List> &_ancestorsAttributes)
159{
161 if (ancestorDepth > 0) {
162 QStack<Collection> ancestors(_ancestors);
163 QStack<CollectionAttribute::List> ancestorAttributes(_ancestorsAttributes);
164 for (int i = 0; i < ancestorDepth; ++i) {
165 if (ancestors.isEmpty()) {
166 Protocol::Ancestor ancestor;
167 ancestor.setId(0);
168 rv << ancestor;
169 break;
170 }
171 const Collection c = ancestors.pop();
172 Protocol::Ancestor a;
173 a.setId(c.id());
174 a.setRemoteId(c.remoteId());
175 a.setName(c.name());
176 if (!ancestorAttributes.isEmpty()) {
178 const auto ancestorAttrs = ancestorAttributes.pop();
179 for (const CollectionAttribute &attr : ancestorAttrs) {
180 attrs.insert(attr.type(), attr.value());
181 }
182 a.setAttributes(attrs);
183 }
184
185 rv << a;
186 }
187 }
188
189 return rv;
190}
191
192Protocol::FetchTagsResponse HandlerHelper::fetchTagsResponse(const Tag &tag, const Protocol::TagFetchScope &tagFetchScope, Connection *connection)
193{
194 Protocol::FetchTagsResponse response;
195 response.setId(tag.id());
196 if (tagFetchScope.fetchIdOnly()) {
197 return response;
198 }
199
200 response.setType(tag.tagType().name().toUtf8());
201 // FIXME FIXME FIXME Terrible hack to workaround limitations of the generated entities code:
202 // The invalid parent is represented in code by -1 but in the DB it is stored as NULL, which
203 // gets converted to 0 by our entities code.
204 if (tag.parentId() == 0) {
205 response.setParentId(-1);
206 } else {
207 response.setParentId(tag.parentId());
208 }
209 response.setGid(tag.gid().toUtf8());
210 if (tagFetchScope.fetchRemoteID() && connection) {
211 // Fail silently if retrieving tag RID is not allowed in current context
212 if (connection->context().resource().isValid()) {
213 QueryBuilder qb(TagRemoteIdResourceRelation::tableName());
214 qb.addColumn(TagRemoteIdResourceRelation::remoteIdColumn());
215 qb.addValueCondition(TagRemoteIdResourceRelation::resourceIdColumn(), Query::Equals, connection->context().resource().id());
216 qb.addValueCondition(TagRemoteIdResourceRelation::tagIdColumn(), Query::Equals, tag.id());
217 if (!qb.exec()) {
218 throw HandlerException("Unable to query Tag Remote ID");
219 }
220 QSqlQuery query = qb.query();
221 // RID may not be available
222 if (query.next()) {
223 response.setRemoteId(Utils::variantToByteArray(query.value(0)));
224 }
225 query.finish();
226 }
227 }
228
229 if (tagFetchScope.fetchAllAttributes() || !tagFetchScope.attributes().isEmpty()) {
230 QueryBuilder qb(TagAttribute::tableName());
231 qb.addColumns({TagAttribute::typeFullColumnName(), TagAttribute::valueFullColumnName()});
232 Query::Condition cond(Query::And);
233 cond.addValueCondition(TagAttribute::tagIdFullColumnName(), Query::Equals, tag.id());
234 if (!tagFetchScope.fetchAllAttributes() && !tagFetchScope.attributes().isEmpty()) {
235 QVariantList types;
236 const auto scope = tagFetchScope.attributes();
237 std::transform(scope.cbegin(), scope.cend(), std::back_inserter(types), [](const QByteArray &ba) {
238 return QVariant(ba);
239 });
240 cond.addValueCondition(TagAttribute::typeFullColumnName(), Query::In, types);
241 }
242 qb.addCondition(cond);
243 if (!qb.exec()) {
244 throw HandlerException("Unable to query Tag Attributes");
245 }
246 QSqlQuery query = qb.query();
247 Protocol::Attributes attributes;
248 while (query.next()) {
249 attributes.insert(Utils::variantToByteArray(query.value(0)), Utils::variantToByteArray(query.value(1)));
250 }
251 query.finish();
252 response.setAttributes(attributes);
253 }
254
255 return response;
256}
257
258Protocol::FetchRelationsResponse HandlerHelper::fetchRelationsResponse(const Relation &relation)
259{
260 Protocol::FetchRelationsResponse resp;
261 resp.setLeft(relation.leftId());
262 resp.setLeftMimeType(relation.left().mimeType().name().toUtf8());
263 resp.setRight(relation.rightId());
264 resp.setRightMimeType(relation.right().mimeType().name().toUtf8());
265 resp.setType(relation.relationType().name().toUtf8());
266 return resp;
267}
268
270{
271 Flag::List flagList;
272 flagList.reserve(flagNames.size());
273 for (const QByteArray &flagName : flagNames) {
274 const Flag flag = Flag::retrieveByNameOrCreate(QString::fromUtf8(flagName));
275 if (!flag.isValid()) {
276 throw HandlerException("Unable to create flag");
277 }
278 flagList.append(flag);
279 }
280 return flagList;
281}
282
284{
285 if (tags.isEmpty()) {
286 return Tag::List();
287 }
289 qb.addValueCondition(Tag::idFullColumnName(), Query::In, tags);
290 if (!qb.exec()) {
291 throw HandlerException("Unable to resolve tags");
292 }
293 const Tag::List result = qb.result();
294 if (result.isEmpty()) {
295 throw HandlerException("No tags found");
296 }
297 return result;
298}
299
300Tag::List HandlerHelper::resolveTagsByGID(const QStringList &tagsGIDs)
301{
302 Tag::List tagList;
303 if (tagsGIDs.isEmpty()) {
304 return tagList;
305 }
306
307 for (const QString &tagGID : tagsGIDs) {
308 Tag::List tags = Tag::retrieveFiltered(Tag::gidColumn(), tagGID);
309 Tag tag;
310 if (tags.isEmpty()) {
311 tag.setGid(tagGID);
312 tag.setParentId(0);
313
314 const TagType type = TagType::retrieveByNameOrCreate(QStringLiteral("PLAIN"));
315 if (!type.isValid()) {
316 throw HandlerException("Unable to create tag type");
317 }
318 tag.setTagType(type);
319 if (!tag.insert()) {
320 throw HandlerException("Unable to create tag");
321 }
322 } else if (tags.count() == 1) {
323 tag = tags[0];
324 } else {
325 // Should not happen
326 throw HandlerException("Tag GID is not unique");
327 }
328
329 tagList.append(tag);
330 }
331
332 return tagList;
333}
334
335Tag::List HandlerHelper::resolveTagsByRID(const QStringList &tagsRIDs, const CommandContext &context)
336{
337 Tag::List tags;
338 if (tagsRIDs.isEmpty()) {
339 return tags;
340 }
341
342 if (!context.resource().isValid()) {
343 throw HandlerException("Tags can be resolved by their RID only in resource context");
344 }
345
346 tags.reserve(tagsRIDs.size());
347 for (const QString &tagRID : tagsRIDs) {
349 Query::Condition cond;
350 cond.addColumnCondition(Tag::idFullColumnName(), Query::Equals, TagRemoteIdResourceRelation::tagIdFullColumnName());
351 cond.addValueCondition(TagRemoteIdResourceRelation::resourceIdFullColumnName(), Query::Equals, context.resource().id());
352 qb.addJoin(QueryBuilder::LeftJoin, TagRemoteIdResourceRelation::tableName(), cond);
353 qb.addValueCondition(TagRemoteIdResourceRelation::remoteIdFullColumnName(), Query::Equals, tagRID);
354 if (!qb.exec()) {
355 throw HandlerException("Unable to resolve tags");
356 }
357
358 Tag tag;
359 Tag::List results = qb.result();
360 if (results.isEmpty()) {
361 // If the tag does not exist, we create a new one with GID matching RID
362 Tag::List tags = resolveTagsByGID(QStringList() << tagRID);
363 if (tags.count() != 1) {
364 throw HandlerException("Unable to resolve tag");
365 }
366 tag = tags[0];
367 TagRemoteIdResourceRelation rel;
368 rel.setRemoteId(tagRID);
369 rel.setTagId(tag.id());
370 rel.setResourceId(context.resource().id());
371 if (!rel.insert()) {
372 throw HandlerException("Unable to create tag");
373 }
374 } else if (results.count() == 1) {
375 tag = results[0];
376 } else {
377 throw HandlerException("Tag RID is not unique within this resource context");
378 }
379
380 tags.append(tag);
381 }
382
383 return tags;
384}
385
386Collection HandlerHelper::collectionFromScope(const Scope &scope, const CommandContext &context)
387{
388 if (scope.scope() == Scope::Invalid || scope.scope() == Scope::Gid) {
389 throw HandlerException("Invalid collection scope");
390 }
391
393 CollectionQueryHelper::scopeToQuery(scope, context, qb);
394 if (!qb.exec()) {
395 throw HandlerException("Failed to execute SQL query");
396 }
397
398 const Collection::List c = qb.result();
399 if (c.isEmpty()) {
400 return Collection();
401 } else if (c.count() == 1) {
402 return c.at(0);
403 } else {
404 throw HandlerException("Query returned more than one result");
405 }
406}
407
408Tag::List HandlerHelper::tagsFromScope(const Scope &scope, const CommandContext &context)
409{
410 if (scope.scope() == Scope::Invalid || scope.scope() == Scope::HierarchicalRid) {
411 throw HandlerException("Invalid tag scope");
412 }
413
414 if (scope.scope() == Scope::Uid) {
415 return resolveTagsByUID(scope.uidSet());
416 } else if (scope.scope() == Scope::Gid) {
417 return resolveTagsByGID(scope.gidSet());
418 } else if (scope.scope() == Scope::Rid) {
419 return resolveTagsByRID(scope.ridSet(), context);
420 }
421
422 Q_ASSERT(false);
423 return Tag::List();
424}
Represents a collection of PIM items.
Definition collection.h:62
Attribute::List attributes() const
Returns a list of all attributes of the collection.
QString remoteId() const
Returns the remote id of the collection.
QString mimeType() const
Returns the mime type of the item.
Definition item.cpp:331
An Akonadi Relation.
Definition relation.h:41
Item right() const
Returns the identifier of the right side of the relation.
Definition relation.cpp:75
Item left() const
Returns the identifier of the left side of the relation.
Definition relation.cpp:65
An Connection represents one connection of a client to the server.
Definition connection.h:39
static Protocol::CachePolicy cachePolicyResponse(const Collection &col)
Returns the protocol representation of the cache policy of the given Collection object.
static Tag::List resolveTagsByUID(const QList< qint64 > &tags)
Converts a imap set of tags into tag records.
static Protocol::FetchCollectionsResponse fetchCollectionsResponse(AkonadiServer &akonadi, const Collection &col)
Returns the protocol representation of the given collection.
static Collection collectionFromIdOrName(const QByteArray &id)
Returns the collection identified by the given id or path.
static Flag::List resolveFlags(const QSet< QByteArray > &flagNames)
Converts a bytearray list of flag names into flag records.
static QList< Protocol::Ancestor > ancestorsResponse(int ancestorDepth, const QStack< Collection > &ancestors, const QStack< CollectionAttribute::List > &_ancestorsAttributes=QStack< CollectionAttribute::List >())
Returns the protocol representation of a collection ancestor chain.
static QString pathForCollection(const Collection &col)
Returns the full path for the given collection.
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 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.
@ LeftJoin
NOTE: only supported for SELECT queries.
Represents a WHERE condition tree.
Definition query.h:62
void addColumnCondition(const QString &column, CompareOperator op, const QString &column2)
Add a WHERE condition which compares a column with another column.
Definition query.cpp:22
void addValueCondition(const QString &column, CompareOperator op, const QVariant &value)
Add a WHERE condition which compares a column with a given value.
Definition query.cpp:12
Helper class for creating and executing database SELECT queries.
QList< T > result()
Returns the result of this SELECT query.
An Akonadi Tag.
Definition tag.h:26
Id id() const
Returns the unique identifier of the tag.
Definition tag.cpp:139
void scopeToQuery(const Scope &scope, const CommandContext &context, QueryBuilder &qb)
Add conditions to qb for the given collection operation scope scope.
Helper integration between Akonadi and Qt.
KSERVICE_EXPORT KService::List query(FilterFunc filterFunc)
void append(QList< T > &&value)
const_reference at(qsizetype i) const const
qsizetype count() const const
T & first()
iterator insert(const_iterator before, parameter_type value)
bool isEmpty() const const
void prepend(parameter_type value)
void reserve(qsizetype size)
qsizetype size() const const
T value(qsizetype i) const const
iterator insert(const Key &key, const T &value)
qsizetype size() const const
QString fromUtf8(QByteArrayView str)
QStringList split(QChar sep, Qt::SplitBehavior behavior, Qt::CaseSensitivity cs) const const
QByteArray toUtf8() const const
QString join(QChar separator) const const
SkipEmptyParts
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Fri Jun 21 2024 11:57:49 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.