Akonadi

collectionmodifyhandler.cpp
1/*
2 SPDX-FileCopyrightText: 2006 Volker Krause <vkrause@kde.org>
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
23using namespace Akonadi;
24using namespace Akonadi::Server;
25using namespace AkRanges;
26
27CollectionModifyHandler::CollectionModifyHandler(AkonadiServer &akonadi)
28 : Handler(akonadi)
29{
30}
31
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()) {
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}
Represents a collection of PIM items.
Definition collection.h:62
void setName(const QString &name)
Sets the i18n'ed name of the collection.
void setRemoteId(const QString &id)
Sets the remote id of the collection.
void setEnabled(bool enabled)
Sets the collection's enabled state.
void setRemoteRevision(const QString &revision)
Sets the remote revision of the collection.
QString remoteId() const
Returns the remote id of the collection.
A RAII helper class to temporarily stop the CacheCleaner.
bool parseStream() override
Parse and handle the IMAP message using the streaming parser.
This class handles all the database access.
Definition datastore.h:95
virtual bool moveCollection(Collection &collection, const Collection &newParent)
moves the collection collection to newParent.
NotificationCollector * notificationCollector()
Returns the notification collector of this DataStore object.
virtual bool removeCollectionAttribute(const Collection &col, const QByteArray &key)
Removes the given collection attribute for col.
The handler interfaces describes an entity capable of handling an AkonadiIMAP command.
Definition handler.h:32
Helper class for retrieving missing items parts from remote resources.
void setCollection(const Collection &collection, bool recursive=true)
Retrieve all items in the given collection.
void collectionChanged(const Collection &collection, const QList< QByteArray > &changes, const QByteArray &resource=QByteArray())
Notify about a changed collection.
void collectionUnsubscribed(const Collection &collection, const QByteArray &resource=QByteArray())
Notify about a collection unsubscription.
void collectionSubscribed(const Collection &collection, const QByteArray &resource=QByteArray())
Notify about a collection subscription.
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.
bool exec()
Executes the query, returns true on success.
virtual void updateSearch(const Collection &collection)
Updates the search query synchronously.
Helper class for creating and executing database SELECT queries.
QList< T > result()
Returns the result of this SELECT query.
Helper class for DataStore transaction handling.
Definition transaction.h:23
bool commit()
Commits the transaction.
bool hasAllowedName(const Collection &collection, const QString &name, Collection::Id parent)
Checks if a collection could exist in the given parent folder with the given name.
Helper integration between Akonadi and Qt.
void append(QList< T > &&value)
iterator begin()
bool contains(const AT &value) const const
iterator end()
bool isEmpty() const const
qsizetype removeAll(const AT &t)
bool removeOne(const AT &t)
const_iterator cbegin() const const
const_iterator cend() const const
QString fromLatin1(QByteArrayView str)
bool isEmpty() const const
QString number(double n, char format, int precision)
QString join(QChar separator) const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Tue Mar 26 2024 11:13:38 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.