Akonadi

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