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
259{
260 Flag::List flagList;
261 flagList.reserve(flagNames.size());
262 for (const QByteArray &flagName : flagNames) {
263 const Flag flag = Flag::retrieveByNameOrCreate(QString::fromUtf8(flagName));
264 if (!flag.isValid()) {
265 throw HandlerException("Unable to create flag");
266 }
267 flagList.append(flag);
268 }
269 return flagList;
270}
271
273{
274 if (tags.isEmpty()) {
275 return Tag::List();
276 }
278 qb.addValueCondition(Tag::idFullColumnName(), Query::In, tags);
279 if (!qb.exec()) {
280 throw HandlerException("Unable to resolve tags");
281 }
282 const Tag::List result = qb.result();
283 if (result.isEmpty()) {
284 throw HandlerException("No tags found");
285 }
286 return result;
287}
288
289Tag::List HandlerHelper::resolveTagsByGID(const QStringList &tagsGIDs)
290{
291 Tag::List tagList;
292 if (tagsGIDs.isEmpty()) {
293 return tagList;
294 }
295
296 for (const QString &tagGID : tagsGIDs) {
297 Tag::List tags = Tag::retrieveFiltered(Tag::gidColumn(), tagGID);
298 Tag tag;
299 if (tags.isEmpty()) {
300 tag.setGid(tagGID);
301 tag.setParentId(0);
302
303 const TagType type = TagType::retrieveByNameOrCreate(QStringLiteral("PLAIN"));
304 if (!type.isValid()) {
305 throw HandlerException("Unable to create tag type");
306 }
307 tag.setTagType(type);
308 if (!tag.insert()) {
309 throw HandlerException("Unable to create tag");
310 }
311 } else if (tags.count() == 1) {
312 tag = tags[0];
313 } else {
314 // Should not happen
315 throw HandlerException("Tag GID is not unique");
316 }
317
318 tagList.append(tag);
319 }
320
321 return tagList;
322}
323
324Tag::List HandlerHelper::resolveTagsByRID(const QStringList &tagsRIDs, const CommandContext &context)
325{
326 Tag::List tags;
327 if (tagsRIDs.isEmpty()) {
328 return tags;
329 }
330
331 if (!context.resource().isValid()) {
332 throw HandlerException("Tags can be resolved by their RID only in resource context");
333 }
334
335 tags.reserve(tagsRIDs.size());
336 for (const QString &tagRID : tagsRIDs) {
338 Query::Condition cond;
339 cond.addColumnCondition(Tag::idFullColumnName(), Query::Equals, TagRemoteIdResourceRelation::tagIdFullColumnName());
340 cond.addValueCondition(TagRemoteIdResourceRelation::resourceIdFullColumnName(), Query::Equals, context.resource().id());
341 qb.addJoin(QueryBuilder::LeftJoin, TagRemoteIdResourceRelation::tableName(), cond);
342 qb.addValueCondition(TagRemoteIdResourceRelation::remoteIdFullColumnName(), Query::Equals, tagRID);
343 if (!qb.exec()) {
344 throw HandlerException("Unable to resolve tags");
345 }
346
347 Tag tag;
348 Tag::List results = qb.result();
349 if (results.isEmpty()) {
350 // If the tag does not exist, we create a new one with GID matching RID
351 Tag::List tags = resolveTagsByGID(QStringList() << tagRID);
352 if (tags.count() != 1) {
353 throw HandlerException("Unable to resolve tag");
354 }
355 tag = tags[0];
356 TagRemoteIdResourceRelation rel;
357 rel.setRemoteId(tagRID);
358 rel.setTagId(tag.id());
359 rel.setResourceId(context.resource().id());
360 if (!rel.insert()) {
361 throw HandlerException("Unable to create tag");
362 }
363 } else if (results.count() == 1) {
364 tag = results[0];
365 } else {
366 throw HandlerException("Tag RID is not unique within this resource context");
367 }
368
369 tags.append(tag);
370 }
371
372 return tags;
373}
374
375Collection HandlerHelper::collectionFromScope(const Scope &scope, const CommandContext &context)
376{
377 if (scope.scope() == Scope::Invalid || scope.scope() == Scope::Gid) {
378 throw HandlerException("Invalid collection scope");
379 }
380
382 CollectionQueryHelper::scopeToQuery(scope, context, qb);
383 if (!qb.exec()) {
384 throw HandlerException("Failed to execute SQL query");
385 }
386
387 const Collection::List c = qb.result();
388 if (c.isEmpty()) {
389 return Collection();
390 } else if (c.count() == 1) {
391 return c.at(0);
392 } else {
393 throw HandlerException("Query returned more than one result");
394 }
395}
396
397Tag::List HandlerHelper::tagsFromScope(const Scope &scope, const CommandContext &context)
398{
399 if (scope.scope() == Scope::Invalid || scope.scope() == Scope::HierarchicalRid) {
400 throw HandlerException("Invalid tag scope");
401 }
402
403 if (scope.scope() == Scope::Uid) {
404 return resolveTagsByUID(scope.uidSet());
405 } else if (scope.scope() == Scope::Gid) {
406 return resolveTagsByGID(scope.gidSet());
407 } else if (scope.scope() == Scope::Rid) {
408 return resolveTagsByRID(scope.ridSet(), context);
409 }
410
411 Q_ASSERT(false);
412 return Tag::List();
413}
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.
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 Jul 26 2024 11:52:53 by doxygen 1.11.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.