File indexing completed on 2024-06-23 05:07:02

0001 /*
0002     SPDX-FileCopyrightText: 2009 Volker Krause <vkrause@kde.org>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 #include "itemmovehandler.h"
0008 
0009 #include "akonadi.h"
0010 #include "akonadiserver_debug.h"
0011 #include "cachecleaner.h"
0012 #include "connection.h"
0013 #include "handlerhelper.h"
0014 #include "storage/collectionqueryhelper.h"
0015 #include "storage/datastore.h"
0016 #include "storage/itemqueryhelper.h"
0017 #include "storage/itemretrievalmanager.h"
0018 #include "storage/itemretriever.h"
0019 #include "storage/selectquerybuilder.h"
0020 #include "storage/transaction.h"
0021 
0022 using namespace Akonadi;
0023 using namespace Akonadi::Server;
0024 
0025 ItemMoveHandler::ItemMoveHandler(AkonadiServer &akonadi)
0026     : Handler(akonadi)
0027 {
0028 }
0029 
0030 void ItemMoveHandler::itemsRetrieved(const QList<qint64> &ids)
0031 {
0032     DataStore *store = connection()->storageBackend();
0033     Transaction transaction(store, QStringLiteral("MOVE"));
0034 
0035     SelectQueryBuilder<PimItem> qb;
0036     qb.setForUpdate();
0037     ItemQueryHelper::itemSetToQuery(ImapSet(ids), qb);
0038     qb.addValueCondition(PimItem::collectionIdFullColumnName(), Query::NotEquals, mDestination.id());
0039 
0040     if (!qb.exec()) {
0041         failureResponse("Unable to execute query");
0042         return;
0043     }
0044 
0045     const QList<PimItem> items = qb.result();
0046     if (items.isEmpty()) {
0047         return;
0048     }
0049 
0050     const QDateTime mtime = QDateTime::currentDateTimeUtc();
0051     // Split the list by source collection
0052     QMultiMap<Entity::Id /* collection */, PimItem> toMove;
0053     QMap<Entity::Id /* collection */, Collection> sources;
0054     ImapSet toMoveIds;
0055     for (PimItem item : items) {
0056         if (!item.isValid()) {
0057             failureResponse("Invalid item in result set!?");
0058             return;
0059         }
0060 
0061         const Collection source = item.collection();
0062         if (!source.isValid()) {
0063             failureResponse("Item without collection found!?");
0064             return;
0065         }
0066         if (!sources.contains(source.id())) {
0067             sources.insert(source.id(), source);
0068         }
0069 
0070         Q_ASSERT(item.collectionId() != mDestination.id());
0071 
0072         item.setCollectionId(mDestination.id());
0073         item.setAtime(mtime);
0074         item.setDatetime(mtime);
0075         // if the resource moved itself, we assume it did so because the change happened in the backend
0076         if (connection()->context().resource().id() != mDestination.resourceId()) {
0077             item.setDirty(true);
0078         }
0079 
0080         if (!item.update()) {
0081             failureResponse("Unable to update item");
0082             return;
0083         }
0084 
0085         toMove.insert(source.id(), item);
0086         toMoveIds.add(QList<qint64>{item.id()});
0087     }
0088 
0089     if (!transaction.commit()) {
0090         failureResponse("Unable to commit transaction.");
0091         return;
0092     }
0093 
0094     // Emit notification for each source collection separately
0095     Collection source;
0096     PimItem::List itemsToMove;
0097     for (auto it = toMove.cbegin(), end = toMove.cend(); it != end; ++it) {
0098         if (source.id() != it.key()) {
0099             if (!itemsToMove.isEmpty()) {
0100                 store->notificationCollector()->itemsMoved(itemsToMove, source, mDestination);
0101             }
0102             source = sources.value(it.key());
0103             itemsToMove.clear();
0104         }
0105 
0106         itemsToMove.push_back(*it);
0107     }
0108 
0109     if (!itemsToMove.isEmpty()) {
0110         store->notificationCollector()->itemsMoved(itemsToMove, source, mDestination);
0111     }
0112 
0113     // Batch-reset RID
0114     // The item should have an empty RID in the destination collection to avoid
0115     // RID conflicts with existing items (see T3904 in Phab).
0116     // We do it after emitting notification so that the FetchHelper can still
0117     // retrieve the RID
0118     QueryBuilder qb2(PimItem::tableName(), QueryBuilder::Update);
0119     qb2.setColumnValue(PimItem::remoteIdColumn(), QString());
0120     ItemQueryHelper::itemSetToQuery(toMoveIds, connection()->context(), qb2);
0121     if (!qb2.exec()) {
0122         failureResponse("Unable to update RID");
0123         return;
0124     }
0125 }
0126 
0127 bool ItemMoveHandler::parseStream()
0128 {
0129     const auto &cmd = Protocol::cmdCast<Protocol::MoveItemsCommand>(m_command);
0130 
0131     mDestination = HandlerHelper::collectionFromScope(cmd.destination(), connection()->context());
0132     if (mDestination.isVirtual()) {
0133         return failureResponse("Moving items into virtual collection is not allowed");
0134     }
0135     if (!mDestination.isValid()) {
0136         return failureResponse("Invalid destination collection");
0137     }
0138 
0139     CommandContext context = connection()->context();
0140     context.setScopeContext(cmd.itemsContext());
0141     if (cmd.items().scope() == Scope::Rid) {
0142         if (!context.collection().isValid()) {
0143             return failureResponse("RID move requires valid source collection");
0144         }
0145     }
0146 
0147     CacheCleanerInhibitor inhibitor(akonadi());
0148 
0149     // make sure all the items we want to move are in the cache
0150     ItemRetriever retriever(akonadi().itemRetrievalManager(), connection(), context);
0151     retriever.setScope(cmd.items());
0152     retriever.setRetrieveFullPayload(true);
0153     QObject::connect(&retriever, &ItemRetriever::itemsRetrieved, &retriever, [this](const QList<qint64> &ids) {
0154         itemsRetrieved(ids);
0155     });
0156     if (!retriever.exec()) {
0157         return failureResponse(retriever.lastError());
0158     }
0159 
0160     return successResponse<Protocol::MoveItemsResponse>();
0161 }