Akonadi

collectionmodifyhandler.cpp
1 /*
2  SPDX-FileCopyrightText: 2006 Volker Krause <[email protected]>
3 
4  SPDX-License-Identifier: LGPL-2.0-or-later
5 */
6 
7 #include "collectionmodifyhandler.h"
8 
9 #include "akonadi.h"
10 #include "connection.h"
11 #include "handlerhelper.h"
12 #include "cachecleaner.h"
13 #include "intervalcheck.h"
14 #include "storage/datastore.h"
15 #include "storage/transaction.h"
16 #include "storage/itemretriever.h"
17 #include "storage/selectquerybuilder.h"
18 #include "storage/collectionqueryhelper.h"
19 #include "search/searchmanager.h"
20 #include "shared/akranges.h"
21 #include "akonadiserver_debug.h"
22 
23 using namespace Akonadi;
24 using namespace Akonadi::Server;
25 using namespace AkRanges;
26 
27 CollectionModifyHandler::CollectionModifyHandler(AkonadiServer &akonadi)
28  : Handler(akonadi)
29 {}
30 
31 bool CollectionModifyHandler::parseStream()
32 {
33  const auto &cmd = Protocol::cmdCast<Protocol::ModifyCollectionCommand>(m_command);
34 
35  Collection collection = HandlerHelper::collectionFromScope(cmd.collection(), connection()->context());
36  if (!collection.isValid()) {
37  return failureResponse("No such collection");
38  }
39 
40  CacheCleanerInhibitor inhibitor(akonadi(), false);
41 
42  if (cmd.modifiedParts() & Protocol::ModifyCollectionCommand::ParentID) {
43  const Collection newParent = Collection::retrieveById(cmd.parentId());
44  if (newParent.isValid() && collection.parentId() != newParent.id()
45  && collection.resourceId() != newParent.resourceId()) {
46  inhibitor.inhibit();
47  ItemRetriever retriever(akonadi().itemRetrievalManager(), connection(), connection()->context());
48  retriever.setCollection(collection, true);
49  retriever.setRetrieveFullPayload(true);
50  if (!retriever.exec()) {
51  throw HandlerException(retriever.lastError());
52  }
53  }
54  }
55 
56  DataStore *db = connection()->storageBackend();
57  Transaction transaction(db, QStringLiteral("MODIFY"));
58  QList<QByteArray> changes;
59 
60  if (cmd.modifiedParts() & Protocol::ModifyCollectionCommand::MimeTypes) {
61  QStringList mts = cmd.mimeTypes();
62  const MimeType::List currentMts = collection.mimeTypes();
63  bool equal = true;
64  for (const MimeType &currentMt : currentMts) {
65  const int removeMts = mts.removeAll(currentMt.name());
66  if (removeMts > 0) {
67  continue;
68  }
69  equal = false;
70  if (!collection.removeMimeType(currentMt)) {
71  return failureResponse("Unable to remove collection mimetype");
72  }
73  }
74  if (!db->appendMimeTypeForCollection(collection.id(), mts)) {
75  return failureResponse("Unable to add collection mimetypes");
76  }
77  if (!equal || !mts.isEmpty()) {
78  changes.append(AKONADI_PARAM_MIMETYPE);
79  }
80  }
81 
82  if (cmd.modifiedParts() & Protocol::ModifyCollectionCommand::CachePolicy) {
83  bool changed = false;
84  const Protocol::CachePolicy newCp = cmd.cachePolicy();
85  if (collection.cachePolicyCacheTimeout() != newCp.cacheTimeout()) {
86  collection.setCachePolicyCacheTimeout(newCp.cacheTimeout());
87  changed = true;
88  }
89  if (collection.cachePolicyCheckInterval() != newCp.checkInterval()) {
90  collection.setCachePolicyCheckInterval(newCp.checkInterval());
91  changed = true;
92  }
93  if (collection.cachePolicyInherit() != newCp.inherit()) {
94  collection.setCachePolicyInherit(newCp.inherit());
95  changed = true;
96  }
97 
98  QStringList parts = newCp.localParts();
99  std::sort(parts.begin(), parts.end());
100  const QString localParts = parts.join(QLatin1Char(' '));
101  if (collection.cachePolicyLocalParts() != localParts) {
102  collection.setCachePolicyLocalParts(localParts);
103  changed = true;
104  }
105  if (collection.cachePolicySyncOnDemand() != newCp.syncOnDemand()) {
106  collection.setCachePolicySyncOnDemand(newCp.syncOnDemand());
107  changed = true;
108  }
109 
110  if (changed) {
111  changes.append(AKONADI_PARAM_CACHEPOLICY);
112  }
113  }
114 
115  if (cmd.modifiedParts() & Protocol::ModifyCollectionCommand::Name) {
116  if (cmd.name() != collection.name()) {
117  if (!CollectionQueryHelper::hasAllowedName(collection, cmd.name(), collection.parentId())) {
118  return failureResponse("Collection with the same name exists already");
119  }
120  collection.setName(cmd.name());
121  changes.append(AKONADI_PARAM_NAME);
122  }
123  }
124 
125  if (cmd.modifiedParts() & Protocol::ModifyCollectionCommand::ParentID) {
126  if (collection.parentId() != cmd.parentId()) {
127  if (!db->moveCollection(collection, Collection::retrieveById(cmd.parentId()))) {
128  return failureResponse("Unable to reparent collection");
129  }
130  changes.append(AKONADI_PARAM_PARENT);
131  }
132  }
133 
134  if (cmd.modifiedParts() & Protocol::ModifyCollectionCommand::RemoteID) {
135  if (cmd.remoteId() != collection.remoteId() && !cmd.remoteId().isEmpty()) {
136  if (!connection()->isOwnerResource(collection)) {
137  qCWarning(AKONADISERVER_LOG) << "Invalid attempt to modify the collection remoteID from"
138  << collection.remoteId() << "to" << cmd.remoteId();
139  return failureResponse("Only resources can modify remote identifiers");
140  }
141  collection.setRemoteId(cmd.remoteId());
142  changes.append(AKONADI_PARAM_REMOTEID);
143  }
144  }
145 
146  if (cmd.modifiedParts() & Protocol::ModifyCollectionCommand::RemoteRevision) {
147  if (cmd.remoteRevision() != collection.remoteRevision()) {
148  collection.setRemoteRevision(cmd.remoteRevision());
149  changes.append(AKONADI_PARAM_REMOTEREVISION);
150  }
151  }
152 
153  if (cmd.modifiedParts() & Protocol::ModifyCollectionCommand::PersistentSearch) {
154  bool changed = false;
155  if (cmd.persistentSearchQuery() != collection.queryString()) {
156  collection.setQueryString(cmd.persistentSearchQuery());
157  changed = true;
158  }
159 
160  QList<QByteArray> queryAttributes = collection.queryAttributes().toUtf8().split(' ');
161  if (cmd.persistentSearchRemote() != queryAttributes.contains(AKONADI_PARAM_REMOTE)) {
162  if (cmd.persistentSearchRemote()) {
163  queryAttributes.append(AKONADI_PARAM_REMOTE);
164  } else {
165  queryAttributes.removeOne(AKONADI_PARAM_REMOTE);
166  }
167  changed = true;
168  }
169  if (cmd.persistentSearchRecursive() != queryAttributes.contains(AKONADI_PARAM_RECURSIVE)) {
170  if (cmd.persistentSearchRecursive()) {
171  queryAttributes.append(AKONADI_PARAM_RECURSIVE);
172  } else {
173  queryAttributes.removeOne(AKONADI_PARAM_RECURSIVE);
174  }
175  changed = true;
176  }
177  if (changed) {
178  collection.setQueryAttributes(QString::fromLatin1(queryAttributes.join(' ')));
179  }
180 
181  QVector<qint64> inCols = cmd.persistentSearchCollections();
182  std::sort(inCols.begin(), inCols.end());
183  const auto cols = inCols | Views::transform([](const auto col) { return QString::number(col); }) | Actions::toQList;
184  const QString colStr = cols.join(QLatin1Char(' '));
185  if (colStr != collection.queryCollections()) {
186  collection.setQueryCollections(colStr);
187  changed = true;
188  }
189 
190  if (changed || cmd.modifiedParts() & Protocol::ModifyCollectionCommand::MimeTypes) {
191  changes.append(AKONADI_PARAM_PERSISTENTSEARCH);
192  }
193  }
194 
195  if (cmd.modifiedParts() & Protocol::ModifyCollectionCommand::ListPreferences) {
196  if (cmd.enabled() != collection.enabled()) {
197  collection.setEnabled(cmd.enabled());
198  changes.append(AKONADI_PARAM_ENABLED);
199  }
200  if (cmd.syncPref() != static_cast<Tristate>(collection.syncPref())) {
201  collection.setSyncPref(static_cast<Collection::Tristate>(cmd.syncPref()));
202  changes.append(AKONADI_PARAM_SYNC);
203  }
204  if (cmd.displayPref() != static_cast<Tristate>(collection.displayPref())) {
205  collection.setDisplayPref(static_cast<Collection::Tristate>(cmd.displayPref()));
206  changes.append(AKONADI_PARAM_DISPLAY);
207  }
208  if (cmd.indexPref() != static_cast<Tristate>(collection.indexPref())) {
209  collection.setIndexPref(static_cast<Collection::Tristate>(cmd.indexPref()));
210  changes.append(AKONADI_PARAM_INDEX);
211  }
212  }
213 
214  if (cmd.modifiedParts() & Protocol::ModifyCollectionCommand::RemovedAttributes) {
215  Q_FOREACH (const QByteArray &attr, cmd.removedAttributes()) {
216  if (db->removeCollectionAttribute(collection, attr)) {
217  changes.append(attr);
218  }
219  }
220  }
221 
222  if (cmd.modifiedParts() & Protocol::ModifyCollectionCommand::Attributes) {
223  const QMap<QByteArray, QByteArray> attrs = cmd.attributes();
224  for (auto iter = attrs.cbegin(), end = attrs.cend(); iter != end; ++iter) {
226  qb.addValueCondition(CollectionAttribute::collectionIdColumn(), Query::Equals, collection.id());
227  qb.addValueCondition(CollectionAttribute::typeColumn(), Query::Equals, iter.key());
228  if (!qb.exec()) {
229  return failureResponse("Unable to retrieve collection attribute");
230  }
231 
232  const CollectionAttribute::List attrs = qb.result();
233  if (attrs.isEmpty()) {
234  CollectionAttribute newAttr;
235  newAttr.setCollectionId(collection.id());
236  newAttr.setType(iter.key());
237  newAttr.setValue(iter.value());
238  if (!newAttr.insert()) {
239  return failureResponse("Unable to add collection attribute");
240  }
241  changes.append(iter.key());
242  } else if (attrs.size() == 1) {
243  CollectionAttribute currAttr = attrs.first();
244  if (currAttr.value() == iter.value()) {
245  continue;
246  }
247  currAttr.setValue(iter.value());
248  if (!currAttr.update()) {
249  return failureResponse("Unable to update collection attribute");
250  }
251  changes.append(iter.key());
252  } else {
253  return failureResponse("WTF: more than one attribute with the same name");
254  }
255  }
256  }
257 
258  if (!changes.isEmpty()) {
259  if (collection.hasPendingChanges() && !collection.update()) {
260  return failureResponse("Unable to update collection");
261  }
262  db->notificationCollector()->collectionChanged(collection, changes);
263  //For backwards compatibility. Must be after the changed notification (otherwise the compression removes it).
264  if (changes.contains(AKONADI_PARAM_ENABLED)) {
265  if (collection.enabled()) {
266  db->notificationCollector()->collectionSubscribed(collection);
267  } else {
268  db->notificationCollector()->collectionUnsubscribed(collection);
269  }
270  }
271  if (!transaction.commit()) {
272  return failureResponse("Unable to commit transaction");
273  }
274 
275  // Only request Search update AFTER committing the transaction to avoid
276  // transaction deadlock with SQLite
277  if (changes.contains(AKONADI_PARAM_PERSISTENTSEARCH)) {
278  akonadi().searchManager().updateSearch(collection);
279  }
280  }
281 
282  return successResponse<Protocol::ModifyCollectionResponse>();
283 }
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 setCollection(const Collection &collection, bool recursive=true)
Retrieve all items in the given collection.
QMap::const_iterator cend() const const
bool isValid() const
Returns whether the collection is valid.
Definition: collection.cpp:124
QVector::iterator begin()
Helper class for retrieving missing items parts from remote resources.
Definition: itemretriever.h:40
The handler interfaces describes an entity capable of handling an AkonadiIMAP command.
Definition: handler.h:35
Represents a collection of PIM items.
Definition: collection.h:63
A RAII helper class to temporarily stop the CacheCleaner.
Definition: cachecleaner.h:33
QString join(const QString &separator) const const
QString number(int n, int base)
void append(const T &value)
Helper class for DataStore transaction handling.
Definition: transaction.h:24
bool isEmpty() const const
int removeAll(const T &value)
QVector< T > result()
Returns the result of this SELECT query.
bool commit()
Commits the transaction.
Definition: transaction.cpp:29
QList::iterator end()
bool contains(const T &value) const const
QMap::const_iterator cbegin() const const
Helper class for creating and executing database SELECT queries.
Id id() const
Returns the unique identifier of the collection.
Definition: collection.cpp:99
Helper integration between Akonadi and Qt.
QString fromLatin1(const char *str, int size)
This class handles all the database access.
Definition: datastore.h:95
QVector::iterator end()
bool removeOne(const T &value)
QList::iterator begin()
bool exec()
Executes the query, returns true on success.
This file is part of the KDE documentation.
Documentation copyright © 1996-2020 The KDE developers.
Generated on Wed Jul 8 2020 23:15:02 by doxygen 1.8.11 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.