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 }