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 "akonadiserver_debug.h"
11 #include "cachecleaner.h"
12 #include "connection.h"
13 #include "handlerhelper.h"
14 #include "intervalcheck.h"
15 #include "search/searchmanager.h"
16 #include "shared/akranges.h"
17 #include "storage/collectionqueryhelper.h"
18 #include "storage/datastore.h"
19 #include "storage/itemretriever.h"
20 #include "storage/selectquerybuilder.h"
21 #include "storage/transaction.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 
32 bool CollectionModifyHandler::parseStream()
33 {
34  const auto &cmd = Protocol::cmdCast<Protocol::ModifyCollectionCommand>(m_command);
35 
36  Collection collection = HandlerHelper::collectionFromScope(cmd.collection(), connection()->context());
37  if (!collection.isValid()) {
38  return failureResponse("No such collection");
39  }
40 
41  CacheCleanerInhibitor inhibitor(akonadi(), false);
42 
43  if (cmd.modifiedParts() & Protocol::ModifyCollectionCommand::ParentID) {
44  const Collection newParent = Collection::retrieveById(cmd.parentId());
45  if (newParent.isValid() && collection.parentId() != newParent.id() && 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" << collection.remoteId() << "to" << cmd.remoteId();
138  return failureResponse("Only resources can modify remote identifiers");
139  }
140  collection.setRemoteId(cmd.remoteId());
141  changes.append(AKONADI_PARAM_REMOTEID);
142  }
143  }
144 
145  if (cmd.modifiedParts() & Protocol::ModifyCollectionCommand::RemoteRevision) {
146  if (cmd.remoteRevision() != collection.remoteRevision()) {
147  collection.setRemoteRevision(cmd.remoteRevision());
148  changes.append(AKONADI_PARAM_REMOTEREVISION);
149  }
150  }
151 
152  if (cmd.modifiedParts() & Protocol::ModifyCollectionCommand::PersistentSearch) {
153  bool changed = false;
154  if (cmd.persistentSearchQuery() != collection.queryString()) {
155  collection.setQueryString(cmd.persistentSearchQuery());
156  changed = true;
157  }
158 
159  QList<QByteArray> queryAttributes = collection.queryAttributes().toUtf8().split(' ');
160  if (cmd.persistentSearchRemote() != queryAttributes.contains(AKONADI_PARAM_REMOTE)) {
161  if (cmd.persistentSearchRemote()) {
162  queryAttributes.append(AKONADI_PARAM_REMOTE);
163  } else {
164  queryAttributes.removeOne(AKONADI_PARAM_REMOTE);
165  }
166  changed = true;
167  }
168  if (cmd.persistentSearchRecursive() != queryAttributes.contains(AKONADI_PARAM_RECURSIVE)) {
169  if (cmd.persistentSearchRecursive()) {
170  queryAttributes.append(AKONADI_PARAM_RECURSIVE);
171  } else {
172  queryAttributes.removeOne(AKONADI_PARAM_RECURSIVE);
173  }
174  changed = true;
175  }
176  if (changed) {
177  collection.setQueryAttributes(QString::fromLatin1(queryAttributes.join(' ')));
178  }
179 
180  QList<qint64> inCols = cmd.persistentSearchCollections();
181  std::sort(inCols.begin(), inCols.end());
182  const auto cols = inCols | Views::transform([](const auto col) {
183  return QString::number(col);
184  })
185  | Actions::toQList;
186  const QString colStr = cols.join(QLatin1Char(' '));
187  if (colStr != collection.queryCollections()) {
188  collection.setQueryCollections(colStr);
189  changed = true;
190  }
191 
192  if (changed || cmd.modifiedParts() & Protocol::ModifyCollectionCommand::MimeTypes) {
193  changes.append(AKONADI_PARAM_PERSISTENTSEARCH);
194  }
195  }
196 
197  if (cmd.modifiedParts() & Protocol::ModifyCollectionCommand::ListPreferences) {
198  if (cmd.enabled() != collection.enabled()) {
199  collection.setEnabled(cmd.enabled());
200  changes.append(AKONADI_PARAM_ENABLED);
201  }
202  if (cmd.syncPref() != static_cast<Tristate>(collection.syncPref())) {
203  collection.setSyncPref(static_cast<Collection::Tristate>(cmd.syncPref()));
204  changes.append(AKONADI_PARAM_SYNC);
205  }
206  if (cmd.displayPref() != static_cast<Tristate>(collection.displayPref())) {
207  collection.setDisplayPref(static_cast<Collection::Tristate>(cmd.displayPref()));
208  changes.append(AKONADI_PARAM_DISPLAY);
209  }
210  if (cmd.indexPref() != static_cast<Tristate>(collection.indexPref())) {
211  collection.setIndexPref(static_cast<Collection::Tristate>(cmd.indexPref()));
212  changes.append(AKONADI_PARAM_INDEX);
213  }
214  }
215 
216  if (cmd.modifiedParts() & Protocol::ModifyCollectionCommand::RemovedAttributes) {
217  const auto attrs = cmd.removedAttributes();
218  for (const QByteArray &attr : attrs) {
219  if (db->removeCollectionAttribute(collection, attr)) {
220  changes.append(attr);
221  }
222  }
223  }
224 
225  if (cmd.modifiedParts() & Protocol::ModifyCollectionCommand::Attributes) {
226  const QMap<QByteArray, QByteArray> attrs = cmd.attributes();
227  for (auto iter = attrs.cbegin(), end = attrs.cend(); iter != end; ++iter) {
229  qb.addValueCondition(CollectionAttribute::collectionIdColumn(), Query::Equals, collection.id());
230  qb.addValueCondition(CollectionAttribute::typeColumn(), Query::Equals, iter.key());
231  if (!qb.exec()) {
232  return failureResponse("Unable to retrieve collection attribute");
233  }
234 
235  const CollectionAttribute::List attrsList = qb.result();
236  if (attrsList.isEmpty()) {
237  CollectionAttribute newAttr;
238  newAttr.setCollectionId(collection.id());
239  newAttr.setType(iter.key());
240  newAttr.setValue(iter.value());
241  if (!newAttr.insert()) {
242  return failureResponse("Unable to add collection attribute");
243  }
244  changes.append(iter.key());
245  } else if (attrsList.size() == 1) {
246  CollectionAttribute currAttr = attrsList.first();
247  if (currAttr.value() == iter.value()) {
248  continue;
249  }
250  currAttr.setValue(iter.value());
251  if (!currAttr.update()) {
252  return failureResponse("Unable to update collection attribute");
253  }
254  changes.append(iter.key());
255  } else {
256  return failureResponse("WTF: more than one attribute with the same name");
257  }
258  }
259  }
260 
261  if (!changes.isEmpty()) {
262  if (collection.hasPendingChanges() && !collection.update()) {
263  return failureResponse("Unable to update collection");
264  }
265  db->notificationCollector()->collectionChanged(collection, changes);
266  // For backwards compatibility. Must be after the changed notification (otherwise the compression removes it).
267  if (changes.contains(AKONADI_PARAM_ENABLED)) {
268  if (collection.enabled()) {
269  db->notificationCollector()->collectionSubscribed(collection);
270  } else {
272  }
273  }
274  if (!transaction.commit()) {
275  return failureResponse("Unable to commit transaction");
276  }
277 
278  // Only request Search update AFTER committing the transaction to avoid
279  // transaction deadlock with SQLite
280  if (changes.contains(AKONADI_PARAM_PERSISTENTSEARCH)) {
281  akonadi().searchManager().updateSearch(collection);
282  }
283  }
284 
285  return successResponse<Protocol::ModifyCollectionResponse>();
286 }
void append(const T &value)
NotificationCollector * notificationCollector()
Returns the notification collector of this DataStore object.
Definition: datastore.cpp:198
This class handles all the database access.
Definition: datastore.h:93
Helper class for DataStore transaction handling.
Definition: transaction.h:22
QString number(int n, int base)
void collectionUnsubscribed(const Collection &collection, const QByteArray &resource=QByteArray())
Notify about a collection unsubscription.
int removeAll(const T &value)
Represents a collection of PIM items.
Definition: collection.h:61
A RAII helper class to temporarily stop the CacheCleaner.
Definition: cachecleaner.h:31
void collectionSubscribed(const Collection &collection, const QByteArray &resource=QByteArray())
Notify about a collection subscription.
bool contains(const T &value) const const
QMap::const_iterator cbegin() const const
void setCollection(const Collection &collection, bool recursive=true)
Retrieve all items in the given collection.
QList< T > result()
Returns the result of this SELECT query.
QMap::const_iterator cend() const const
bool removeOne(const T &value)
bool isEmpty() const const
void setRemoteId(const QString &id)
Sets the remote id of the collection.
Definition: collection.cpp:101
void setEnabled(bool enabled)
Sets the collection's enabled state.
Definition: collection.cpp:357
bool isEmpty() const const
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 collectionChanged(const Collection &collection, const QList< QByteArray > &changes, const QByteArray &resource=QByteArray())
Notify about a changed collection.
QString remoteId() const
Returns the remote id of the collection.
Definition: collection.cpp:106
bool exec()
Executes the query, returns true on success.
virtual bool moveCollection(Collection &collection, const Collection &newParent)
moves the collection collection to newParent.
Definition: datastore.cpp:873
Helper class for creating and executing database SELECT queries.
The handler interfaces describes an entity capable of handling an AkonadiIMAP command.
Definition: handler.h:39
QString fromLatin1(const char *str, int size)
virtual bool removeCollectionAttribute(const Collection &col, const QByteArray &key)
Removes the given collection attribute for col.
Definition: datastore.cpp:1197
QList::iterator begin()
Helper class for retrieving missing items parts from remote resources.
Definition: itemretriever.h:38
QList::iterator end()
bool commit()
Commits the transaction.
Definition: transaction.cpp:29
void setName(const QString &name)
Sets the i18n'ed name of the collection.
Definition: collection.cpp:221
void setRemoteRevision(const QString &revision)
Sets the remote revision of the collection.
Definition: collection.cpp:111
Helper integration between Akonadi and Qt.
This file is part of the KDE documentation.
Documentation copyright © 1996-2023 The KDE developers.
Generated on Tue Nov 28 2023 03:52:30 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.