Akonadi

itemmovehandler.cpp
1 /*
2  SPDX-FileCopyrightText: 2009 Volker Krause <[email protected]>
3 
4  SPDX-License-Identifier: LGPL-2.0-or-later
5 */
6 
7 #include "itemmovehandler.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 "storage/collectionqueryhelper.h"
15 #include "storage/datastore.h"
16 #include "storage/itemqueryhelper.h"
17 #include "storage/itemretrievalmanager.h"
18 #include "storage/itemretriever.h"
19 #include "storage/selectquerybuilder.h"
20 #include "storage/transaction.h"
21 
22 using namespace Akonadi;
23 using namespace Akonadi::Server;
24 
25 ItemMoveHandler::ItemMoveHandler(AkonadiServer &akonadi)
26  : Handler(akonadi)
27 {
28 }
29 
30 void ItemMoveHandler::itemsRetrieved(const QVector<qint64> &ids)
31 {
32  DataStore *store = connection()->storageBackend();
33  Transaction transaction(store, QStringLiteral("MOVE"));
34 
36  qb.setForUpdate();
37  ItemQueryHelper::itemSetToQuery(ImapSet(ids), qb);
38  qb.addValueCondition(PimItem::collectionIdFullColumnName(), Query::NotEquals, mDestination.id());
39 
40  if (!qb.exec()) {
41  failureResponse("Unable to execute query");
42  return;
43  }
44 
45  const QVector<PimItem> items = qb.result();
46  if (items.isEmpty()) {
47  return;
48  }
49 
51  // Split the list by source collection
52  QMultiMap<Entity::Id /* collection */, PimItem> toMove;
53  QMap<Entity::Id /* collection */, Collection> sources;
54  ImapSet toMoveIds;
55  for (PimItem item : items) {
56  if (!item.isValid()) {
57  failureResponse("Invalid item in result set!?");
58  return;
59  }
60 
61  const Collection source = item.collection();
62  if (!source.isValid()) {
63  failureResponse("Item without collection found!?");
64  return;
65  }
66  if (!sources.contains(source.id())) {
67  sources.insert(source.id(), source);
68  }
69 
70  Q_ASSERT(item.collectionId() != mDestination.id());
71 
72  item.setCollectionId(mDestination.id());
73  item.setAtime(mtime);
74  item.setDatetime(mtime);
75  // if the resource moved itself, we assume it did so because the change happened in the backend
76  if (connection()->context().resource().id() != mDestination.resourceId()) {
77  item.setDirty(true);
78  }
79 
80  if (!item.update()) {
81  failureResponse("Unable to update item");
82  return;
83  }
84 
85  toMove.insert(source.id(), item);
86  toMoveIds.add(QVector<qint64>{item.id()});
87  }
88 
89  if (!transaction.commit()) {
90  failureResponse("Unable to commit transaction.");
91  return;
92  }
93 
94  // Emit notification for each source collection separately
95  Collection source;
96  PimItem::List itemsToMove;
97  for (auto it = toMove.cbegin(), end = toMove.cend(); it != end; ++it) {
98  if (source.id() != it.key()) {
99  if (!itemsToMove.isEmpty()) {
100  store->notificationCollector()->itemsMoved(itemsToMove, source, mDestination);
101  }
102  source = sources.value(it.key());
103  itemsToMove.clear();
104  }
105 
106  itemsToMove.push_back(*it);
107  }
108 
109  if (!itemsToMove.isEmpty()) {
110  store->notificationCollector()->itemsMoved(itemsToMove, source, mDestination);
111  }
112 
113  // Batch-reset RID
114  // The item should have an empty RID in the destination collection to avoid
115  // RID conflicts with existing items (see T3904 in Phab).
116  // We do it after emitting notification so that the FetchHelper can still
117  // retrieve the RID
118  QueryBuilder qb2(PimItem::tableName(), QueryBuilder::Update);
119  qb2.setColumnValue(PimItem::remoteIdColumn(), QString());
120  ItemQueryHelper::itemSetToQuery(toMoveIds, connection()->context(), qb2);
121  if (!qb2.exec()) {
122  failureResponse("Unable to update RID");
123  return;
124  }
125 }
126 
127 bool ItemMoveHandler::parseStream()
128 {
129  const auto &cmd = Protocol::cmdCast<Protocol::MoveItemsCommand>(m_command);
130 
131  mDestination = HandlerHelper::collectionFromScope(cmd.destination(), connection()->context());
132  if (mDestination.isVirtual()) {
133  return failureResponse("Moving items into virtual collection is not allowed");
134  }
135  if (!mDestination.isValid()) {
136  return failureResponse("Invalid destination collection");
137  }
138 
139  CommandContext context = connection()->context();
140  context.setScopeContext(cmd.itemsContext());
141  if (cmd.items().scope() == Scope::Rid) {
142  if (!context.collection().isValid()) {
143  return failureResponse("RID move requires valid source collection");
144  }
145  }
146 
147  CacheCleanerInhibitor inhibitor(akonadi());
148 
149  // make sure all the items we want to move are in the cache
150  ItemRetriever retriever(akonadi().itemRetrievalManager(), connection(), context);
151  retriever.setScope(cmd.items());
152  retriever.setRetrieveFullPayload(true);
153  QObject::connect(&retriever, &ItemRetriever::itemsRetrieved, [this](const QVector<qint64> &ids) {
154  itemsRetrieved(ids);
155  });
156  if (!retriever.exec()) {
157  return failureResponse(retriever.lastError());
158  }
159 
160  return successResponse<Protocol::MoveItemsResponse>();
161 }
NotificationCollector * notificationCollector()
Returns the notification collector of this DataStore object.
Definition: datastore.cpp:198
This class handles all the database access.
Definition: datastore.h:94
bool isEmpty() const const
Helper class for DataStore transaction handling.
Definition: transaction.h:22
void setForUpdate(bool forUpdate=true)
Indicate to the database to acquire an exclusive lock on the rows already during SELECT statement.
Represents a collection of PIM items.
Definition: collection.h:61
A RAII helper class to temporarily stop the CacheCleaner.
Definition: cachecleaner.h:31
QDateTime currentDateTimeUtc()
QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
QVector< T > result()
Returns the result of this SELECT query.
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 itemsMoved(const PimItem::List &items, const Collection &collectionSrc=Collection(), const Collection &collectionDest=Collection(), const QByteArray &sourceResource=QByteArray())
Notify about moved items Provide as many parameters as you have at hand currently,...
bool exec()
Executes the query, returns true on success.
void setScope(const Scope &scope)
Retrieve all items matching the given item scope.
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
Helper class for retrieving missing items parts from remote resources.
Definition: itemretriever.h:38
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-2023 The KDE developers.
Generated on Mon May 8 2023 03:52:16 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.