Akonadi

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

KDE's Doxygen guidelines are available online.